Understand Vexless
Settlements
How open items are cleared, and why it is always stated.
Open items
Some accounts are open-item accounts: receivables, payables, a few others. A posting on one of them is a claim on a party, and it stays open until other postings on the same account clear it. remaining on the posting is what is left; a posting with remaining ≠ 0 is an open item. The invoice's receivable is a debit, the payment's is a credit, and when the credit is allocated against the debit both remainders fall to zero.
Allocations
An allocation is an amount between one debit posting and one credit posting on the same open-item account. Allocations that touch each other group into a settlement. The rules:
- Both postings are on the same open-item account, one debit and one credit.
- The amount is positive and does not exceed either posting's remaining.
- A posting belongs to at most one settlement.
- Every allocation records its
basisand itsactor. Everything you state isEXPLICIT.
Clearing is stated, never inferred. There is no reference matcher and no suspense account. Vexless does not decide which invoice a payment clears; the author of the transaction, or a later explicit settlement, says so. The REFERENCE and CONVENTION bases exist in the enum for an author that can say on what grounds it decided; nothing in Vexless produces them.
Two ways to state one
On the origin that pays
The simplest form. A bank transaction names the origins it pays in settles, and Vexless allocates the transaction's posting on the open-item account against each named origin's open item.
{ "type": "BANK_TRANSACTION", …,
"postings": [ { "accountNumber": "1910", "amount": 125500 }, { "accountNumber": "1700", "amount": -125500, "partyId": "pty_…" } ],
"settles": [ { "originId": "org_invoice…", "amount": 125500 } ] } # amount optional; defaults to the smaller remaining
Between posting ids
When the link is learned after the fact, from a card processor's payout report, a remittance advice, or a user matching by hand. The posting ids come from open items. Needs settlements:write.
POST /v1/companies/{companyId}/settlements
{ "allocations": [ { "debitPostingId": "pst_invoice…", "creditPostingId": "pst_payment…", "amount": 125500 } ] }
{ "id": "stl_…", "accountNumber": "1700", "status": "OPEN", "closedAt": null, "closedBy": null, "createdAt": "…",
"allocations": [ { "id": "alc_…", "debitPostingId": "pst_invoice…", "creditPostingId": "pst_payment…", "amount": 125500, "basis": "EXPLICIT", "actor": "user:mikko", "createdAt": "…" } ] }
Both forms produce the same allocations, the same settlement, and the same origin.settled for every origin whose open items reach zero.
Partial payments and overpayments
- Less than the invoice. The invoice keeps a remainder. Its
settlement.stateispartial,paidandremainingsay how much, and it staysPOSTED. The next payment clears the rest. - More than the invoice. The invoice settles. The difference stays open on the payer as a credit: an open item with a negative remaining, visible in open items and available to allocate against the customer's next invoice.
- One transfer, several invoices. Name each in
settleswith its amount, or send several allocations. - A credit note. Allocate its receivable credit against the original invoice's debit; both settle.
Settlement state on the origin
Every invoice origin carries a derived settlement:
"settlement": { "state": "partial", "paid": 100000, "remaining": 25500, "paidAt": null, "paidBy": ["org_banktx…"], "currency": "EUR" }
state is unpaid, partial or settled. paidBy lists the origins whose postings were allocated against it. paidAt is the date it reached zero. The origin's status becomes SETTLED at the same moment, and origin.settled fires once, on the way in. List invoices by it with GET /origins?settlementState=unpaid.
Residuals
A settlement that cannot reach zero, a few cents of rounding, a bank fee taken from the transfer, an FX difference, or a debt that will never be paid, is closed by an accountant with review:write. Vexless books the difference as a SYSTEM origin whose posting joins the settlement, and the settlement closes.
POST /v1/companies/{companyId}/settlements/{settlementId}/close
{ "kind": "WRITE_OFF", "date": "2026-09-30" } # kind: WRITE_OFF | FX_DIFFERENCE | FEE; defaults to WRITE_OFF
The origin that was written off settles, and origin.settled fires for it with the system origin in paidBy.
What reopens a settlement
A correction that replaces an entry releases the allocations on its replaced postings and reopens their settlements. A reversal releases the reversed origin's allocations. In both cases the affected invoice goes back from SETTLED to POSTED; origin.reversed tells you when a payment was undone, and origin.posted with a higher version when an invoice was corrected. Read the current state from the origin rather than assuming settled is forever.
Reading a settlement
GET /v1/companies/{companyId}/settlements/{settlementId}
Settlement ids are not on the origin; the way in is from the allocations you created, or through the open items that share a settlement.