Build with Vexless

Sales

Issue invoices from your software, let Vexless number and reference them, and learn when they are paid.

What Vexless does with a sales invoice

  • Assigns the invoice number, sequential and gapless per company, and the RF payment reference the buyer puts on the transfer.
  • Resolves the buyer to a party from any identifier you send.
  • Posts the entry from the postings you send, or holds the invoice at RECEIVED until someone else interprets it.
  • Tracks what is paid: the receivable is an open item, and settlement on the origin says how much of it has been cleared.
  • Records the delivery channel. Delivery itself is not performed in this version; see delivery.
  • Fires origin.posted and, when the money arrives, origin.settled.

Issue an invoice

One request. The document, the buyer, and what it means in the ledger.

POST /v1/companies/{companyId}/origins
Idempotency-Key: {companyId}-inv-2026-000117

{
  "type": "SALES_INVOICE",
  "externalReference": "2026-000117",
  "buyer": { "businessId": "7654321-1", "name": "Ostaja Oy", "externalId": "cust-42" },
  "dueDate": "2026-09-17",
  "lines": [ { "description": "Consulting, August 2026", "net": 100000, "vat": 25500, "taxCode": "FI_SALE_25_5", "quantity": 1, "unit": "pcs" } ],
  "postings": [
    { "accountNumber": "1700", "amount":  125500 },
    { "accountNumber": "3010", "amount": -100000, "taxCode": "FI_SALE_25_5" },
    { "accountNumber": "2939", "amount":  -25500, "taxCode": "FI_SALE_25_5" } ]
}
{ "id": "org_…", "type": "SALES_INVOICE", "status": "POSTED",
  "invoiceNumber": 117, "paymentReference": "RF74000117",
  "parties": [ { "role": "BUYER", "partyId": "pty_…", "name": "Ostaja Oy", "externalId": "cust-42" } ],
  "documentDate": "2026-09-03", "dueDate": "2026-09-17",
  "total": { "amount": 125500, "currency": "EUR" },
  "settlement": { "state": "unpaid", "paid": 0, "remaining": 125500, "paidAt": null, "paidBy": [], "currency": "EUR" },
  "delivery": { "channel": "PDF_EMAIL", "status": "PENDING_PROVIDER" },
  "entries": [ { "number": 214, "version": 1, … } ], … }

The three postings are the invoice as the ledger sees it: the receivable, the revenue, and the VAT. They must sum to zero. The receivable is on an open-item account, so it needs a party; a posting with no partyId takes the origin's counterparty, here the buyer. Interpretations explains the rules in full.

Fields

buyerpartyrequiredAny one identifier. See naming a party.
dueDatedaterequired
linesline[]requiredAt least one
externalReferencestringYour invoice id. Deduplicates, and comes back on every event.
documentDatedateDefaults to today; at most 60 days ahead
totalintegerGross total as issued; refused if it does not equal lines net plus VAT
postingsposting[]Omit to leave the invoice awaiting an interpretation
currency, fxRateFor foreign-currency invoices; see below
dataobjectAnything you want stored with the document. Returned as sent.

Lines

What the document said, kept as sent. Lines are not turned into postings; they are the record of the document and the basis of its total.

descriptionstringrequired
netintegerrequiredNet in the origin currency, minor units. Negative on credit notes.
taxCodestringe.g. FI_SALE_25_5, FI_SALE_EU_SERVICES. What the document says.
vatintegerThe VAT amount as printed
dimensionIdsstring[]At most one per category. See dimensions.
quantity, unitKept for invoice rendering

Issue it without postings

If your software does not know which accounts an invoice hits, send the invoice without postings. It still gets its number and RF reference, and it waits at RECEIVED. An accountant with review:write, an agent, or your own software later sends the interpretation, and you see origin.posted when it happens.

POST /v1/companies/{companyId}/origins/{originId}/interpretations
{ "postings": [ { "accountNumber": "1700", "amount": 125500 }, { "accountNumber": "3010", "amount": -100000, "taxCode": "FI_SALE_25_5" }, { "accountNumber": "2939", "amount": -25500, "taxCode": "FI_SALE_25_5" } ] }

Nothing is guessed in the meantime. There is no default account for a customer, no rule that reads the lines. The invoice is a document until somebody says what it means.

