Invoices
Outbound invoicing: draft, send, mark paid, credit, PDF download. Mixed-rate VAT supported.
Endpoints
GET/api/v1/companies/:companyId/invoices: List invoices for a company.GET/api/v1/companies/:companyId/invoices/:id: Retrieve a single invoice by id.GET/api/v1/companies/:companyId/invoices/:id/pdf: Download the rendered invoice PDF.POST/api/v1/companies/:companyId/invoices: Create a draft invoice, proforma, or delivery note.POST/api/v1/companies/:companyId/invoices/:id/credit: Issue a credit note (kreditfaktura) against an invoice.POST/api/v1/companies/:companyId/invoices/:id/mark-paid: Record a payment against an invoice.POST/api/v1/companies/:companyId/invoices/:id/mark-sent: Transition a draft invoice to sent (without emailing).POST/api/v1/companies/:companyId/invoices/:id/send: Send a draft invoice to the customer by email.POST/api/v1/companies/:companyId/invoices/bulk-create: Create up to 50 draft invoices in one call (partial-success).PATCH/api/v1/companies/:companyId/invoices/:id: Update a draft invoice (metadata fields only).
GET /api/v1/companies/:companyId/invoices
invoices.list · scope invoices:read
List invoices for a company.
Returns invoices in most-recent-first order. Includes the customer name inline; pass ?expand=customer for the full customer record, ?expand=items for line items.
Use when: You need to enumerate invoices for a company: for AR reporting, payment matching, or building an invoice dashboard.
Don't use for: Fetching a single invoice you already know the id of: use GET /api/v1/companies/{companyId}/invoices/{id}. Supplier invoices are a different resource (supplier-invoices).
Pitfalls
- Draft invoices have invoice_number=null until they are sent.
- remaining_amount is the unpaid portion (total − paid_amount); use status=paid or remaining_amount=0 to filter for closed invoices.
- Credit notes appear with status=credited and a credited_invoice_id field on the detail endpoint.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Example response
{
"data": [
{
"id": "0e9c…",
"invoice_number": "2026-0042",
"customer_id": "a8f1…",
"customer_name": "Acme AB",
"invoice_date": "2026-05-01",
"due_date": "2026-05-31",
"status": "sent",
"document_type": "invoice",
"currency": "SEK",
"subtotal": 10000,
"vat_amount": 2500,
"total": 12500,
"remaining_amount": 12500,
"paid_at": null,
"created_at": "2026-05-01T09:14:33Z"
}
],
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12",
"next_cursor": null
}
}
GET /api/v1/companies/:companyId/invoices/:id
invoices.get · scope invoices:read
Retrieve a single invoice by id.
Returns the full invoice record with the customer embedded. Pass ?expand=items for line items, ?expand=payments for payment history, or ?expand=items,payments for both.
Use when: You have an invoice id (from a webhook, the list endpoint, or a customer transaction) and need the full record including amounts, dates, status, and the customer details.
Don't use for: Listing invoices (use GET /api/v1/companies/{companyId}/invoices). Bookkeeping verifikationer tied to the invoice (use the journal-entries endpoints in a later phase).
Pitfalls
- Returns 404 if the invoice does not belong to the company in the URL: does not leak existence across companies.
- paid_at and remaining_amount can lag behind the latest payment by a few seconds during high-volume reconciliation.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Example response
{
"data": {
"id": "0e9c…",
"invoice_number": "2026-0042",
"customer_id": "a8f1…",
"customer": {
"id": "a8f1…",
"name": "Acme AB"
},
"invoice_date": "2026-05-01",
"due_date": "2026-05-31",
"status": "sent",
"total": 12500,
"remaining_amount": 12500,
"paid_at": null,
"created_at": "2026-05-01T09:14:33Z"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
GET /api/v1/companies/:companyId/invoices/:id/pdf
invoices.pdf · scope invoices:read
Download the rendered invoice PDF.
Returns the invoice as application/pdf. The filename in Content-Disposition reflects the document type: faktura-<number>.pdf for sent invoices, kreditfaktura-<number>.pdf for credit notes, utkast-<id-slice>.pdf for drafts. This endpoint is byte-equivalent to the dashboard download.
Use when: You need to fetch an invoice PDF for archival, forwarding to a customer outside the Accounted send flow, or attaching to an external workflow.
Don't use for: Sending the invoice to the customer: use POST /invoices/{id}/send, which renders the PDF, emails it, and archives it as a verifikationsunderlag in one atomic step.
Pitfalls
- Drafts (no invoice_number yet) render with an "utkast" filename. The PDF carries no F-series number: do not treat it as a finalized invoice.
- PDF rendering can take several hundred milliseconds for invoices with many line items. Cache on the client if requesting repeatedly.
- Credit notes embed the original invoice's löpnummer per ML 17 kap 22-23§: if the original was hard-deleted (not possible via Accounted but theoretically via a manual DB edit), the reference is omitted.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Example response
{
"_note": "Returns application/pdf binary stream."
}
POST /api/v1/companies/:companyId/invoices
invoices.create · scope invoices:write
Create a draft invoice, proforma, or delivery note.
Creates an invoice in draft status. The F-series invoice_number is allocated atomically on the first send action (PR-B-2b). Per-item VAT rates are validated against the customer's allowed rates (mixed-rate invoices supported). Non-SEK invoices are converted to SEK at the Riksbanken exchange rate fetched at create time. Supports ROT/RUT deduction lines (items[].deduction_type = "rot"|"rut" with invoice-level deduction_personnummer + deduction_housing_designation, or deduction_apartment_number + deduction_brf_org_number for bostadsrätt), article linkage (items[].article_id + optional revenue_account override from the artikelregister), and project/cost-centre tagging (default_dimensions / items[].dimensions). Idempotent (mandatory Idempotency-Key). Dry-runnable: the preview returns the validated would-be invoice + items with computed totals; no journal entry is involved at draft stage (posting happens on :send). Set is_self_billed=true (with external_invoice_number + received_date) to instead register a received self-billing invoice (mottagen självfaktura, ML 17 kap 15§): a sale booked immediately with the counterparty's number, not a draft.
Use when: You need to issue a new invoice, proforma, or delivery note. Use dry-run first to confirm VAT calculations and currency conversion before committing.
Don't use for: Updating an existing invoice (PATCH instead, drafts only). Issuing a credit note (use POST /:id:credit in PR-B-2b). Posting a previously-created draft to the journal (use POST /:id:send in PR-B-2b).
Pitfalls
- Idempotency-Key is mandatory; calls without it return 400.
- For mixed-rate invoices, set vat_rate per item explicitly. Items where vat_rate is omitted use the customer's default rate from getVatRules().
- Non-SEK currencies require an active Riksbanken exchange-rate fetch. Failure is non-fatal: the invoice is created with null SEK fields and the agent can recompute later.
- invoice_number is null on creation. The number is allocated atomically when the invoice transitions out of draft. Counting on a specific number at create time is a bug.
- document_type='delivery_note' produces no VAT and a different number sequence (D-series). Most use cases want the default document_type='invoice'.
- is_self_billed=true registers a self-billing invoice your CUSTOMER issued on your behalf (a sale for you). It is booked immediately (not a draft, no F-number), so external_invoice_number and received_date are required and it is NOT dry-run-free of side effects on the live call. Do NOT set it for a normal invoice you issue yourself.
- 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). Tags are stored on the draft and applied to the journal entry lines when the invoice is sent. When the company has the dimension registry enabled, unknown or archived codes are rejected at :send with 400 DIMENSION_VALIDATION_FAILED — list valid codes via GET /dimensions.
- ROT/RUT: set items[].deduction_type ("rot"|"rut") on labor lines plus labor_hours and work_type (Skatteverket arbetstypskod). The invoice must carry deduction_personnummer AND housing info: deduction_housing_designation (fastighetsbeteckning) for småhus, or deduction_apartment_number + deduction_brf_org_number for bostadsrätt. deduction_amount is computed server-side and cannot be set by the caller; the response exposes deduction_total and remaining_amount = total - deduction_total (Skatteverket pays the rest via 1513). Validation failures return 400 INVOICE_CREATE_ROT_RUT_VALIDATION.
- Articles: pass items[].article_id (from the artikelregister, GET /articles) to link a line to a catalog article; price/description are still taken from the request body (the API never auto-fills from the article: send the values you want on the invoice). items[].revenue_account optionally overrides the BAS class-3 account and is validated against the chart of accounts.
Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes
Example request
{
"customer_id": "a8f1…",
"invoice_date": "2026-05-12",
"due_date": "2026-06-11",
"currency": "SEK",
"items": [
{
"description": "Konsultation",
"quantity": 8,
"unit": "tim",
"unit_price": 1250
}
]
}
Example response
{
"data": {
"id": "0e9c…",
"invoice_number": null,
"customer_id": "a8f1…",
"invoice_date": "2026-05-12",
"due_date": "2026-06-11",
"status": "draft",
"currency": "SEK",
"subtotal": 10000,
"vat_amount": 2500,
"total": 12500,
"remaining_amount": 12500
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/invoices/:id/credit
invoices.credit · scope invoices:write
Issue a credit note (kreditfaktura) against an invoice.
Creates a credit note referencing the original invoice. The credit note carries reversed-sign amounts (matching the original line for line) and gets invoice_number=KR-<original>. The original invoice transitions to status=credited. Under faktureringsmetoden, posts a reversing journal entry (Credit AR 1510 / Debit revenue + Debit output VAT). Under kontantmetoden the credit note still creates the row but defers the reversal entry until refund. Idempotent and dry-runnable. Emits invoice.credited.
Use when: You need to legally cancel an issued invoice (ML 17 kap 22-23§). The original invoice cannot be edited once issued: credit it and reissue corrected.
Don't use for: Cancelling a draft (DELETE the draft instead). Refunding a partial payment without invalidating the whole invoice (book the refund manually via the journal-entries API in a future PR).
Pitfalls
- Idempotency-Key is mandatory. Retried credits with the same key replay the cached response: no duplicate credit note is created.
- The original invoice must be in sent / paid / overdue status. Drafts, cancelled invoices, and already-credited invoices are rejected with specific error codes.
- Credit-note items mirror the original's lines with negated values. To credit only part of an invoice (line-level), credit the full invoice first then reissue with the corrected lines.
- Under kontantmetoden no journal entry is created here: refund booking is deferred. A
JOURNAL_ENTRY_NOT_POSTEDwarning is NOT emitted in this case (the deferral is correct, not a failure).
Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes
Example request
{
"reason": "Felaktig kund"
}
Example response
{
"data": {
"id": "ccccccc-c…",
"invoice_number": "KR-2026-0042",
"credited_invoice_id": "0e9c…",
"status": "sent",
"total": -12500,
"journal_entry_id": "8b4b…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/invoices/:id/mark-paid
invoices.mark-paid · scope invoices:write
Record a payment against an invoice.
Marks a sent / overdue invoice as paid (or partially_paid). Books the payment via Debit 1930 / Credit 1510 under faktureringsmetoden, or Debit 1930 / Credit revenue + Credit output VAT under kontantmetoden. Optional body supports partial payments via custom balanced journal lines and exchange-rate adjustments for foreign-currency invoices. Idempotent and dry-runnable. Emits invoice.paid.
Use when: A customer paid an invoice via a channel other than the synced bank account (cash, manual transfer, separate processor). Use dry-run to confirm the booking before committing.
Don't use for: Reverting a payment: the public API does not expose unmark-paid. Issue a credit note via POST /:id/credit to cancel the underlying invoice instead. Bank-matched payments: those flow through the transactions endpoints.
Pitfalls
- Idempotency-Key is mandatory. Retried marks with the same key replay the cached response.
- Custom
linesmust balance (sum of debits = sum of credits, both > 0). Otherwise returns 400 INVOICE_PAID_LINES_UNBALANCED. - For foreign-currency invoices, supply
exchange_rate_difference(SEK delta vs the invoice's booked rate) to book the FX adjustment correctly. Omitting it on a non-SEK invoice will mis-book the FX gain/loss. - Cash basis (kontantmetoden) recognizes revenue HERE, not at :mark-sent. The dashboard tracks this via company_settings.accounting_method.
- Duplicate-payment guard: if an unlinked inbound bank transaction looks like this payment, returns 409 INVOICE_PAID_LIKELY_DUPLICATE with candidate transactions. Retry with
force: trueto bypass, but the retry MUST use a fresh Idempotency-Key (the original is body-hash bound; reusing it returns 400 IDEMPOTENCY_KEY_REUSE). The guard is also evaluated under dry-run, so a successful dry-run does not guarantee a successful commit.
Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: yes
Example request
{
"payment_date": "2026-05-12"
}
Example response
{
"data": {
"id": "0e9c…",
"invoice_number": "2026-0042",
"status": "paid",
"total": 12500,
"paid_amount": 12500,
"remaining_amount": 0,
"paid_at": "2026-05-12",
"journal_entry_id": "7b3a…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/invoices/:id/mark-sent
invoices.mark-sent · scope invoices:write
Transition a draft invoice to sent (without emailing).
Marks a draft invoice as sent: for invoices delivered outside Accounted (Peppol, postal, manual email). Allocates the F-series invoice_number atomically (ML 17 kap 24§ p.2). On accounting_method=accrual, also posts the invoice journal entry (Debit AR 1510 / Credit revenue + output VAT). Emits invoice.sent. Idempotent and dry-runnable. The companion :send action (PR-B-2b-3) adds PDF rendering and email delivery on top of this same flow.
Use when: You delivered the invoice through a channel other than Accounted's email (Peppol, postal, your own SMTP) and need to record it as sent so the F-series number is allocated and the journal entry is posted.
Don't use for: Sending the invoice via Accounted email: use :send (PR-B-2b-3) for that. Marking an already-sent invoice as paid: use :mark-paid (PR-B-2b-2).
Pitfalls
- Only invoices in
status=draftcan be marked sent. Other states return 409 INVOICE_UPDATE_NOT_DRAFT (re-used; the action is structurally an update). - Allocation is atomic. If a concurrent transition beats the agent's request to the same draft, the runner-up gets 409 INVOICE_UPDATE_NOT_DRAFT and no number is consumed.
- Delivery notes (document_type=delivery_note) don't transition to sent: they were never drafts in the f-series sense. This endpoint will reject them with 400 VALIDATION_ERROR.
- Idempotency-Key is mandatory. A retried mark-sent with the same key replays the cached response.
Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: yes
Example response
{
"data": {
"id": "0e9c…",
"invoice_number": "2026-0042",
"status": "sent",
"total": 12500,
"journal_entry_id": "7b3a…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/invoices/:id/send
invoices.send · scope invoices:write
Send a draft invoice to the customer by email.
The full send pipeline: preflight PDF render → allocate F-series number atomically → final PDF render → email via Resend (PDF attachment, copy to company) → flip status to sent → post journal entry (accrual + real invoice) → archive PDF as underlag → emit invoice.sent. Email failure is a hard 502 before state changes; post-email failures surface as warnings but the invoice IS marked sent.
Use when: You want Accounted to deliver the invoice to the customer via email. For invoices delivered through another channel (Peppol, postal, own SMTP) use :mark-sent instead.
Don't use for: Re-sending an already-sent invoice (returns 409 INVOICE_UPDATE_NOT_DRAFT). Sending a delivery note (no F-series lifecycle). Sending a credit note (use the :credit endpoint to issue the kreditfaktura; subsequent re-send of the credit note via :mark-sent is the supported path).
Pitfalls
- Idempotency-Key is mandatory.
- Email service must be configured: without RESEND_API_KEY + RESEND_FROM_EMAIL the endpoint returns 503 INVOICE_SEND_EMAIL_NOT_CONFIGURED.
- Customer must have an email address. 400 INVOICE_SEND_NO_CUSTOMER_EMAIL otherwise.
- A cancelled invoice is rejected (400 INVOICE_SEND_CANCELLED): its F-series number is preserved for compliance but the document is not a valid faktura.
- Email failure before the status flip leaves the F-series number consumed but the invoice in
draftstatus. Same orphan window as :mark-sent (architecturally tracked, matches internal route). - After the email succeeds, journal-entry/archive/event failures become warnings on the response; the invoice IS marked sent regardless.
Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes
Example response
{
"data": {
"id": "0e9c…",
"invoice_number": "2026-0042",
"status": "sent",
"total": 12500,
"message_id": "re_abc123",
"sent_to": "finance@acme.test",
"cc": "billing@gnubok-user.test",
"journal_entry_id": "7b3a…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/invoices/bulk-create
invoices.bulk-create · scope invoices:write
Create up to 50 draft invoices in one call (partial-success).
Bulk-creation endpoint. Each invoice in the request array is validated and inserted independently. By default, individual failures do not roll back successes: the response carries a per-item results array with ok/error markers and a summary. Idempotent (the whole batch is keyed by the single Idempotency-Key). Dry-runnable.
Use when: You're importing a batch of invoices from another system, or producing many invoices programmatically (e.g. monthly subscription billing). Use dry-run first to validate the whole batch before committing.
Don't use for: Sending the same invoice to multiple customers: POST /invoices once per customer. Long-running imports of > 50 invoices: split into pages. Transactional all-or-nothing imports: not yet supported (passing all_or_nothing: true returns 501 NOT_IMPLEMENTED; the flag is reserved for a future RPC).
Pitfalls
- Idempotency-Key is mandatory and covers the WHOLE batch. A retried bulk-create returns the cached full response: it does not retry only the failed items.
- Passing all_or_nothing: true returns 501 NOT_IMPLEMENTED. Today only partial-success batches exist; omit the flag (or pass false).
- Each per-item invoice still goes through the same VAT-rule validation as POST /invoices. A mismatched per-item vat_rate produces a per-item failure, not a whole-batch failure.
- Currency conversion is best-effort PER ITEM. A failed Riksbanken fetch leaves that item's SEK columns null but does NOT fail the item.
Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes
Example request
{
"invoices": [
{
"customer_id": "a8f1…",
"invoice_date": "2026-05-12",
"due_date": "2026-06-11",
"currency": "SEK",
"items": [
{
"description": "A",
"quantity": 1,
"unit": "st",
"unit_price": 1000
}
]
}
]
}
Example response
{
"data": {
"results": [
{
"ok": true,
"request_index": 0,
"data": {
"id": "0e9c…",
"invoice_number": null,
"status": "draft",
"total": 1250
}
}
],
"summary": {
"total": 1,
"succeeded": 1,
"failed": 0
}
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
PATCH /api/v1/companies/:companyId/invoices/:id
invoices.update · scope invoices:write
Update a draft invoice (metadata fields only).
Partial update for invoices in draft status. Allowed fields: invoice_date, due_date, delivery_date, your_reference, our_reference, notes, default_dimensions (project/cost-centre tags, e.g. {"6":"P001"}; replaces the whole bag). customer_id, currency, document_type, items, and computed totals are immutable: replace those by deleting the draft and recreating it. Returns 409 INVOICE_UPDATE_NOT_DRAFT if the invoice is no longer in draft status. Idempotent and dry-runnable.
Use when: You need to correct a typo, push the due date, or update a customer reference on a draft you have not sent yet. The invoice number stays null until the first :send action.
Don't use for: Updating a sent / paid / credited invoice (those are immutable per ML 17 kap; issue a credit note via POST /:id:credit in PR-B-2b). Changing items, currency, or customer: drafts are cheap to delete and recreate.
Pitfalls
- Idempotency-Key is mandatory.
- A 409 INVOICE_UPDATE_NOT_DRAFT means the invoice has been sent / paid / credited / cancelled. The error code name is shared with the DELETE handler.
- Items are immutable here: to change line items, delete the draft and POST a fresh one.
- default_dimensions replaces the entire bag (no per-key merge): read the current value first if you want to add a tag. Send {} to clear all tags. Codes are validated against the dimension registry at :send, not at PATCH time.
Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes
Example request
{
"due_date": "2026-07-15",
"notes": "Förlängd förfallotid"
}
Example response
{
"data": {
"id": "0e9c…",
"status": "draft",
"due_date": "2026-07-15",
"notes": "Förlängd förfallotid"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}