gnubok

Supplier invoices

AP lifecycle: register, approve, mark paid, credit. With ROT/RUT and reverse-charge support.

Endpoints


GET /api/v1/companies/:companyId/supplier-invoices {#get-supplier-invoices-list}

supplier-invoices.list · scope suppliers:read

List supplier invoices for a company.

Returns supplier invoices in most-recent-first order. 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.

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.

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",
  "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 supplier invoice.

Flips a supplier invoice from registered to approved. 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 current status !== "registered". Use the detail endpoint to inspect status first if unsure.

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 credited regardless 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 fix a typo in supplier_invoice_number, adjust dates, 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).

Pitfalls

  • Returns 400 SI_NOT_DRAFT when current status !== "registered".
  • invoice_date / due_date changes do not re-post the registration JE; if the entry date needs to change, credit the SI and re-register.

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"
  }
}