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

externalIdstringrequiredThe bank's transaction id; deduplicates
ibanstringThe company's own account. Alternative: externalAccountId.
amountintegerrequiredSigned; positive is money in
bookingDatedaterequiredAt most 60 days ahead. Becomes the origin's documentDate.
valueDatedate
counterpartypartyUsually name and iban as the bank reports them. Becomes the payer or payee.
referencestringRF or Finnish reference on the payment, kept on the origin
messagestringFree-text message as the bank reports it
postingsposting[]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.

  • settles needs postings: 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 amount when 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.receivedA transaction arrived without postings and is waiting
origin.postedThe transaction is booked
origin.settledFor each invoice the transaction cleared, with paidBy naming the transaction
origin.reversedUndone; any invoice it had paid is open again
connection.needs_auth · reconciliation.brokenDefined; not emitted in this version