Build with Vexless

Payments

Vexless pays purchase invoices. Approval is your workflow: the call is the approval, and the actor on it is recorded as the approver. Nothing is posted when a payment is sent; the ledger moves when the bank transaction arrives, and the payment links the two.

Approve and send

POST /v1/companies/{companyId}/payments
X-Actor: user:mikko
Idempotency-Key: {companyId}-pay-org_bill…

{ "originId": "org_bill…", "executeOn": "2026-09-30", "amount": 50200 }   # amount defaults to remaining; executeOn to the due date
{ "id": "pay_…", "originId": "org_bill…", "status": "CREATED",
  "amount": { "amount": 50200, "currency": "EUR" }, "executeOn": "2026-09-30",
  "payeeIban": "FI4250001510000023", "paymentReference": "RF9812345678", "bankReference": null, "rejectReason": null,
  "bankTransactionOriginId": null, "actor": "user:mikko", "createdAt": "…", "updatedAt": "…" }

The payment is built from the bill: the payee IBAN your company supplied for the seller, the bill's payment reference, and the remaining amount unless you pay part of it. Several payments on one bill are fine; each is capped at what remains.

What happens next

CREATED→SENT→ACCEPTED→EXECUTED
  • CREATED. Held until executeOn. Cancellable.
  • SENT, then ACCEPTED with a bankReference. The bank took the transfer. payment.accepted.
  • EXECUTED. Money moved. The bank transaction arrives as its own origin, bankTransactionOriginId points at it, and it is allocated against the bill. payment.executed, then the bill's origin.settled.
  • REJECTED with a rejectReason. payment.rejected. The bill stays open; create a new payment when the cause is fixed.
  • CANCELLED. By you, while CREATED.

Until EXECUTED the ledger is untouched. There is no payments-in-transit account; the bill is open until the money has left.

The payee IBAN

Vexless pays only to an IBAN your company has supplied for the seller: on the bill's seller, or with a party update. Without one the call answers 422 PAYMENT_NO_PAYEE_IBAN.

PATCH /v1/companies/{companyId}/parties/{partyId}
{ "iban": "FI4250001510000023" }

Identifiers another company learned about the same supplier are never used for your payments.

Bills still in review

A payment on a bill that is still at RECEIVED is accepted and held until the bill is posted. Executing it before then, in the sandbox, answers 422 PAYMENT_INVALID_STATE with the bill's originStatus.

Cancel

POST /v1/companies/{companyId}/payments/{paymentId}/cancel     # while CREATED

Once the payment is SENT it is the bank's. Cancelling then answers 422 PAYMENT_INVALID_STATE with the current status.

In the sandbox

The bank never answers on its own. Execute or reject the payment yourself, and the same states and events follow:

POST /v1/companies/{companyId}/sandbox/payments/{paymentId}/execute   { "bookingDate": "2026-09-30" }   # body optional
POST /v1/companies/{companyId}/sandbox/payments/{paymentId}/reject    { "reason": "insufficient funds" }

Execution books the transaction today, or on executeOn when that date has already passed; pass bookingDate to choose. See Sandbox.

Reading payments

GET /v1/companies/{companyId}/payments/{paymentId}
GET /v1/companies/{companyId}/origins/{originId}       # payments[] lists every payment on the bill with id, status, amount, executeOn, bankTransactionOriginId

Events you will see

payment.acceptedpaymentId, originId, amount, bankReferenceThe bank took the transfer
payment.rejectedpaymentId, originId, reason
payment.executedpaymentId, originId, bankTransactionOriginId, amountMoney moved; the bill's origin.settled follows

Real bank providers are not connected in this version. Payments execute only in the sandbox.