Track what is open

GET /v1/companies/{companyId}/open-items?direction=receivable&partyId=pty_…&dueBefore=2026-09-30
{ "items": [ { "originId": "org_…", "invoiceNumber": 117, "externalReference": "2026-000117", "paymentReference": "RF74000117",
               "partyName": "Ostaja Oy", "partyExternalId": "cust-42", "direction": "receivable",
               "amount": { "amount": 125500, "currency": "EUR" }, "remaining": { "amount": 125500, "currency": "EUR" },
               "documentDate": "2026-09-03", "dueDate": "2026-09-17", … } ],
  "next": null }

Filter by partyId for one customer's statement, by dueBefore for what is overdue. GET /origins?type=SALES_INVOICE&settlementState=unpaid gives the same invoices as full origins.

Money arrives as a BANK_TRANSACTION origin. In production the bank connection sends it; today you do, and you name the invoice it pays in settles. Vexless allocates the transaction's receivable posting against the invoice's, marks the allocation EXPLICIT, and fires origin.settled for the invoice.

X-Vexless-Event: origin.settled

{ "id": "evt_…", "type": "origin.settled", "companyId": "cmp_…", "occurredAt": "2026-09-15T09:12:03.120Z",
  "originId": "org_…", "externalReference": "2026-000117",
  "paidAt": "2026-09-15", "paid": { "amount": 125500, "currency": "EUR" }, "paidBy": ["org_banktx…"] }

A partial payment leaves the invoice at POSTED with settlement.state: "partial" and the remainder open. Banking shows the transaction side; Settlements explains partial payments and overpayments.

Credit notes

A credit note is a sales invoice with negative lines. It gets its own invoice number, and its postings credit the receivable. Clear it against the original with an explicit settlement between the two receivable postings; both then show as settled.

POST /v1/companies/{companyId}/origins
{ "type": "SALES_INVOICE", "externalReference": "2026-000118", "buyer": { "partyId": "pty_…" }, "dueDate": "2026-09-17",
  "lines": [ { "description": "Credit: consulting, August 2026", "net": -100000, "vat": -25500, "taxCode": "FI_SALE_25_5" } ],
  "postings": [ { "accountNumber": "1700", "amount": -125500 }, { "accountNumber": "3010", "amount": 100000, "taxCode": "FI_SALE_25_5" }, { "accountNumber": "2939", "amount": 25500, "taxCode": "FI_SALE_25_5" } ],
  "data": { "credits": "2026-000117" } }

POST /v1/companies/{companyId}/settlements
{ "allocations": [ { "debitPostingId": "pst_invoice…", "creditPostingId": "pst_creditnote…", "amount": 125500 } ] }

Delivery

Every sales invoice carries delivery: { channel, status }. The channel is EINVOICE when the buyer has an e-invoice address, otherwise PDF_EMAIL. In this version delivery is recorded and not performed: status stays PENDING_PROVIDER. Render and send the invoice yourself; the number and RF reference are in the response, and invoice.delivered is defined for when a provider is connected.

Foreign currency

Books are kept in the company currency, EUR. An invoice in another currency carries currency and fxRate, book units per origin unit. Line amounts and total are in the origin currency; postings are in book currency, and the original amount is kept on each posting. There is no rate source yet, so the rate is yours to supply.

{ "type": "SALES_INVOICE", "currency": "SEK", "fxRate": 0.0885, "lines": [ { "description": "…", "net": 1000000, "taxCode": "FI_SALE_EU_SERVICES" } ],
  "postings": [ { "accountNumber": "1700", "amount": 88500 }, { "accountNumber": "3400", "amount": -88500, "taxCode": "FI_SALE_EU_SERVICES" } ], … }

Corrections

An invoice that was wrong is credited, as above. An invoice that was booked wrong is re-interpreted by a reviewer: a new interpretation version replaces the entry under the same number and origin.posted fires again with a higher version. An invoice that should never have existed and has no payment allocated to it can be reversed.

Events you will see

origin.receivedThe invoice arrived without postings and is waiting
origin.postedPosted, or re-posted with a higher version
origin.settledFully paid or applied. Carries paid, paidAt, paidBy[]
origin.reversedUndone
invoice.deliveredDefined; not emitted in this version