Supplier invoices
AP lifecycle: register, approve, mark paid, credit. With ROT/RUT and reverse-charge support.
Endpoints
GET/api/v1/companies/:companyId/supplier-invoices: List supplier invoices for a company.GET/api/v1/companies/:companyId/supplier-invoices/:id: Retrieve a single supplier invoice by id.POST/api/v1/companies/:companyId/supplier-invoices: Register a new supplier invoice.POST/api/v1/companies/:companyId/supplier-invoices/:id/approve: Approve a registered or overdue supplier invoice.POST/api/v1/companies/:companyId/supplier-invoices/:id/credit: Issue a credit note for a supplier invoice.POST/api/v1/companies/:companyId/supplier-invoices/:id/mark-paid: Record a payment against a supplier invoice.PATCH/api/v1/companies/:companyId/supplier-invoices/:id: Update a registered supplier invoice.
GET /api/v1/companies/:companyId/supplier-invoices {#get-supplier-invoices-list}
supplier-invoices.list · scope suppliers:read
List supplier invoices for a company.
Cursor-paginated supplier-invoice list ordered by created_at DESC, id ASC (newest-registered first; the invoice_date column is the seller's invoice date and is filterable via ?date_from / ?date_to but is not the sort key). Filters: status, supplier_id, currency, date_from / date_to (filter by invoice_date).
Use when: You need to enumerate registered supplier invoices for an AP dashboard, a payment run, or a leverantörsreskontra reconciliation.
Don't use for: Fetching a single supplier invoice: use GET /supplier-invoices/{id}. Listing customer invoices (different resource).
Pitfalls
- Credit notes (is_credit_note=true) appear in the same list as the originals; filter by status=credited or check the flag to separate.
- remaining_amount is the unpaid portion; a partially_paid SI has remaining_amount > 0.
- arrival_number is internal book-keeping, not the seller's invoice number: use supplier_invoice_number for matching to received documents.
- Ordering is by created_at (registration time), not invoice_date. A late-registered invoice appears where it was registered: filter on ?date_from / ?date_to when you care about the seller's invoice date.
- Cursor pagination: pass ?cursor=<next_cursor> from the previous response. A stale or tampered cursor is ignored and the first page is returned again.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Example response
{
"data": [
{
"id": "0e9c…",
"supplier_id": "a8f1…",
"supplier_name": "Office Depot AB",
"arrival_number": 42,
"supplier_invoice_number": "2026-1234",
"invoice_date": "2026-05-10",
"due_date": "2026-06-09",
"status": "registered",
"currency": "SEK",
"subtotal": 1000,
"vat_amount": 250,
"total": 1250,
"paid_amount": 0,
"remaining_amount": 1250,
"is_credit_note": false,
"paid_at": null,
"created_at": "2026-05-13T15:00:00Z"
}
],
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12",
"next_cursor": null
}
}
GET /api/v1/companies/:companyId/supplier-invoices/:id {#get-supplier-invoices-get}
supplier-invoices.get · scope suppliers:read
Retrieve a single supplier invoice by id.
Returns the full supplier-invoice record. Pass ?expand=supplier,items,payments to embed the related rows in the same response.
Use when: You need the full record before approving, paying, or crediting it, or for audit trail / reconciliation.
Don't use for: Listing supplier invoices (use the list endpoint). Customer-invoice lookups (different resource).
Pitfalls
- Credit notes return is_credit_note=true and a credited_invoice_id pointing at the original.
- registration_journal_entry_id and payment_journal_entry_id let you trace the SI to its bokföring rows; they are null when no JE has been posted (e.g. on a kontantmetoden SI before payment).
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Example response
{
"data": {
"id": "0e9c…",
"supplier_id": "a8f1…",
"arrival_number": 42,
"supplier_invoice_number": "2026-1234",
"status": "registered",
"currency": "SEK",
"subtotal": 1000,
"vat_amount": 250,
"total": 1250,
"remaining_amount": 1250,
"is_credit_note": false
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/supplier-invoices {#post-supplier-invoices-create}
supplier-invoices.create · scope suppliers:write
Register a new supplier invoice.
Creates a supplier invoice in registered status and posts the registration journal entry under faktureringsmetoden (Debit expense + Debit 2641 Ingående moms / Credit 2440 Leverantörsskulder). Under kontantmetoden no JE is posted at this stage. Idempotent (mandatory Idempotency-Key). Dry-runnable.
Use when: You're registering an incoming leverantörsfaktura. Use dry-run first to validate VAT calculations + period-lock state before committing.
Don't use for: Marking an existing SI as paid (use POST /:id/mark-paid). Issuing a credit note (use POST /:id/credit). Customer invoices (different resource).
Pitfalls
- Idempotency-Key is mandatory.
- invoice_date must fall within an open fiscal period: a date covered by a locked period or the company-wide bookkeeping lock returns 400 PERIOD_LOCKED.
- Under faktureringsmetoden the registration JE is posted atomically with the SI row. JE failure aborts the whole call and no SI row is left behind (strict-mode).
- supplier_id must reference an existing, non-archived supplier in the same company: 404 SUPPLIER_NOT_FOUND otherwise.
- Duplicate (supplier_id, supplier_invoice_number) returns 409 SI_CREATE_DUPLICATE_INVOICE_NUMBER. Use the credit flow on the original instead of re-registering with a tweaked number.
- Foreign currency: omit exchange_rate and the server fetches Riksbanken's rate for invoice_date (ML 8 kap 21-23 §). If no rate can be resolved the create is refused with 400 SI_FX_RATE_MISSING rather than stored unconverted: pass exchange_rate explicitly to proceed. A SEK invoice needs no rate and gets total_sek = total.
- exchange_rate is SEK per 1 unit of the invoice currency and must satisfy 0 < rate < 100000, the same bounds the supplier_invoices CHECK enforces. Out-of-range values return 400 VALIDATION_ERROR; passing an invoice total where a rate belongs is the usual cause.
- Project/cost-center tagging: pass default_dimensions ({"6":"P001"} = project, {"1":"KS01"} = kostnadsställe) for the whole invoice and/or items[].dimensions per line (per-line wins per key). The registration JE lines are tagged accordingly. When the company has the dimension registry enabled, unknown or archived codes are rejected with 400 DIMENSION_VALIDATION_FAILED — list valid codes via GET /dimensions.
Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes
Example request
{
"supplier_id": "a8f1…",
"supplier_invoice_number": "2026-1234",
"invoice_date": "2026-05-10",
"due_date": "2026-06-09",
"default_dimensions": {
"6": "P001"
},
"items": [
{
"description": "Office supplies",
"amount": 1000,
"account_number": "5410",
"vat_rate": 0.25
}
]
}
Example response
{
"data": {
"id": "0e9c…",
"supplier_id": "a8f1…",
"arrival_number": 42,
"supplier_invoice_number": "2026-1234",
"status": "registered",
"total": 1250,
"registration_journal_entry_id": "7b3a…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/supplier-invoices/:id/approve {#post-supplier-invoices-approve}
supplier-invoices.approve · scope suppliers:write
Approve a registered or overdue supplier invoice.
Attests a supplier invoice that has not been approved yet (status registered or overdue). The resulting status is approved, or overdue when the invoice is still past its due date. No journal entry is posted here: the registration JE was already booked at :create under accrual, or is deferred to :mark-paid under cash. Idempotent. Dry-runnable.
Use when: A registered SI has been reviewed and you want to mark it ready for payment. Many AP workflows gate :mark-paid behind an explicit approval step.
Don't use for: Posting a journal entry (already done at :create under accrual). Paying the SI (use :mark-paid). Re-approving an already-approved SI (returns 400 SI_APPROVE_NOT_REGISTERED).
Pitfalls
- Idempotency-Key is mandatory.
- Returns 400 SI_APPROVE_NOT_REGISTERED when the invoice is already approved (approved_at set) or sits in a settled status. Use the detail endpoint to inspect status first if unsure.
- A still-past-due invoice comes back with status "overdue", not "approved": approved_at is the attest marker, the status is derived from the due date.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: yes
Example response
{
"data": {
"id": "0e9c…",
"status": "approved",
"arrival_number": 42,
"supplier_invoice_number": "2026-1234"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/supplier-invoices/:id/credit {#post-supplier-invoices-credit}
supplier-invoices.credit · scope suppliers:write
Issue a credit note for a supplier invoice.
Creates a kreditfaktura that reverses the original supplier invoice. Under accrual the reversing JE is posted atomically (Debit 2440 / Credit expense + Credit 2641). The original status flips to credited. Strict-mode: any failure rolls back the credit-note row. Idempotent. Dry-runnable.
Use when: You need to nullify a registered, approved, partially_paid, or paid supplier invoice: for a returned shipment, an over-invoice, or a vendor dispute resolution. Use dry-run to confirm the totals first.
Don't use for: Editing line items on an unchanged invoice (use PATCH on registered SIs). Crediting an already-credited SI (returns 409 SI_CREDIT_ALREADY_CREDITED). Reversing a v1-issued credit (no v1 endpoint today: use the dashboard).
Pitfalls
- Idempotency-Key is mandatory.
- Today's date is used as the credit-note invoice_date. It must fall in an open fiscal period: locked period returns 400 SI_CREDIT_PERIOD_LOCKED.
- Cash basis (kontantmetoden): no reversing JE is posted: recognition is deferred until a refund transaction is booked. The credit-note row is still created so the AP audit trail stays consistent.
- The original SI is flipped to
creditedregardless of how much of it was already paid; reconcile the bank refund via the transactions endpoints.
Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes
Example response
{
"data": {
"credit_note_id": "4d2a…",
"original_id": "0e9c…",
"arrival_number": 43,
"supplier_invoice_number": "KREDIT-2026-1234",
"registration_journal_entry_id": "9c2f…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/supplier-invoices/:id/mark-paid {#post-supplier-invoices-mark-paid}
supplier-invoices.mark-paid · scope suppliers:write
Record a payment against a supplier invoice.
Books the payment journal entry (Debit 2440 / Credit 1930 under accrual; or Debit expense + Debit 2641 / Credit 1930 under cash) and flips the SI status to paid (full settlement) or partially_paid. Strict-mode: a JE failure aborts before any SI mutation. Idempotent. Dry-runnable.
Use when: You paid a registered or approved leverantörsfaktura through a channel other than the synced bank flow. For bank-matched payments use POST /transactions/{id}/match-supplier-invoice instead: that path also reconciles the bank line.
Don't use for: Refunding a payment (the public API does not expose unmark-paid; credit the SI instead). Paying a credited or already-paid SI (returns 409 SI_PAID_ALREADY).
Pitfalls
- Idempotency-Key is mandatory.
- payment_date must fall in an open fiscal period: locked period returns 400 PERIOD_LOCKED.
- exchange_rate_difference (SEK delta vs the booked rate at registration) is required for foreign-currency SIs to book the FX gain/loss to 3960 / 7960. Omitting it on a non-SEK SI under accrual mis-books FX.
- Strict-mode: a JE creation failure ABORTS before the status flip. There is no partial-state recovery banner: retry the call.
- Cash basis (kontantmetoden) recognizes the expense + ingående moms HERE, not at :create.
Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: yes
Example request
{
"payment_date": "2026-05-13"
}
Example response
{
"data": {
"id": "0e9c…",
"status": "paid",
"total": 1250,
"paid_amount": 1250,
"remaining_amount": 0,
"paid_at": "2026-05-13",
"payment_journal_entry_id": "7b3a…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
PATCH /api/v1/companies/:companyId/supplier-invoices/:id {#patch-supplier-invoices-update}
supplier-invoices.update · scope suppliers:write
Update a registered supplier invoice.
Patches a supplier invoice with the supplied fields. Only allowed on registered status: once approved, paid, or credited, the record is effectively immutable from the API's perspective. Idempotent (mandatory Idempotency-Key). Dry-runnable.
Use when: You need to adjust due_date, or attach a payment reference / notes to a registered SI before approval. Use dry-run to confirm the merged state first.
Don't use for: Editing line items (immutable: credit the SI and register a new one). Changing status (use action verbs). Approved/paid/credited SIs (returns 400 SI_NOT_DRAFT). invoice_date / supplier_invoice_number on an SI that already has a registration verifikat (returns 400 SI_EDIT_VERIFIKAT_LOCKED).
Pitfalls
- Returns 400 SI_NOT_DRAFT when current status !== "registered".
- invoice_date and supplier_invoice_number are on the posted registration verifikat (entry_date and description). Once registration_journal_entry_id is set, patching them returns 400 SI_EDIT_VERIFIKAT_LOCKED: correct the entry via a rättelse (gnubok_correct_entry) or credit the SI and re-register. Resending the unchanged value is accepted.
- Patching a field never re-posts the registration JE.
Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes
Example request
{
"payment_reference": "OCR-1234567890"
}
Example response
{
"data": {
"id": "0e9c…",
"payment_reference": "OCR-1234567890"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}