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

sellerpartyrequiredAny one identifier. Include iban if you will pay it through Vexless.
documentDatedaterequiredThe invoice date as printed; at most 60 days ahead
linesline[]requiredSend vat per line as printed
externalReferencestringThe supplier's invoice number. Deduplicates, and helps recognise the payment later.
dueDatedateDefaults the payment's executeOn
totalintegerGross total as printed; refused if it does not equal lines net plus VAT
paymentReferencestringThe supplier's RF or Finnish reference; goes on the payment
postingsposting[]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.receivedThe bill arrived without postings and is waiting
origin.postedPosted, or re-posted with a higher version
origin.rejectedA reviewer decided it is not a document
payment.accepted · payment.rejected · payment.executedThe payment's progress at the bank
origin.settledThe bill is paid or otherwise cleared
origin.reversedUndone