Build with Vexless
Banking
Bank transactions are origins like any other. In production a bank connection delivers them; today you send them. Either way, you say what each one clears.
Bank accounts
A company's bank accounts are its externalAccounts, each mapped to a ledger account. A sandbox company has one from creation, FI0000000000000000 on account 1910. A live company gets one when a bank connection is completed:
POST /v1/companies/{companyId}/connections
{ "kind": "BANK", "provider": "op" }
{ "id": "con_…", "status": "NEEDS_AUTH", "consentUrl": "https://…" } # send your user there
Real bank providers are not connected in this version; the connection stays in NEEDS_AUTH. Bank transactions can always be sent through the origins endpoint, which is also how a bank feed you operate yourself delivers them.
Send a bank transaction
One line of a statement. You send both sides: the bank account, and what the money was.
POST /v1/companies/{companyId}/origins
Idempotency-Key: {companyId}-banktx-8812
{
"type": "BANK_TRANSACTION",
"externalId": "bank-tx-8812",
"iban": "FI0000000000000000",
"amount": 125500,
"bookingDate": "2026-09-15",
"valueDate": "2026-09-15",
"reference": "RF74000117",
"counterparty": { "name": "OSTAJA OY", "iban": "FI2112345600000785" },
"postings": [ { "accountNumber": "1910", "amount": 125500 },
{ "accountNumber": "1700", "amount": -125500, "partyId": "pty_…" } ],
"settles": [ { "originId": "org_invoice…" } ]
}
{ "id": "org_…", "type": "BANK_TRANSACTION", "status": "POSTED",
"parties": [ { "role": "PAYER", "partyId": "pty_…", "name": "Ostaja Oy", … } ],
"documentDate": "2026-09-15", "total": { "amount": 125500, "currency": "EUR" }, "externalReference": "bank-tx-8812",
"entries": [ { "number": 216, "version": 1, "postings": [
{ "accountNumber": "1910", "amount": 125500, … },
{ "accountNumber": "1700", "amount": -125500, "partyId": "pty_…", "remaining": 0, … } ] } ], … }
Fields
| externalId | stringrequired | The bank's transaction id; deduplicates |
| iban | string | The company's own account. Alternative: externalAccountId. |
| amount | integerrequired | Signed; positive is money in |
| bookingDate | daterequired | At most 60 days ahead. Becomes the origin's documentDate. |
| valueDate | date | |
| counterparty | party | Usually name and iban as the bank reports them. Becomes the payer or payee. |
| reference | string | RF or Finnish reference on the payment, kept on the origin |
| message | string | Free-text message as the bank reports it |
| postings | posting[] | Omit to leave the transaction awaiting an interpretation |
| settles | [{ originId, amount? }] | The invoices this transaction pays. See below. |
| currency, fxRate |
Say what it clears
Vexless does not work out which invoice a payment belongs to. There is no reference matcher and no suspense account. You name the origins in settles, and Vexless allocates the transaction's posting on the open-item account against each named origin's open item, marks the allocations EXPLICIT, and fires origin.settled for every origin that reaches zero.
settlesneedspostings: the allocation is between the named origin's open item and your posting on the same account. A receipt clears a receivable on 1700; a payment you made clears a payable on 2880.- One transaction can clear several invoices. Give each an
amountwhen the split matters; without one, the allocation is the smaller of the two remainders, applied in order. - A payment larger than the invoice leaves the difference open on the payer as a credit. A payment smaller than the invoice leaves the invoice
partial.
{ "type": "BANK_TRANSACTION", "externalId": "bank-tx-8813", "iban": "FI0000000000000000", "amount": 251000, "bookingDate": "2026-09-16",
"postings": [ { "accountNumber": "1910", "amount": 251000 }, { "accountNumber": "1700", "amount": -251000, "partyId": "pty_…" } ],
"settles": [ { "originId": "org_inv117…", "amount": 125500 }, { "originId": "org_inv119…", "amount": 125500 } ] }
When your software learns the link after the fact, for instance from a card processor's payout report, use POST /settlements with the posting ids from open items. Settlements covers both forms and the rules they share.
Money out
A payment sent through Vexless produces its own bank transaction when the bank executes it; the payment's bankTransactionOriginId points at it, and the bill is allocated without you doing anything. See Payments.
A payment made outside Vexless, from the bank's own interface, arrives as a transaction like any other. Send it with settles naming the bill:
{ "type": "BANK_TRANSACTION", "externalId": "bank-tx-9001", "iban": "FI0000000000000000", "amount": -125500, "bookingDate": "2026-09-15",
"counterparty": { "name": "TOIMITTAJA OY", "iban": "FI4250001510000023" }, "reference": "RF9812345678",
"postings": [ { "accountNumber": "2880", "amount": 125500, "partyId": "pty_…" }, { "accountNumber": "1910", "amount": -125500 } ],
"settles": [ { "originId": "org_bill…" } ] }
Transactions that are not invoices
Bank fees, interest, a loan drawdown, a transfer between own accounts. Post them where they belong and leave settles out. If your software does not know where they belong, send the transaction without postings and let the accountant say.
{ "type": "BANK_TRANSACTION", "externalId": "bank-tx-9002", "iban": "FI0000000000000000", "amount": -500, "bookingDate": "2026-09-30", "message": "Service fee",
"postings": [ { "accountNumber": "6900", "amount": 500 }, { "accountNumber": "1910", "amount": -500 } ] }
Duplicates and reversals
A transaction is deduplicated on externalId and on content: sending it again answers 409 ORIGIN_DUPLICATE with existingId. A feed that pushes the same transaction under two different ids is undone with a reversal: the second one gets a negated entry, its allocations are released, and any invoice it had paid is open again with origin.reversed telling you so.
Statement balances
Balance-level reconciliation, checking the ledger's bank balance against the balance the bank reports, is part of the design and not exposed in this version. GET /balances?accounts=1910 gives the ledger side at any date.
Events you will see
| origin.received | A transaction arrived without postings and is waiting | |
| origin.posted | The transaction is booked | |
| origin.settled | For each invoice the transaction cleared, with paidBy naming the transaction | |
| origin.reversed | Undone; any invoice it had paid is open again | |
| connection.needs_auth · reconciliation.broken | Defined; not emitted in this version |