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. 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,
bankTransactionOriginIdpoints at it, and it is allocated against the bill.payment.executed, then the bill'sorigin.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.accepted | paymentId, originId, amount, bankReference | The bank took the transfer |
| payment.rejected | paymentId, originId, reason | |
| payment.executed | paymentId, originId, bankTransactionOriginId, amount | Money moved; the bill's origin.settled follows |
Real bank providers are not connected in this version. Payments execute only in the sandbox.