Build with Vexless
Purchases
Capture the bills your customers receive, get them booked, and pay them.
Where bills come from
In production most purchase invoices arrive through the company's e-invoice connection and appear as origins without you doing anything. The ones your users capture themselves, a photographed receipt or a PDF from an email, you send. Both paths produce the same origin, the same events and the same open item. The e-invoice provider is not connected in this version, so for now every bill comes from you.
Send a bill
A bill your software can book itself: send the postings with it. The seller's VAT amounts are authoritative, so send vat per line as printed.
POST /v1/companies/{companyId}/origins
Idempotency-Key: {companyId}-bill-sup-88-A10442
{
"type": "PURCHASE_INVOICE",
"externalReference": "A10442",
"seller": { "businessId": "1111111-1", "name": "Toimittaja Oy", "externalId": "sup-88", "iban": "FI4250001510000023" },
"documentDate": "2026-09-01",
"dueDate": "2026-09-15",
"paymentReference": "RF9812345678",
"lines": [ { "description": "Office rent, September 2026", "net": 100000, "vat": 25500, "taxCode": "FI_PURCHASE_25_5" } ],
"total": 125500,
"postings": [
{ "accountNumber": "6000", "amount": 100000, "taxCode": "FI_PURCHASE_25_5" },
{ "accountNumber": "1763", "amount": 25500, "taxCode": "FI_PURCHASE_25_5" },
{ "accountNumber": "2880", "amount": -125500 } ]
}
{ "id": "org_…", "type": "PURCHASE_INVOICE", "status": "POSTED",
"parties": [ { "role": "SELLER", "partyId": "pty_…", "name": "Toimittaja Oy", "externalId": "sup-88" } ],
"externalReference": "A10442", "paymentReference": "RF9812345678", "dueDate": "2026-09-15",
"total": { "amount": 125500, "currency": "EUR" },
"settlement": { "state": "unpaid", "paid": 0, "remaining": 125500, "paidAt": null, "paidBy": [], "currency": "EUR" },
"entries": [ { "number": 215, "version": 1, "postings": [
{ "accountNumber": "6000", "accountName": "Vuokrat", "amount": 100000, … },
{ "accountNumber": "1763", "accountName": "Arvonlisäverosaamiset", "amount": 25500, … },
{ "accountNumber": "2880", "accountName": "Ostovelat", "amount": -125500, "partyId": "pty_…", "remaining": -125500, … } ] } ], … }
The payable posting on 2880 took the seller as its party. The iban on the seller is the account Vexless may pay this bill to; without one, a payment answers PAYMENT_NO_PAYEE_IBAN.
Fields
| seller | partyrequired | Any one identifier. Include iban if you will pay it through Vexless. |
| documentDate | daterequired | The invoice date as printed; at most 60 days ahead |
| lines | line[]required | Send vat per line as printed |
| externalReference | string | The supplier's invoice number. Deduplicates, and helps recognise the payment later. |
| dueDate | date | Defaults the payment's executeOn |
| total | integer | Gross total as printed; refused if it does not equal lines net plus VAT |
| paymentReference | string | The supplier's RF or Finnish reference; goes on the payment |
| postings | posting[] | Omit to leave the bill awaiting an interpretation |
| currency, fxRate, data |
Attach the document
The PDF, XML or photo behind the bill. Vexless stores it, keeps it for the statutory retention period, and deduplicates by content: sending the same bytes twice returns the existing attachment.
POST /v1/companies/{companyId}/origins/{originId}/attachments
{ "filename": "A10442.pdf", "mediaType": "application/pdf", "contentBase64": "…" } # at most 10 MB decoded
{ "id": "att_…", "originId": "org_…", "filename": "A10442.pdf", "mediaType": "application/pdf", "size": 48213,
"hash": "sha256…", "retentionUntil": "2032-12-31", "createdBy": "user:mikko", "createdAt": "…" }
GET …/attachments lists them; GET …/attachments/{attachmentId} returns the bytes with their media type and an X-Vexless-Sha256 header. Retention is six years from the end of the document's year.
A bill you cannot book yourself
Send it without postings. It stays RECEIVED, its payable is not yet an open item, and an accountant interprets it. You see origin.received now and origin.posted when they are done. Nothing is guessed in the meantime, and nothing about the supplier changes what happens to the next bill.
POST /v1/companies/{companyId}/origins
{ "type": "PURCHASE_INVOICE", "externalReference": "A10442", "seller": { "businessId": "1111111-1" },
"documentDate": "2026-09-01", "dueDate": "2026-09-15",
"lines": [ { "description": "Office rent, September 2026", "net": 100000, "vat": 25500, "taxCode": "FI_PURCHASE_25_5" } ] }
{ "id": "org_…", "status": "RECEIVED", "currentInterpretationId": null, "entries": [], "settlement": null, … }
Your software may also send the interpretation itself later, with origins:write, as long as the bill is still at RECEIVED:
POST /v1/companies/{companyId}/origins/{originId}/interpretations
{ "postings": [ { "accountNumber": "6000", "amount": 100000, "taxCode": "FI_PURCHASE_25_5" }, { "accountNumber": "1763", "amount": 25500, "taxCode": "FI_PURCHASE_25_5" }, { "accountNumber": "2880", "amount": -125500 } ] }
A supplier's defaultAccountNumber and defaultTaxCode, set on the party by an accountant, are read back to you as a hint for whoever writes the interpretation. Vexless never applies them.
Track what is owed
GET /v1/companies/{companyId}/open-items?direction=payable&dueBefore=2026-09-30
{ "items": [ { "originId": "org_…", "externalReference": "A10442", "paymentReference": "RF9812345678",
"partyName": "Toimittaja Oy", "partyExternalId": "sup-88", "direction": "payable",
"amount": { "amount": 125500, "currency": "EUR" }, "remaining": { "amount": 125500, "currency": "EUR" },
"documentDate": "2026-09-01", "dueDate": "2026-09-15", … } ],
"next": null }
Bills still at RECEIVED are not open items yet. List them with GET /origins?type=PURCHASE_INVOICE&status=RECEIVED.
Pay it
Through Vexless: one call, which is the approval. The actor on it is recorded as the approver.
POST /v1/companies/{companyId}/payments
X-Actor: user:mikko
{ "originId": "org_…", "executeOn": "2026-09-15" } # amount defaults to remaining; executeOn to the due date
Nothing is posted when the payment is sent. When the bank executes it, the bank transaction arrives as its own origin, is allocated against the bill, and you see payment.executed and then the bill's origin.settled. Payments covers states, cancellation and the sandbox.
Paid outside Vexless: send the bank transaction yourself with settles naming the bill. See Banking.
Corrections
A bill that arrived twice under different ids, or was captured wrong, is reversed: a new entry negates the original and the bill goes to REVERSED. A bill that was booked wrong is re-interpreted by a reviewer with review:write; the entry gets a new version under the same number and origin.posted fires again. A bill that is not a document at all is rejected by a reviewer while still at RECEIVED.
Events you will see
| origin.received | The bill arrived without postings and is waiting | |
| origin.posted | Posted, or re-posted with a higher version | |
| origin.rejected | A reviewer decided it is not a document | |
| payment.accepted · payment.rejected · payment.executed | The payment's progress at the bank | |
| origin.settled | The bill is paid or otherwise cleared | |
| origin.reversed | Undone |