{"openapi":"3.0.0","info":{"title":"Numina Public API","version":"1.0.0","description":"Public API for the Numina accounting platform. Read accounts, bank lines and the ledger; create, change and book journal entries and invoices; upload documents.\n\n## Authentication\n\nSend `Authorization: Bearer <token>`, with an API key (`numi_...`, created under Settings → API & apps) or an OAuth access token. Keys and tokens belong to one company and are limited by their scopes. The write scopes do not include read access. GET /v1/me shows what your credentials can do.\n\n## Amounts and dates\n\nDebit is positive and credit is negative. A journal entry's lines must sum to exactly zero. Dates are `YYYY-MM-DD`; timestamps are ISO 8601 in UTC.\n\n## Book a journal entry\n\n1. `POST /v1/journal-entries` with a balanced entry. It is a draft and does not affect balances.\n2. `POST /v1/journal-entries/{id}/attachments` with its document. You can also use `POST /v1/attachments` and then `PUT /v1/journal-entries/{id}` with `attachment_id`.\n3. `POST /v1/journal-entries/{id}/book`, which needs `journal:book`. Each call is your own approval: Numina does not review the entry, and booking is final.\n\n## Pagination\n\nList routes that take `limit` and `offset` return `has_more` and `next_offset`. `limit` defaults to 100 with a maximum of 500; on `/v1/ledger-entries` it defaults to and allows up to 1000. Out-of-range values return 400, except on `/v1/invoices` and `/v1/contacts`, which clamp them. `/v1/accounts` and `/v1/vat-codes` return everything.\n\n## Errors\n\nErrors are JSON: `{ \"error\": code, \"error_description\": text, \"details\"?: [{ \"field\", \"message\" }] }`. Branch on `error`, because the text can change and can be in Danish. A 404 `not_found` on any route can also mean the OAuth user no longer belongs to the company. Two failures are plain text instead: a body that is not valid JSON gets a 400, and a JSON body over 4 MB gets a 500.\n\n## Rate limits\n\nCounted per API key or OAuth connection, never per IP address: 600 requests per minute across the API. Within that, uploads (`POST /v1/journal-entries/{id}/attachments`, `POST /v1/attachments` and `POST /v1/attachments/{id}/files`) are limited to 120 per minute and invoice PDFs to 60. Responses carry `RateLimit-*` headers, and going over returns 429. Write to support@numina.app if you need more.\n\n## Retries\n\n`Idempotency-Key` works on `POST /v1/journal-entries`, `POST /v1/invoices` and `POST /v1/invoices/{id}/send`. A failed request frees its key. Both book routes are safe to repeat. Repeating an upload to an entry or to an empty attachment returns 409, and repeating `POST /v1/attachments` returns the existing document with 200. `POST /v1/contacts` is not protected, so a retry can create a duplicate. `POST /v1/products` refuses a repeat only if the product number is already in use.\n\nThere are no webhooks yet, so poll for changes.","contact":{"name":"Numina API Support","email":"support@numina.app"}},"tags":[{"name":"Journal Entries","description":"Create, change and book entries. Create a draft, upload its document, then book it."},{"name":"Attachments","description":"Documents (bilag). File contents cannot be downloaded through the API."},{"name":"Invoices","description":"Create, book and send invoices. Credit notes and offers are created in Numina and can be read here."},{"name":"Contacts","description":"Customers and suppliers."},{"name":"Products","description":"The product catalogue used on invoice lines."},{"name":"Accounts","description":"Chart of accounts and VAT codes."},{"name":"Balances","description":"Trial balance and movement per period. Booked entries only."},{"name":"Ledger entries","description":"Posted entries, as the reports read them."},{"name":"Bank","description":"Bank accounts, and bank lines with their reconciliation state. Read-only."},{"name":"Company","description":"The connected company, its accounting years, and a check of your credentials."}],"servers":[{"url":"https://api.numina.app","description":"Production"}],"components":{"securitySchemes":{"oauth2":{"type":"oauth2","description":"Authorization code flow with PKCE (S256). A company admin creates an app under Settings → API & apps. Other companies can connect it once it is made available to them, approved by one of their admins, for up to 10 companies; past that Numina reviews the app, on request to support@numina.app. Clients may register themselves at /oauth/register, but those cannot hold a booking scope. Access tokens last an hour; refresh tokens last 30 days and rotate on use. Endpoints are also published at https://api.numina.app/.well-known/oauth-authorization-server.","flows":{"authorizationCode":{"authorizationUrl":"https://api.numina.app/oauth/authorize","tokenUrl":"https://api.numina.app/oauth/token","scopes":{"setup:read":"Read the company, accounting years, chart of accounts and VAT codes","reports:read":"Read balances and the trial balance","journal:read":"Read draft and booked journal entries","journal:write":"Create, change and delete draft journal entries. Nothing is booked","journal:book":"Book journal entries","invoices:read":"Read invoices","invoices:write":"Create invoice drafts","invoices:book":"Book and send invoices","contacts:read":"Read customers and suppliers","contacts:write":"Create and update customers and suppliers","products:read":"Read the product catalogue","products:write":"Create, update and delete products","documents:read":"Read uploaded documents","documents:write":"Upload documents","bank:read":"Read bank accounts and bank lines","read:ledger":"Legacy: every read scope. Prefer the resource scopes for new integrations","write:draft":"Legacy: every write scope. Prefer the resource scopes for new integrations","write:ledger":"Legacy: every write and book scope, without read access. Prefer the resource scopes for new integrations"}}}},"apiKey":{"type":"http","scheme":"bearer","bearerFormat":"API Key","description":"API key in the `Authorization: Bearer numi_...` header. Create keys in Numina under Settings → API & apps. A key belongs to one company and carries the scopes chosen when it was created. Each endpoint's OAuth scopes apply to keys in the same way."}},"schemas":{"ErrorDetail":{"type":"object","properties":{"field":{"type":"string","description":"Field that caused the error, as a dotted path","example":"lines.0.amount"},"message":{"type":"string","description":"Specific error message for this field","example":"Invalid input: expected number, received string"}},"required":["field","message"]},"ErrorResponse":{"type":"object","properties":{"error":{"type":"string","description":"Machine-readable error code","example":"validation_error","enum":["invalid_request","validation_error","unauthorized","forbidden","not_found","conflict","attachment_exists","attachment_required","booking_not_available","payment_details_required","insufficient_scope","invalid_token","company_archived","idempotency_key_in_use","idempotency_key_reused","rate_limit_exceeded","internal_error","missing_file","file_too_large","invalid_file","upload_error","attachment_not_empty","accounting_year_not_found","range_crosses_accounting_year","server_error"]},"error_description":{"type":"string","description":"Human-readable error description","example":"Journal entries do not balance"},"details":{"type":"array","items":{"$ref":"#/components/schemas/ErrorDetail"},"description":"Detailed validation errors (when applicable)"},"scope":{"type":"string","description":"On `insufficient_scope`: the scopes the route asks for, separated by spaces","example":"write:ledger"}},"required":["error","error_description"]},"GetAccountsResponse":{"type":"object","properties":{"accounts":{"type":"array","items":{"$ref":"#/components/schemas/Account"}}},"required":["accounts"]},"Account":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"account_number":{"type":"integer","nullable":true,"description":"Account number in chart of accounts","example":3000},"name":{"type":"string","description":"Account name","example":"Revenue"},"description":{"type":"string","description":"Account description","example":"Revenue from sales"},"account_type":{"type":"string","description":"Account type: \"profitAndLoss\" (P&L account), \"status\" (balance-sheet account), \"heading\" (section heading, never posted to) or \"totalFrom\" (subtotal of an account range). Empty when not set.","example":"profitAndLoss"},"default_vat_code":{"type":"string","nullable":true,"description":"Default VAT code for this account","example":"U25"}},"required":["id","account_number","name","description","account_type","default_vat_code"]},"ListContactsResponse":{"type":"object","properties":{"contacts":{"type":"array","items":{"$ref":"#/components/schemas/Contact"}},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true}},"required":["contacts","has_more","next_offset"]},"Contact":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Contact ID","example":"550e8400-e29b-41d4-a716-446655440000"},"name":{"type":"string","description":"Contact name","example":"Acme ApS"},"cvr":{"type":"integer","nullable":true,"description":"Danish CVR number","example":12345678},"vat_number":{"type":"string","nullable":true,"description":"VAT number (international)","example":"DK12345678"},"country_code":{"type":"string","nullable":true,"description":"ISO country code","example":"DK"},"address_street":{"type":"string","nullable":true,"description":"Street address","example":"Vestergade 1"},"address_zip":{"type":"string","nullable":true,"description":"Postal code","example":"1456"},"address_city":{"type":"string","nullable":true,"description":"City","example":"Copenhagen"},"country":{"type":"string","nullable":true,"description":"Country name. Sent as `address_country`.","example":"Denmark"},"email":{"type":"string","nullable":true,"description":"Contact email","example":"billing@acme.dk"},"phone":{"type":"string","nullable":true,"description":"Contact phone number","example":"+45 12 34 56 78"},"attn_person":{"type":"string","nullable":true,"description":"Attention person","example":"Jane Doe"},"e_invoice_number":{"type":"string","nullable":true,"description":"E-invoice identifier (CVR or GLN)","example":"12345678"},"e_invoice_scheme":{"type":"string","nullable":true,"description":"E-invoice scheme (DK:CVR or GLN)","example":"DK:CVR"},"archived":{"type":"boolean","description":"Archived contacts stay readable for their history but can't be invoiced","example":false}},"required":["id","name","cvr","vat_number","country_code","address_street","address_zip","address_city","country","email","phone","attn_person","e_invoice_number","e_invoice_scheme","archived"]},"CreateContactRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500,"description":"Contact name (required)","example":"Acme ApS"},"cvr":{"type":"integer","nullable":true,"description":"Danish CVR number","example":12345678},"vat_number":{"type":"string","nullable":true,"description":"VAT number (international)","example":"DK12345678"},"country_code":{"type":"string","nullable":true,"description":"ISO country code","example":"DK"},"address_street":{"type":"string","nullable":true,"description":"Street address","example":"Vestergade 1"},"address_zip":{"type":"string","nullable":true,"description":"Postal code","example":"1456"},"address_city":{"type":"string","nullable":true,"description":"City","example":"Copenhagen"},"address_country":{"type":"string","nullable":true,"description":"Country name","example":"Denmark"},"email":{"type":"string","nullable":true,"description":"Contact email","example":"billing@acme.dk"},"phone":{"type":"string","nullable":true,"description":"Contact phone number","example":"+45 12 34 56 78"},"attn_person":{"type":"string","nullable":true,"description":"Attention person","example":"Jane Doe"},"e_invoice_number":{"type":"string","nullable":true,"description":"E-invoice identifier (CVR or GLN)","example":"12345678"},"e_invoice_scheme":{"type":"string","nullable":true,"description":"E-invoice scheme (DK:CVR or GLN)","example":"DK:CVR"}},"required":["name"]},"UpdateContactRequest":{"type":"object","properties":{"name":{"type":"string","minLength":1,"maxLength":500,"description":"Contact name","example":"Acme ApS"},"cvr":{"type":"integer","nullable":true,"description":"Danish CVR number","example":12345678},"vat_number":{"type":"string","nullable":true,"description":"VAT number (international)","example":"DK12345678"},"country_code":{"type":"string","nullable":true,"description":"ISO country code","example":"DK"},"address_street":{"type":"string","nullable":true,"description":"Street address","example":"Vestergade 1"},"address_zip":{"type":"string","nullable":true,"description":"Postal code","example":"1456"},"address_city":{"type":"string","nullable":true,"description":"City","example":"Copenhagen"},"address_country":{"type":"string","nullable":true,"description":"Country name","example":"Denmark"},"email":{"type":"string","nullable":true,"description":"Contact email","example":"billing@acme.dk"},"phone":{"type":"string","nullable":true,"description":"Contact phone number","example":"+45 12 34 56 78"},"attn_person":{"type":"string","nullable":true,"description":"Attention person","example":"Jane Doe"},"e_invoice_number":{"type":"string","nullable":true,"description":"E-invoice identifier (CVR or GLN)","example":"12345678"},"e_invoice_scheme":{"type":"string","nullable":true,"description":"E-invoice scheme (DK:CVR or GLN)","example":"DK:CVR"}}},"ListProductsResponse":{"type":"object","properties":{"products":{"type":"array","items":{"$ref":"#/components/schemas/Product"}},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true}},"required":["products","has_more","next_offset"]},"Product":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Product ID","example":"550e8400-e29b-41d4-a716-446655440000"},"product_number":{"type":"string","nullable":true,"description":"Product number (varenummer), unique within the company","example":"1001"},"description":{"type":"string","description":"Product name, copied to the invoice line","example":"Consulting hour"},"quantity":{"type":"number","description":"Default quantity on the invoice line","example":1},"unit":{"type":"string","enum":["unit","hour","day","kilometer","week","month"],"description":"Unit: unit, hour, day, kilometer, week, or month","example":"hour"},"unit_price":{"type":"number","description":"Unit price (excl. VAT)","example":950},"sales_account_number":{"type":"integer","nullable":true,"description":"Account number of the sales revenue account","example":1000},"use_account_vat":{"type":"boolean","description":"True when the VAT comes from the sales account's default VAT code","example":true},"vat_code":{"type":"string","nullable":true,"description":"VAT code used when use_account_vat is false. Null then means no VAT.","example":null}},"required":["id","product_number","description","quantity","unit","unit_price","sales_account_number","use_account_vat","vat_code"]},"CreateProductRequest":{"type":"object","properties":{"product_number":{"type":"string","nullable":true,"maxLength":64,"description":"Product number (varenummer). Must be unique within the company; null or empty clears it.","example":"1001"},"description":{"type":"string","minLength":1,"maxLength":500,"description":"Product name (required)","example":"Consulting hour"},"quantity":{"type":"number","minimum":0,"exclusiveMinimum":true,"default":1,"description":"Default quantity (default 1)","example":1},"unit":{"type":"string","enum":["unit","hour","day","kilometer","week","month"],"default":"unit","description":"Unit (default unit)","example":"hour"},"unit_price":{"type":"number","description":"Unit price (excl. VAT)","example":950},"sales_account_number":{"type":"integer","description":"Account number of a sales account (from GET /v1/accounts)","example":1000},"use_account_vat":{"type":"boolean","description":"Use the sales account's default VAT code. Cannot be combined with vat_code.","example":true},"vat_code":{"type":"string","nullable":true,"description":"Sales VAT code (e.g. 'U25') from GET /v1/vat-codes, or null for no VAT. Setting it turns use_account_vat off.","example":"U25"}},"required":["description","unit_price","sales_account_number"]},"UpdateProductRequest":{"type":"object","properties":{"product_number":{"type":"string","nullable":true,"maxLength":64,"description":"Product number (varenummer). Must be unique within the company; null or empty clears it.","example":"1001"},"description":{"type":"string","minLength":1,"maxLength":500,"description":"Product name","example":"Consulting hour"},"quantity":{"type":"number","minimum":0,"exclusiveMinimum":true,"description":"Default quantity","example":1},"unit":{"type":"string","enum":["unit","hour","day","kilometer","week","month"],"description":"Unit","example":"hour"},"unit_price":{"type":"number","description":"Unit price (excl. VAT)","example":950},"sales_account_number":{"type":"integer","description":"Account number of a sales account (from GET /v1/accounts)","example":1000},"use_account_vat":{"type":"boolean","description":"Use the sales account's default VAT code. Cannot be combined with vat_code.","example":true},"vat_code":{"type":"string","nullable":true,"description":"Sales VAT code (e.g. 'U25') from GET /v1/vat-codes, or null for no VAT. Setting it turns use_account_vat off.","example":"U25"}}},"ListBankAccountsResponse":{"type":"object","properties":{"bank_accounts":{"type":"array","items":{"$ref":"#/components/schemas/BankAccount"}}},"required":["bank_accounts"]},"BankAccount":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Pass as `bank_account_id` on POST /v1/invoices. For a connected account it is also the `bank_account_id` on its bank lines."},"type":{"type":"string","enum":["connected","manual"],"description":"'connected': a bank account Numina reads bank lines from. 'manual': payment details the company typed in for its invoices, for a bank that is not connected. A company has at most one, and it has no bank lines.","example":"connected"},"bank_name":{"type":"string","nullable":true,"example":"Danske Bank"},"account_name":{"type":"string","nullable":true,"example":"Erhvervskonto"},"ledger_account_number":{"type":"number","nullable":true,"description":"The ledger account the bank account is booked on (from GET /v1/accounts). Null for a manual account and for a connected account that is not set up yet.","example":5820},"connection_status":{"type":"string","nullable":true,"enum":["configured","auth_expired","account_unavailable",null],"description":"'auth_expired': the company must sign in to the bank again before new bank lines arrive. 'account_unavailable': the bank no longer serves this account. Neither stops the account from being used on invoices. Null for a manual account.","example":"configured"},"is_archived":{"type":"boolean","description":"The company has archived the account. Its bank lines remain."},"payment_details":{"$ref":"#/components/schemas/PaymentDetails"},"usable_on_invoices":{"type":"boolean","description":"Whether `bank_account_id` on POST /v1/invoices accepts this account: it has payment details and is not archived."},"is_invoice_default":{"type":"boolean","description":"The account an invoice gets when `bank_account_id` is omitted: the only usable account, or with several, the one the company chose as its default in Numina. False on every account when there are several and no default."}},"required":["id","type","bank_name","account_name","ledger_account_number","connection_status","is_archived","payment_details","usable_on_invoices","is_invoice_default"]},"PaymentDetails":{"type":"object","nullable":true,"properties":{"bank_name":{"type":"string","nullable":true,"example":"Danske Bank"},"scheme":{"type":"string","nullable":true,"enum":["dk-registration-number","gb-sort-code","ach",null],"description":"What `clearing_code` and `account_number` are: a Danish registration number, a UK sort code or a US ACH routing number. Null when there is only an IBAN.","example":"dk-registration-number"},"clearing_code":{"type":"string","nullable":true,"description":"Registration number, sort code or ACH routing number","example":"3409"},"account_number":{"type":"string","nullable":true,"example":"0012345678"},"iban":{"type":"string","nullable":true,"example":"DK5030000012345678"},"swift_bic":{"type":"string","nullable":true,"example":"DABADKKK"}},"required":["bank_name","scheme","clearing_code","account_number","iban","swift_bic"],"description":"What an invoice paid into this account shows. Null when Numina has no account number or IBAN for it."},"ListInvoicesResponse":{"type":"object","properties":{"invoices":{"type":"array","items":{"$ref":"#/components/schemas/Invoice"}},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true}},"required":["invoices","has_more","next_offset"]},"Invoice":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Invoice ID","example":"550e8400-e29b-41d4-a716-446655440000"},"invoice_number":{"type":"integer","nullable":true,"description":"Invoice number (assigned when booked)","example":1001},"type":{"type":"string","enum":["invoice","creditNote","offer"],"description":"Invoice type. Create a credit note with POST /v1/invoices/{id}/credit-notes; offers are created in Numina.","example":"invoice"},"credited_invoice_id":{"type":"string","nullable":true,"format":"uuid","description":"On a credit note, the invoice it credits"},"status":{"type":"string","enum":["draft","sent","closed","paid","cancelled","accepted","declined"],"description":"`draft`: not booked. `sent`: booked, whether or not it was delivered (see `delivery_status`). `closed`: fully settled. `paid`: settled, only on invoices migrated from Billy. `cancelled`: a booked credit note, which does not mean it was voided; a credit note can also show `closed` when its balance is zero. An offer is `draft`, `sent`, `accepted` or `declined`. To find settled invoices, use `closed` and `paid`, or `outstanding_balance`.","example":"sent"},"issue_date":{"type":"string","description":"Issue date","example":"2026-01-15"},"due_date":{"type":"string","nullable":true,"description":"Payment due date","example":"2026-02-15"},"contact_id":{"type":"string","nullable":true,"format":"uuid","description":"Contact ID","example":"550e8400-e29b-41d4-a716-446655440001"},"contact_name":{"type":"string","nullable":true,"description":"Contact name (snapshot at invoice creation)","example":"Acme ApS"},"currency":{"type":"string","nullable":true,"description":"Currency code","example":"DKK"},"subtotal":{"type":"number","description":"Subtotal (excl. VAT)","example":10000},"vat":{"type":"number","description":"VAT amount","example":2500},"vat_rate":{"type":"number","nullable":true,"description":"VAT rate percentage; null when the invoice contains mixed VAT treatments","example":25},"total":{"type":"number","description":"Total amount (incl. VAT)","example":12500},"has_mixed_vat":{"type":"boolean"},"outstanding_balance":{"type":"number","nullable":true,"description":"Outstanding balance in DKK (not invoice currency; 0 when settled). Only populated for booked invoices.","example":12500},"outstanding_balance_currency":{"type":"string","enum":["DKK"]},"currency_rate":{"type":"number","description":"Invoice currency to DKK: amount × rate = DKK. On a draft, the rate you sent; once booked, the rate Numina used (see `currency_rate` on POST /v1/invoices)."},"delivery_status":{"type":"string","enum":["sent","failed","pending","not_requested"],"description":"Channel handoff state. 'sent' means provider acceptance, not confirmed delivery to the recipient."},"email_delivery_status":{"type":"string","nullable":true},"email_sent_at":{"type":"string","nullable":true},"e_invoice_sent_at":{"type":"string","nullable":true},"is_overdue":{"type":"boolean","description":"Whether the invoice is past its due date and still owed (see `payment_state`)","example":false},"payment_state":{"type":"string","nullable":true,"enum":["open","paid","covered_by_unmatched_payments","exchange_difference",null],"description":"Whether a booked invoice is owed. 'covered_by_unmatched_payments': the customer's balance is covered by payments booked without an invoice match, so the invoice is not owed although `outstanding_balance` stays positive; the customer's balance is the truth. 'exchange_difference': the remainder is a currency difference, not unpaid money. Null for drafts, offers, credit notes and cancelled invoices.","example":"open"},"unmatched_payments_note":{"type":"string","description":"Set only when payment_state is 'covered_by_unmatched_payments'."},"items":{"type":"array","items":{"type":"object","properties":{"unit":{"type":"string","description":"Unit: unit, hour, day, kilometer, week, or month"},"vat_account_id":{"type":"string","nullable":true},"vat_code":{"type":"string","nullable":true,"description":"VAT code on this line, null for no VAT"},"sales_account_number":{"type":"integer","nullable":true,"description":"Sales account this line is booked to"},"vat_rate":{"type":"number","description":"VAT percentage for this line"},"id":{"type":"string","description":"Line item ID","example":"550e8400-e29b-41d4-a716-446655440002"},"description":{"type":"string","description":"Line item description","example":"Consulting services"},"quantity":{"type":"number","description":"Quantity","example":10},"unit_price":{"type":"number","description":"Unit price (excl. VAT)","example":1000}},"required":["unit","vat_account_id","vat_code","sales_account_number","vat_rate","id","description","quantity","unit_price"]},"description":"Invoice line items"},"memo":{"type":"string","nullable":true,"description":"Note displayed on the invoice","example":"Project Q1 delivery"},"payment_details":{"allOf":[{"$ref":"#/components/schemas/PaymentDetails"},{"description":"The bank account the invoice asks the customer to pay into, as copied when it was created. Null when it names none; such an invoice cannot be booked."}]},"created_at":{"type":"string","nullable":true,"description":"Creation timestamp (ISO 8601, UTC)","example":"2026-01-15T10:30:00Z"}},"required":["id","invoice_number","type","credited_invoice_id","status","issue_date","due_date","contact_id","contact_name","currency","subtotal","vat","vat_rate","total","has_mixed_vat","outstanding_balance","outstanding_balance_currency","currency_rate","delivery_status","email_delivery_status","email_sent_at","e_invoice_sent_at","is_overdue","payment_state","items","memo","payment_details","created_at"]},"CreateInvoiceRequest":{"type":"object","properties":{"contact_id":{"type":"string","format":"uuid","description":"ID of the contact for this invoice","example":"550e8400-e29b-41d4-a716-446655440000"},"issue_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Invoice issue date in YYYY-MM-DD format","example":"2026-01-15"},"due_date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Payment due date in YYYY-MM-DD format","example":"2026-02-15"},"currency":{"type":"string","minLength":3,"maxLength":3,"default":"DKK","description":"ISO 4217 currency code (default: DKK). A currency Numina has no exchange rate for returns 400.","example":"DKK"},"currency_rate":{"type":"number","default":1,"description":"Exchange rate to DKK (1.0 for DKK invoices), kept while the invoice is a draft. Ignored at booking: a foreign-currency invoice then uses the latest rate Numina has on or before its issue date. Without such a rate Numina uses a fixed fallback rate.","example":1},"sales_account_number":{"type":"integer","description":"Sales account for lines that don't set their own (from GET /v1/accounts). Required unless every item has its own `sales_account_number`.","example":1000},"vat_code":{"type":"string","description":"VAT code for lines that don't set their own (e.g. 'U25'). Omit for 0% VAT on those lines. The rate comes from the code; GET /v1/vat-codes lists them. Set `vat_code` on each item to mix rates on one invoice.","example":"U25"},"items":{"type":"array","items":{"type":"object","properties":{"description":{"type":"string","minLength":1,"description":"Line item description","example":"Consulting services"},"quantity":{"type":"number","description":"Quantity","example":10},"unit_price":{"type":"number","description":"Unit price (excl. VAT)","example":1000},"sales_account_number":{"type":"integer","description":"Sales account for this line (from GET /v1/accounts). Omit to use the invoice's `sales_account_number`.","example":1030},"vat_code":{"type":"string","nullable":true,"description":"VAT code for this line (from GET /v1/vat-codes). `null` for no VAT on this line. Omitted, the line takes the default VAT code of its own `sales_account_number`, as in the invoice editor, and otherwise the invoice's `vat_code`.","example":"U25"}},"required":["description","quantity","unit_price"]},"minItems":1,"description":"Invoice line items (minimum 1 item required)"},"memo":{"type":"string","maxLength":2000,"description":"Note displayed on the invoice","example":"Payment due within 30 days"},"bank_account_id":{"type":"string","format":"uuid","description":"The bank account the customer pays into (from GET /v1/bank-accounts, one with `usable_on_invoices`). Its payment details are copied onto the invoice. Omit to use the account marked `is_invoice_default`; when there is none the draft has no payment details and cannot be booked."},"book":{"type":"boolean","default":false,"description":"If true, also requires invoices:book and books the invoice (assigns invoice number, creates AR entries, status becomes 'sent'). It does not send it. If false, saves a draft.","example":false}},"required":["contact_id","issue_date","items"]},"CreateCreditNoteRequest":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Credit note date in YYYY-MM-DD format (default: today). It must be in an open accounting year.","example":"2026-11-10"},"amount":{"type":"number","minimum":0,"exclusiveMinimum":true,"description":"Amount to credit incl. VAT, in the invoice's currency (default: all that is outstanding). It cannot exceed what is outstanding. Lines are credited in proportion.","example":1250},"memo":{"type":"string","maxLength":2000,"description":"Note on the credit note (default: the invoice's note)"}}},"SendInvoiceRequest":{"type":"object","properties":{"email":{"type":"string","maxLength":500,"description":"Recipients instead of the invoice's or contact's address, up to 5, separated by commas. Once a send is accepted, they replace the address saved on the invoice. The contact does not change.","example":"faktura@kunde.dk"}}},"CreateJournalEntryResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Transaction ID","example":"550e8400-e29b-41d4-a716-446655440000"},"date":{"type":"string","nullable":true,"description":"Transaction date","example":"2026-01-15"},"text":{"type":"string","nullable":true,"description":"Transaction description","example":"Invoice payment from customer"},"status":{"type":"string","nullable":true,"description":"Transaction status (draft entries are 'open')","example":"open"},"contact_id":{"type":"string","nullable":true,"format":"uuid","description":"Associated contact (customer/supplier) ID","example":"550e8400-e29b-41d4-a716-446655440000"},"created_at":{"type":"string","nullable":true,"description":"Transaction creation timestamp (ISO 8601, UTC)","example":"2026-01-15T10:30:00Z"},"external_reference":{"type":"string","nullable":true,"description":"The `external_reference` sent when the entry was created","example":"payroll-run-2026-01"},"lines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Journal entry line ID","example":"550e8400-e29b-41d4-a716-446655440000"},"account_number":{"type":"integer","nullable":true,"description":"Account number","example":3000},"account_id":{"type":"string","nullable":true,"description":"Account ID","example":"550e8400-e29b-41d4-a716-446655440001"},"account_name":{"type":"string","description":"Account name","example":"Revenue"},"amount":{"type":"number","description":"Signed amount in `currency_code`: debit is positive, credit is negative. On a draft it includes VAT. On a booked entry VAT is on its own line.","example":-1000},"text":{"type":"string","nullable":true,"description":"Line description","example":"Invoice payment"},"vat_code":{"type":"string","nullable":true,"description":"VAT code if applicable","example":"U25"},"currency_code":{"type":"string","description":"Currency of the line. Entries created through this API are always DKK; entries created in Numina can be in another currency.","example":"DKK","readOnly":true},"currency_rate":{"type":"number","description":"Rate to DKK. 1 for DKK.","example":1,"readOnly":true}},"required":["id","account_number","account_id","account_name","amount","text","vat_code","currency_code","currency_rate"]},"description":"Journal entry lines"}},"required":["id","date","text","status","contact_id","created_at","external_reference","lines"]},"DuplicateExternalReferenceResponse":{"allOf":[{"$ref":"#/components/schemas/ErrorResponse"},{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"The existing entry, when the conflict is a duplicate reference","example":"550e8400-e29b-41d4-a716-446655440000"}}}]},"CreateJournalEntryRequest":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Transaction date in YYYY-MM-DD format","example":"2024-01-15"},"text":{"type":"string","minLength":1,"maxLength":500,"description":"Transaction description","example":"Cash sale"},"contact_id":{"type":"string","format":"uuid","description":"Optional contact (customer/supplier) ID to associate with this transaction","example":"550e8400-e29b-41d4-a716-446655440000"},"lines":{"type":"array","items":{"type":"object","properties":{"account_number":{"type":"integer","description":"Account number in chart of accounts","example":3000},"amount":{"type":"number","description":"Signed amount in DKK: debit is positive, credit is negative. The lines must sum to exactly zero. With a `vat_code`, the amount includes VAT and the VAT is split out when the entry is booked.","example":-1000},"vat_code":{"type":"string","description":"VAT code (e.g., 'U25' for 25% sales VAT). Stored for later calculation during booking.","example":"U25"}},"required":["account_number","amount"]},"minItems":2,"description":"Journal entry line items (minimum 2 lines required for balanced entry)","example":[{"account_number":1010,"amount":-1250,"vat_code":"U25"},{"account_number":6480,"amount":1250}]},"external_reference":{"type":"string","minLength":1,"maxLength":255,"description":"Your own id for this entry (max 255 characters), unique per company for your app. A second entry with the same reference returns 409 with the existing entry's `id`. Available to apps registered as a Numina integration.","example":"payroll-run-2026-01"}},"required":["date","text","lines"]},"ListJournalEntriesResponse":{"type":"object","properties":{"journal_entries":{"type":"array","items":{"$ref":"#/components/schemas/JournalEntry"}},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true}},"required":["journal_entries","has_more","next_offset"]},"JournalEntry":{"allOf":[{"$ref":"#/components/schemas/CreateJournalEntryResponse"},{"type":"object","properties":{"lines":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid","description":"Journal entry line ID","example":"550e8400-e29b-41d4-a716-446655440000"},"account_number":{"type":"integer","nullable":true,"description":"Account number","example":3000},"account_id":{"type":"string","nullable":true,"description":"Account ID","example":"550e8400-e29b-41d4-a716-446655440001"},"account_name":{"type":"string","description":"Account name","example":"Revenue"},"amount":{"type":"number","description":"Signed amount in `currency_code`: debit is positive, credit is negative. On a draft it includes VAT. On a booked entry VAT is on its own line.","example":-1000},"text":{"type":"string","nullable":true,"description":"Line description","example":"Invoice payment"},"vat_code":{"type":"string","nullable":true,"description":"VAT code if applicable","example":"U25"},"currency_code":{"type":"string","description":"Currency of the line. Entries created through this API are always DKK; entries created in Numina can be in another currency.","example":"DKK","readOnly":true},"currency_rate":{"type":"number","description":"Rate to DKK. 1 for DKK.","example":1,"readOnly":true}},"required":["id","account_number","account_id","account_name","amount","text","vat_code","currency_code","currency_rate"]},"description":"On a draft, the lines as entered, with VAT included in the amount. On a booked entry, the posted ledger entries, with VAT on its own line. Empty on a canceled entry."},"state":{"type":"string","enum":["draft","booked","canceled"],"description":"Where the entry stands. `status` keeps the raw value, which can differ between drafts.","example":"draft"},"attachment":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"file_name":{"type":"string","nullable":true},"status":{"type":"string","nullable":true},"attachment_number":{"type":"number","nullable":true,"description":"The bilag number, normally assigned when the entry is booked. Numbers restart each accounting year.","example":42}},"required":["id","file_name","status","attachment_number"],"description":"The document on this entry"}},"required":["state","attachment"]}]},"UpdateJournalEntryRequest":{"type":"object","properties":{"date":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Transaction date in YYYY-MM-DD format","example":"2024-01-15"},"text":{"type":"string","minLength":1,"maxLength":500,"description":"Transaction description","example":"Cash sale"},"contact_id":{"type":"string","nullable":true,"format":"uuid","description":"Set the contact; null clears it","example":"550e8400-e29b-41d4-a716-446655440000"},"lines":{"type":"array","items":{"type":"object","properties":{"account_number":{"type":"integer","description":"Account number in chart of accounts","example":3000},"amount":{"type":"number","description":"Signed amount in DKK: debit is positive, credit is negative. The lines must sum to exactly zero. With a `vat_code`, the amount includes VAT and the VAT is split out when the entry is booked.","example":-1000},"vat_code":{"type":"string","description":"VAT code (e.g., 'U25' for 25% sales VAT). Stored for later calculation during booking.","example":"U25"}},"required":["account_number","amount"]},"minItems":2,"description":"Replaces all lines. Send the full set, in DKK; it must sum to exactly zero."},"attachment_id":{"type":"string","nullable":true,"format":"uuid","description":"Put a document from POST /v1/attachments on this draft. To replace a document, take the current one off first. Null takes the current one off; the document itself stays.","example":"550e8400-e29b-41d4-a716-446655440000"}}},"CreateAttachmentResponse":{"type":"object","properties":{"attachment":{"$ref":"#/components/schemas/AttachmentResponse"}},"required":["attachment"]},"AttachmentResponse":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"file_name":{"type":"string","nullable":true},"status":{"type":"string","nullable":true,"description":"`uploaded`, `processing`, `processing_ocr`: being handled. `processed`: handling is finished. This says nothing about whether OCR ran, which happens only if you asked for it. `pooled`: waiting in the document pool for a bank posting. `empty`: no file yet. `failed_*`: processing failed. Can be null.","example":"uploaded"},"created_at":{"type":"string","nullable":true,"description":"Creation timestamp (ISO 8601, UTC)"}},"required":["id","file_name","status","created_at"]},"AddAttachmentFilesResponse":{"type":"object","properties":{"attachment":{"allOf":[{"$ref":"#/components/schemas/AttachmentResponse"},{"type":"object","properties":{"attachment_number":{"type":"integer","nullable":true,"description":"Bilagsnummer, as shown in Numina. Numbers restart each accounting year.","example":185}},"required":["attachment_number"]}]},"files":{"type":"array","items":{"type":"object","properties":{"file_name":{"type":"string"},"sha256":{"type":"string","description":"SHA-256 of the file as received, hex"}},"required":["file_name","sha256"]}}},"required":["attachment","files"]},"ListAttachmentsResponse":{"type":"object","properties":{"attachments":{"type":"array","items":{"$ref":"#/components/schemas/AttachmentDetail"}},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true}},"required":["attachments","has_more","next_offset"]},"AttachmentDetail":{"allOf":[{"$ref":"#/components/schemas/AttachmentResponse"},{"type":"object","properties":{"attachment_number":{"type":"number","nullable":true,"description":"The bilag number, normally assigned when it is booked. Numbers restart each accounting year, so read it together with `booked_accounting_year`.","example":185},"year":{"type":"string","nullable":true,"description":"The year set on the document. Can be null. For the accounting year of a booked document, use `booked_accounting_year`.","example":"2026"},"is_empty":{"type":"boolean","description":"True for a bilag without a file. Fill it with POST /v1/attachments/{id}/files."},"file_count":{"type":"integer"},"booked_accounting_year":{"type":"string","nullable":true,"description":"The accounting year it was booked in. Null when none is recorded. Bilag numbers restart each year.","example":"2026"},"content_sha256":{"type":"string","nullable":true,"description":"SHA-256 of the file, where Numina recorded one, to check whether a file is already here"},"journal_entry_ids":{"type":"array","items":{"type":"string","format":"uuid"},"description":"The journal entries this document is on"}},"required":["attachment_number","year","is_empty","file_count","booked_accounting_year","content_sha256","journal_entry_ids"]}]},"InboxUploadResponse":{"type":"object","properties":{"attachment":{"$ref":"#/components/schemas/AttachmentDetail"},"duplicate":{"type":"boolean","description":"True when the company already had this file"}},"required":["attachment","duplicate"]},"GetVatCodesResponse":{"type":"object","properties":{"vat_codes":{"type":"array","items":{"$ref":"#/components/schemas/VatCode"}}},"required":["vat_codes"]},"VatCode":{"type":"object","properties":{"vat_code":{"type":"string","description":"VAT code identifier","example":"U25"},"id":{"type":"string","format":"uuid"},"name":{"type":"string","description":"VAT code name","example":"Udgående moms 25%"},"vat_type":{"type":"string","description":"Kind of VAT, for example sales_dk, purchase_tax or reverse_charge_sales","example":"sales_dk"},"rate":{"type":"number","description":"Percentage: 25 means 25%","example":25}},"required":["vat_code","id","name","vat_type","rate"]},"TrialBalance":{"type":"object","properties":{"date":{"type":"string","example":"2026-06-30"},"accounting_year":{"type":"string","description":"The accounting year containing date","example":"2026"},"year_start":{"type":"string","example":"2026-01-01"},"year_end":{"type":"string","example":"2026-12-31"},"year_is_closed":{"type":"boolean"},"base_currency":{"type":"string","enum":["DKK"]},"accounts":{"type":"array","items":{"type":"object","properties":{"account_number":{"type":"integer","example":5820},"name":{"type":"string","example":"Bank"},"statement":{"type":"string","enum":["balance_sheet","profit_loss"],"description":"Which financial statement the account belongs to"},"category":{"type":"string","nullable":true,"enum":["asset","liability","equity","income","expense",null],"description":"Account category from the chart of accounts; null when the account has none set"},"balance_dkk":{"type":"number"}},"required":["account_number","name","statement","category","balance_dkk"]}}},"required":["date","accounting_year","year_start","year_end","year_is_closed","base_currency","accounts"],"example":{"date":"2026-06-30","accounting_year":"2026","year_start":"2026-01-01","year_end":"2026-12-31","year_is_closed":false,"base_currency":"DKK","accounts":[{"account_number":1010,"name":"Salg af varer","statement":"profit_loss","category":"income","balance_dkk":-500000},{"account_number":2000,"name":"Vareforbrug","statement":"profit_loss","category":"expense","balance_dkk":300000},{"account_number":5820,"name":"Bank","statement":"balance_sheet","category":"asset","balance_dkk":250000},{"account_number":6800,"name":"Leverandørgæld","statement":"balance_sheet","category":"liability","balance_dkk":-50000}]}},"AccountBalances":{"type":"object","properties":{"from":{"type":"string","example":"2026-01-01"},"to":{"type":"string","example":"2026-02-28"},"granularity":{"type":"string","enum":["month","quarter","year"]},"base_currency":{"type":"string","enum":["DKK"]},"periods":{"type":"array","items":{"type":"object","properties":{"period":{"type":"string","description":"\"2026-01\" (month), \"2026-Q1\" (quarter) or \"2026\" (year)","example":"2026-01"},"start":{"type":"string","description":"First day, clipped to from","example":"2026-01-01"},"end":{"type":"string","description":"Last day, clipped to to","example":"2026-01-31"}},"required":["period","start","end"]}},"accounts":{"type":"array","items":{"type":"object","properties":{"account_number":{"type":"integer","example":5820},"name":{"type":"string","example":"Bank"},"statement":{"type":"string","enum":["balance_sheet","profit_loss"],"description":"Which financial statement the account belongs to"},"category":{"type":"string","nullable":true,"enum":["asset","liability","equity","income","expense",null],"description":"Account category from the chart of accounts; null when the account has none set"},"periods":{"type":"array","items":{"type":"object","properties":{"period":{"type":"string","description":"\"2026-01\" (month), \"2026-Q1\" (quarter) or \"2026\" (year)","example":"2026-01"},"net_change":{"type":"number","description":"Sum of booked entries in the period, excluding primo"}},"required":["period","net_change"]}},"net_change_total":{"type":"number"},"opening_balance":{"type":"number","description":"Balance-sheet accounts only: balance at the start of from, including the accounting year's opening balances (primo), so last year's result is already in equity"},"closing_balance":{"type":"number","description":"Balance-sheet accounts only: balance at the end of to (as GET /v1/trial-balance); equals opening_balance + net_change_total"}},"required":["account_number","name","statement","category","periods","net_change_total"]}}},"required":["from","to","granularity","base_currency","periods","accounts"],"example":{"from":"2026-01-01","to":"2026-02-28","granularity":"month","base_currency":"DKK","periods":[{"period":"2026-01","start":"2026-01-01","end":"2026-01-31"},{"period":"2026-02","start":"2026-02-01","end":"2026-02-28"}],"accounts":[{"account_number":1010,"name":"Salg af varer","statement":"profit_loss","category":"income","periods":[{"period":"2026-01","net_change":-40000},{"period":"2026-02","net_change":-45000}],"net_change_total":-85000},{"account_number":5820,"name":"Bank","statement":"balance_sheet","category":"asset","periods":[{"period":"2026-01","net_change":-20000},{"period":"2026-02","net_change":30000}],"net_change_total":10000,"opening_balance":180000,"closing_balance":190000},{"account_number":6000,"name":"Overført resultat","statement":"balance_sheet","category":"equity","periods":[{"period":"2026-01","net_change":0},{"period":"2026-02","net_change":0}],"net_change_total":0,"opening_balance":-285000,"closing_balance":-285000}]}},"LedgerEntries":{"type":"object","properties":{"entries":{"type":"array","items":{"$ref":"#/components/schemas/LedgerEntry"}},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true}},"required":["entries","has_more","next_offset"]},"LedgerEntry":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"transaction_id":{"type":"string","format":"uuid"},"date":{"type":"string","example":"2026-03-14"},"entry_number":{"type":"integer","description":"Posting number, sequential within the accounting year"},"account_number":{"type":"integer","nullable":true,"example":4000},"account_name":{"type":"string","example":"Kontorartikler"},"text":{"type":"string"},"amount":{"type":"number","description":"Amount in currency"},"amount_dkk":{"type":"number"},"currency":{"type":"string","example":"DKK"},"is_vat_entry":{"type":"boolean","description":"The VAT posting split off a line booked with a VAT code"},"vat_code":{"type":"string","nullable":true,"description":"VAT code the entry was booked with","example":"I25"},"entry_type":{"type":"string","nullable":true,"enum":["vat_settlement","primo","system_rounding",null],"description":"null for ordinary postings; primo marks opening balances"},"attachment_id":{"type":"string","nullable":true,"format":"uuid"},"attachment_number":{"type":"integer","nullable":true,"description":"Bilagsnummer, as shown in Numina","example":185}},"required":["id","transaction_id","date","entry_number","account_number","account_name","text","amount","amount_dkk","currency","is_vat_entry","vat_code","entry_type","attachment_id","attachment_number"]},"ListBankTransactionsResponse":{"type":"object","properties":{"bank_transactions":{"type":"array","items":{"$ref":"#/components/schemas/BankTransaction"}},"has_more":{"type":"boolean"},"next_offset":{"type":"integer","nullable":true}},"required":["bank_transactions","has_more","next_offset"]},"BankTransaction":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"bank_account_id":{"type":"string","format":"uuid"},"bank_account_name":{"type":"string","nullable":true},"ledger_account_number":{"type":"number","nullable":true},"date":{"type":"string","nullable":true},"value_date":{"type":"string","nullable":true},"amount":{"type":"number","description":"Negative is money out"},"currency":{"type":"string"},"counterparty":{"type":"string","nullable":true},"payment_reference":{"type":"string","nullable":true},"state":{"type":"string","enum":["unreconciled","reconciled"],"example":"unreconciled"},"journal_entries":{"type":"array","items":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"state":{"type":"string","enum":["draft","booked"]}},"required":["id","state"]},"description":"The entries this line is reconciled with. Here `state` only separates booked from not booked, so a canceled entry shows as one of these two."},"agent_processing":{"type":"boolean","description":"Numina's bookkeeping agent is handling this line right now"}},"required":["id","bank_account_id","bank_account_name","ledger_account_number","date","value_date","amount","currency","counterparty","payment_reference","state","journal_entries","agent_processing"]},"Me":{"type":"object","properties":{"user":{"type":"object","nullable":true,"properties":{"id":{"type":"string","format":"uuid"},"email":{"type":"string"},"first_name":{"type":"string","nullable":true},"last_name":{"type":"string","nullable":true}},"required":["id","email","first_name","last_name"],"description":"The signed-in user. Null for an API key."},"company":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"}},"required":["id","name","slug"]},"scopes":{"type":"array","items":{"type":"string"},"example":["contacts:read","invoices:read"]},"auth_type":{"type":"string","enum":["oauth","api_key"]}},"required":["user","company","scopes","auth_type"]},"Company":{"type":"object","properties":{"id":{"type":"string","format":"uuid"},"name":{"type":"string"},"slug":{"type":"string"},"cvr":{"type":"string","nullable":true,"example":"12345678"},"is_vat_registered":{"type":"boolean"},"base_currency":{"type":"string","example":"DKK"}},"required":["id","name","slug","cvr","is_vat_registered","base_currency"]},"AccountingYears":{"type":"object","properties":{"accounting_years":{"type":"array","items":{"type":"object","properties":{"year":{"type":"string","example":"2026"},"start_date":{"type":"string","example":"2026-01-01"},"end_date":{"type":"string","example":"2026-12-31"},"is_closed":{"type":"boolean"}},"required":["year","start_date","end_date","is_closed"]}}},"required":["accounting_years"]}},"parameters":{}},"paths":{"/v1/accounts":{"get":{"summary":"List accounts","description":"Returns the company's whole chart of accounts, not paginated. Headings and subtotals are included; only `profitAndLoss` and `status` accounts can be posted to.","tags":["Accounts"],"security":[{"oauth2":["setup:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"responses":{"200":{"description":"List of accounts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetAccountsResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/contacts":{"get":{"summary":"List contacts","description":"Returns the company's contacts, including archived ones, ordered by name. An out-of-range `limit` or `offset` is clamped rather than refused: `limit` to 1-500, and 0 or a value that is not a number falls back to the default.","tags":["Contacts"],"security":[{"oauth2":["contacts:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","description":"Part of the name, or the exact CVR number","example":"Acme"},"required":false,"description":"Part of the name, or the exact CVR number","name":"search","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100,"description":"Maximum number of results (default 100). Clamped to 1-500.","example":100},"required":false,"description":"Maximum number of results (default 100). Clamped to 1-500.","name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0,"description":"Number of results to skip for pagination","example":0},"required":false,"description":"Number of results to skip for pagination","name":"offset","in":"query"}],"responses":{"200":{"description":"List of contacts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListContactsResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create contact","description":"Creates a new contact (customer/supplier) for the authenticated company.","tags":["Contacts"],"security":[{"oauth2":["contacts:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateContactRequest"}}}},"responses":{"201":{"description":"Contact created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/contacts/{id}":{"get":{"summary":"Get contact","description":"Returns a single contact by ID.","tags":["Contacts"],"security":[{"oauth2":["contacts:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Contact ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Contact ID","name":"id","in":"path"}],"responses":{"200":{"description":"Contact details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"summary":"Update contact","description":"Updates an existing contact. Only provided fields are updated.","tags":["Contacts"],"security":[{"oauth2":["contacts:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Contact ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Contact ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateContactRequest"}}}},"responses":{"200":{"description":"Contact updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Contact"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/products":{"get":{"summary":"List products","description":"Returns the company's product catalogue (varekartotek), ordered by name.","tags":["Products"],"security":[{"oauth2":["products:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","maxLength":200,"description":"Search by partial name or product number","example":"Consulting"},"required":false,"description":"Search by partial name or product number","name":"search","in":"query"},{"schema":{"type":"string","maxLength":64,"description":"Exact product number","example":"1001"},"required":false,"description":"Exact product number","name":"product_number","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100,"description":"Maximum number of results (default 100, max 500)","example":100},"required":false,"description":"Maximum number of results (default 100, max 500)","name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0,"description":"Number of results to skip for pagination","example":0},"required":false,"description":"Number of results to skip for pagination","name":"offset","in":"query"}],"responses":{"200":{"description":"List of products","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListProductsResponse"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create product","description":"Creates a product in the company's catalogue. Returns 409 when the product number is already used.","tags":["Products"],"security":[{"oauth2":["products:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateProductRequest"}}}},"responses":{"201":{"description":"Product created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The product number is already used","content":{"application/json":{"examples":{"conflict_1":{"summary":"Product number 1001 is already used by another product","value":{"error":"conflict","error_description":"Product number 1001 is already used by another product"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/products/{id}":{"get":{"summary":"Get product","description":"Returns a single product by ID.","tags":["Products"],"security":[{"oauth2":["products:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Product ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Product ID","name":"id","in":"path"}],"responses":{"200":{"description":"Product details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"summary":"Update product","description":"Updates a product. Only provided fields are updated. Returns 409 when the product number is already used.","tags":["Products"],"security":[{"oauth2":["products:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Product ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Product ID","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateProductRequest"}}}},"responses":{"200":{"description":"Product updated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Product"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The product number is already used","content":{"application/json":{"examples":{"conflict_1":{"summary":"Product number 1001 is already used by another product","value":{"error":"conflict","error_description":"Product number 1001 is already used by another product"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"summary":"Delete product","description":"Deletes a product. Invoices keep their lines; they copy the product when added.","tags":["Products"],"security":[{"oauth2":["products:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Product ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Product ID","name":"id","in":"path"}],"responses":{"204":{"description":"Product deleted"},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/bank-accounts":{"get":{"summary":"List bank accounts","description":"The company's connected bank accounts and, when it has typed in payment details for a bank that is not connected, one manual account. Use it to choose where an invoice is paid (`bank_account_id` on POST /v1/invoices) and to filter bank lines. Not paginated.","tags":["Bank"],"security":[{"oauth2":["bank:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"responses":{"200":{"description":"Bank accounts","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBankAccountsResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/invoices":{"get":{"summary":"List invoices","description":"Returns the company's invoices, credit notes and offers, newest issue date first; check `type`. A malformed `contact_id`, `from_date` or `to_date` returns 400. An unknown `status` returns an empty list. An out-of-range `limit` or `offset` is clamped rather than refused: `limit` to 1-500, and 0 or a value that is not a number falls back to the default.","tags":["Invoices"],"security":[{"oauth2":["invoices:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","enum":["draft","sent","closed","paid","cancelled","accepted","declined"],"description":"Filter by status. A value that does not exist returns an empty list.","example":"sent"},"required":false,"description":"Filter by status. A value that does not exist returns an empty list.","name":"status","in":"query"},{"schema":{"type":"string","format":"uuid","description":"Filter by contact ID"},"required":false,"description":"Filter by contact ID","name":"contact_id","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Filter invoices issued on or after this date (YYYY-MM-DD)","example":"2026-01-01"},"required":false,"description":"Filter invoices issued on or after this date (YYYY-MM-DD)","name":"from_date","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Filter invoices issued on or before this date (YYYY-MM-DD)","example":"2026-12-31"},"required":false,"description":"Filter invoices issued on or before this date (YYYY-MM-DD)","name":"to_date","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100,"description":"Maximum number of results (default 100). Clamped to 1-500.","example":100},"required":false,"description":"Maximum number of results (default 100). Clamped to 1-500.","name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0,"description":"Number of results to skip for pagination","example":0},"required":false,"description":"Number of results to skip for pagination","name":"offset","in":"query"}],"responses":{"200":{"description":"List of invoices","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListInvoicesResponse"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Create invoice","description":"Creates a sales invoice draft. Each line takes its VAT code and sales account from the item, or from the invoice's `vat_code` and `sales_account_number` when the item has none, so one invoice can mix rates (for example a 25% item and a fee without VAT). Line sales accounts must be marked as sales accounts and line VAT codes must be sales codes.\n\nWith `book: true` the request also needs invoices:book and books the invoice the same way as POST /v1/invoices/{id}/book. Each call is your own approval of the invoice: Numina does not review it, so make sure a person at your company has checked it or has approved the rules your system books by. Booking is final and cannot be undone through this API. It does not send the invoice. Fuld Service customers get 403 `booking_not_available`, a date in a closed accounting year or before the first accounting date gets 400, and an invoice with no bank account to be paid into gets 400 `payment_details_required`, all before anything is written.\n\nPayment details come from `bank_account_id`, or from the company's default account when you omit it (see `is_invoice_default` on GET /v1/bank-accounts). Numina never guesses between several accounts: with more than one and no default, pass `bank_account_id`.\n\nIf booking fails after those checks (400, 409 or 500), the draft remains, and the error response does not include its id. Look for it with GET /v1/invoices?status=draft before you retry, because a failed request frees its Idempotency-Key and a retry creates a second draft. You can also create without `book` and then call POST /v1/invoices/{id}/book, which is safe to repeat.\n\nSend an `Idempotency-Key` header (for example a UUID generated per request) to retry safely after a timeout or network error. A retry with the same key and the same body returns the original response with the header `Idempotent-Replayed: true`. Keys are scoped to the company and kept for 24 hours after a successful request. A failed request (4xx/5xx) frees the key, so a retry with that key runs again. Reusing a key with a different body returns 422; retrying while the first request is still processing returns 409.","tags":["Invoices"],"security":[{"oauth2":["invoices:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.","example":"5f8d2a3e-6c1b-4b7e-9a0f-2d4c6e8b1a3f"},"required":false,"description":"Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.","name":"Idempotency-Key","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateInvoiceRequest"}}}},"responses":{"201":{"description":"Invoice created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, the company is archived, or the company is on Fuld Service","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}},"booking_not_available_3":{"summary":"Your bookkeepers at Numina book for this company, so entries cannot be booked through the API. Create drafts instead.","value":{"error":"booking_not_available","error_description":"Your bookkeepers at Numina book for this company, so entries cannot be booked through the API. Create drafts instead."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The invoice is being updated (with `book: true`; the draft remains), or a request with the same Idempotency-Key is still being processed","content":{"application/json":{"examples":{"conflict_1":{"summary":"Invoice is already being updated. Please try again.","value":{"error":"conflict","error_description":"Invoice is already being updated. Please try again."}},"idempotency_key_in_use_2":{"summary":"A request with this Idempotency-Key is still being processed. Retry shortly.","value":{"error":"idempotency_key_in_use","error_description":"A request with this Idempotency-Key is still being processed. Retry shortly."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Entity - The Idempotency-Key was already used with a different request","content":{"application/json":{"examples":{"idempotency_key_reused_1":{"summary":"This Idempotency-Key was already used with a different request. Use a new key for a new request.","value":{"error":"idempotency_key_reused","error_description":"This Idempotency-Key was already used with a different request. Use a new key for a new request."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/invoices/{id}":{"get":{"summary":"Get invoice","description":"Returns a single invoice with full details including line items and payment status (outstanding balance, overdue status).","tags":["Invoices"],"security":[{"oauth2":["invoices:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Invoice ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Invoice ID","name":"id","in":"path"}],"responses":{"200":{"description":"Invoice details with payment status","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"summary":"Delete draft invoice","description":"Deletes a draft. Drafts have no invoice number, so nothing is missing from the numbering. A booked invoice cannot be deleted; credit it in Numina instead. Deleting an invoice that is already gone returns 404.","tags":["Invoices"],"security":[{"oauth2":["invoices:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Invoice ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Invoice ID","name":"id","in":"path"}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The invoice is not a draft, or it is being booked right now","content":{"application/json":{"examples":{"conflict_1":{"summary":"Only drafts can be deleted. Credit a booked invoice instead.","value":{"error":"conflict","error_description":"Only drafts can be deleted. Credit a booked invoice instead."}},"conflict_2":{"summary":"Only invoices can be deleted here","value":{"error":"conflict","error_description":"Only invoices can be deleted here"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/invoices/{id}/pdf":{"get":{"summary":"Download invoice PDF","description":"Returns a booked invoice or credit note as the PDF the customer gets by email, so you can send it yourself. Numina stores the PDF when the invoice is booked, so every download is the same file. Drafts have no PDF here, because they have no invoice number yet.","tags":["Invoices"],"security":[{"oauth2":["invoices:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Invoice ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Invoice ID","name":"id","in":"path"}],"responses":{"200":{"description":"The PDF. `Content-Disposition` carries the file name the email would use.","content":{"application/pdf":{"schema":{"type":"string","format":"binary"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The invoice is a draft or an offer","content":{"application/json":{"examples":{"conflict_1":{"summary":"Book the invoice before downloading its PDF","value":{"error":"conflict","error_description":"Book the invoice before downloading its PDF"}},"conflict_2":{"summary":"Only invoices and credit notes have a PDF here","value":{"error":"conflict","error_description":"Only invoices and credit notes have a PDF here"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/invoices/{id}/book":{"post":{"summary":"Book draft invoice","description":"Books a draft invoice to the ledger and assigns its number. Each call is your own approval of the invoice: Numina does not review it, so make sure a person at your company has checked it or has approved the rules your system books by. Booking is final and cannot be undone through this API. It does not send the invoice; use POST /v1/invoices/{id}/send.\n\nNot available to Fuld Service customers (403 `booking_not_available`), whose books Numina's bookkeepers keep.\n\nReturns 400 `validation_error` for a date in a closed accounting year or before the first accounting date, VAT on a company that is not VAT registered, an e-invoice that cannot be sent, or an invoice that is not complete. Returns 400 `payment_details_required` for an invoice with no payment details when the company has no default bank account to fill them from (see `is_invoice_default` on GET /v1/bank-accounts); create the invoice again with `bank_account_id`. A foreign-currency invoice uses the latest exchange rate Numina has on or before its issue date; see `currency_rate` on POST /v1/invoices.\n\nSafe to retry: booking an invoice that is already booked returns it unchanged with 200. Credit notes and offers cannot be booked here and return 409.","tags":["Invoices"],"security":[{"oauth2":["invoices:book"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Invoice ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Invoice ID","name":"id","in":"path"}],"responses":{"200":{"description":"The booked invoice","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, the company is archived, or the company is on Fuld Service","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}},"booking_not_available_3":{"summary":"Your bookkeepers at Numina book for this company, so entries cannot be booked through the API. Create drafts instead.","value":{"error":"booking_not_available","error_description":"Your bookkeepers at Numina book for this company, so entries cannot be booked through the API. Create drafts instead."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - This is a credit note or offer, or the invoice is being updated","content":{"application/json":{"examples":{"conflict_1":{"summary":"Only invoices can be booked here","value":{"error":"conflict","error_description":"Only invoices can be booked here"}},"conflict_2":{"summary":"Invoice is already being updated. Please try again.","value":{"error":"conflict","error_description":"Invoice is already being updated. Please try again."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/invoices/{id}/credit-notes":{"post":{"summary":"Credit invoice","description":"Creates and books a credit note for a booked invoice, by default for all that is outstanding, and lowers the invoice's outstanding balance. Each call is your own approval of the invoice: Numina does not review it, so make sure a person at your company has checked it or has approved the rules your system books by. Booking is final and cannot be undone through this API. It does not send the credit note; use POST /v1/invoices/{id}/send with the credit note's ID.\n\nNot available to Fuld Service customers (403 `booking_not_available`).\n\nEvery successful call books a credit note, so send an `Idempotency-Key` header to retry safely. Two credit notes for the same invoice at once: one gets 409 and nothing is booked for it.\n\nReturns 409 for a draft, an offer or a credit note, and when nothing is outstanding. Returns 400 `validation_error` for an amount above what is outstanding or a date in a closed accounting year.","tags":["Invoices"],"security":[{"oauth2":["invoices:book"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Invoice ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Invoice ID","name":"id","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.","example":"5f8d2a3e-6c1b-4b7e-9a0f-2d4c6e8b1a3f"},"required":false,"description":"Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.","name":"Idempotency-Key","in":"header"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateCreditNoteRequest"}}}},"responses":{"201":{"description":"The booked credit note","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, the company is archived, or the company is on Fuld Service","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}},"booking_not_available_3":{"summary":"Your bookkeepers at Numina book for this company, so entries cannot be booked through the API. Create drafts instead.","value":{"error":"booking_not_available","error_description":"Your bookkeepers at Numina book for this company, so entries cannot be booked through the API. Create drafts instead."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The invoice cannot be credited, has nothing outstanding, or is being updated","content":{"application/json":{"examples":{"conflict_1":{"summary":"Invoice must be sent before creating a credit note","value":{"error":"conflict","error_description":"Invoice must be sent before creating a credit note"}},"conflict_2":{"summary":"Fakturaen har intet udestående beløb at kreditere","value":{"error":"conflict","error_description":"Fakturaen har intet udestående beløb at kreditere"}},"conflict_3":{"summary":"Invoice is already being updated. Please try again.","value":{"error":"conflict","error_description":"Invoice is already being updated. Please try again."}},"idempotency_key_in_use_4":{"summary":"A request with this Idempotency-Key is still being processed. Retry shortly.","value":{"error":"idempotency_key_in_use","error_description":"A request with this Idempotency-Key is still being processed. Retry shortly."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Entity - The Idempotency-Key was already used with a different request","content":{"application/json":{"examples":{"idempotency_key_reused_1":{"summary":"This Idempotency-Key was already used with a different request. Use a new key for a new request.","value":{"error":"idempotency_key_reused","error_description":"This Idempotency-Key was already used with a different request. Use a new key for a new request."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/invoices/{id}/send":{"post":{"summary":"Send invoice by email","description":"Emails a booked invoice or credit note to the customer with its PDF. Offers and drafts return 409. A sandbox company cannot send email and gets 403 `forbidden`. With no `email` and no address on the invoice or contact, the response is 400.\n\nA 200 means the email provider accepted the message for at least one recipient. It does not mean the email was delivered, and another recipient may have been refused. Check `delivery_status` and `email_delivery_status`. If no recipient is accepted, the response is 500.\n\nEvery successful call sends an email, so send an `Idempotency-Key` header to retry safely after a timeout. A failed request frees its key, so a retry after a 5xx runs again and can email again: the failure can come after the provider accepted the message.\n\nSend an `Idempotency-Key` header (for example a UUID generated per request) to retry safely after a timeout or network error. A retry with the same key and the same body returns the original response with the header `Idempotent-Replayed: true`. Keys are scoped to the company and kept for 24 hours after a successful request. A failed request (4xx/5xx) frees the key, so a retry with that key runs again. Reusing a key with a different body returns 422; retrying while the first request is still processing returns 409.","tags":["Invoices"],"security":[{"oauth2":["invoices:book"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Invoice ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Invoice ID","name":"id","in":"path"},{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.","example":"5f8d2a3e-6c1b-4b7e-9a0f-2d4c6e8b1a3f"},"required":false,"description":"Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.","name":"Idempotency-Key","in":"header"}],"requestBody":{"required":false,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SendInvoiceRequest"}}}},"responses":{"200":{"description":"The invoice, after sending","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Invoice"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, the company is archived, or it is a sandbox company","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: invoices:book","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: invoices:book","scope":"invoices:book"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}},"forbidden_3":{"summary":"Det er ikke muligt i en demo-konto.","value":{"error":"forbidden","error_description":"Det er ikke muligt i en demo-konto."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The invoice is a draft or an offer, or a request with the same Idempotency-Key is still being processed","content":{"application/json":{"examples":{"conflict_1":{"summary":"Book the invoice before sending it","value":{"error":"conflict","error_description":"Book the invoice before sending it"}},"conflict_2":{"summary":"Only invoices and credit notes can be sent here","value":{"error":"conflict","error_description":"Only invoices and credit notes can be sent here"}},"idempotency_key_in_use_3":{"summary":"A request with this Idempotency-Key is still being processed. Retry shortly.","value":{"error":"idempotency_key_in_use","error_description":"A request with this Idempotency-Key is still being processed. Retry shortly."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"422":{"description":"Unprocessable Entity - The Idempotency-Key was already used with a different request","content":{"application/json":{"examples":{"idempotency_key_reused_1":{"summary":"This Idempotency-Key was already used with a different request. Use a new key for a new request.","value":{"error":"idempotency_key_reused","error_description":"This Idempotency-Key was already used with a different request. Use a new key for a new request."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/journal-entries":{"post":{"summary":"Create journal entry","description":"Creates a draft journal entry for the authenticated company. The entry is created with status 'open' and does not affect balances until it is booked, in the Numina UI or with POST /v1/journal-entries/{id}/book. VAT calculations (if vat_code provided) happen automatically during booking.\n\nAn account, VAT code or contact that does not exist returns 400 `invalid_request`.\n\nSend an `Idempotency-Key` header (for example a UUID generated per request) to retry safely after a timeout or network error. A retry with the same key and the same body returns the original response with the header `Idempotent-Replayed: true`. Keys are scoped to the company and kept for 24 hours after a successful request. A failed request (4xx/5xx) frees the key, so a retry with that key runs again. Reusing a key with a different body returns 422; retrying while the first request is still processing returns 409.","tags":["Journal Entries"],"security":[{"oauth2":["journal:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.","example":"5f8d2a3e-6c1b-4b7e-9a0f-2d4c6e8b1a3f"},"required":false,"description":"Unique key for this request (max 255 characters). Retrying with the same key and body returns the original response instead of doing the work again.","name":"Idempotency-Key","in":"header"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJournalEntryRequest"}}}},"responses":{"201":{"description":"Journal entry created successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateJournalEntryResponse"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - An entry with this `external_reference` already exists (`id` is that entry), or a request with the same Idempotency-Key is still being processed","content":{"application/json":{"examples":{"duplicate":{"summary":"Duplicate external_reference","value":{"error":"conflict","error_description":"A journal entry with this external_reference already exists","id":"550e8400-e29b-41d4-a716-446655440000"}},"inUse":{"summary":"Idempotency-Key in use","value":{"error":"idempotency_key_in_use","error_description":"A request with this Idempotency-Key is still being processed. Retry shortly."}}},"schema":{"$ref":"#/components/schemas/DuplicateExternalReferenceResponse"}}}},"422":{"description":"Unprocessable Entity - The Idempotency-Key was already used with a different request","content":{"application/json":{"examples":{"idempotency_key_reused_1":{"summary":"This Idempotency-Key was already used with a different request. Use a new key for a new request.","value":{"error":"idempotency_key_reused","error_description":"This Idempotency-Key was already used with a different request. Use a new key for a new request."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"get":{"summary":"List journal entries","description":"Lists draft, booked and canceled entries, newest entry date first. For posted entries across a period as reports read them, use GET /v1/ledger-entries.","tags":["Journal Entries"],"security":[{"oauth2":["journal:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","enum":["draft","booked","canceled"],"description":"Only entries in this state. Omit for all.","example":"draft"},"required":false,"description":"Only entries in this state. Omit for all.","name":"state","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Earliest date (YYYY-MM-DD)","example":"2026-01-01"},"required":false,"description":"Earliest date (YYYY-MM-DD)","name":"from_date","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Latest date (YYYY-MM-DD)","example":"2026-12-31"},"required":false,"description":"Latest date (YYYY-MM-DD)","name":"to_date","in":"query"},{"schema":{"type":"string","format":"uuid","description":"Only entries on this contact"},"required":false,"description":"Only entries on this contact","name":"contact_id","in":"query"},{"schema":{"type":"string","minLength":1,"maxLength":255,"description":"Only the entry with this `external_reference`, to find an entry again after a timeout","example":"payroll-run-2026-01"},"required":false,"description":"Only the entry with this `external_reference`, to find an entry again after a timeout","name":"external_reference","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100,"description":"Maximum number of results (default 100, max 500)","example":100},"required":false,"description":"Maximum number of results (default 100, max 500)","name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0,"description":"Number of results to skip","example":0},"required":false,"description":"Number of results to skip","name":"offset","in":"query"}],"responses":{"200":{"description":"Journal entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListJournalEntriesResponse"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/journal-entries/{id}":{"get":{"summary":"Get journal entry","description":"Returns one draft, booked or canceled entry with its lines and document.","tags":["Journal Entries"],"security":[{"oauth2":["journal:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Journal entry ID, as returned when it was created","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Journal entry ID, as returned when it was created","name":"id","in":"path"}],"responses":{"200":{"description":"The journal entry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalEntry"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"put":{"summary":"Update draft journal entry","description":"Changes a draft; only the fields you send change. `lines` replaces all lines, and lines that do not sum to exactly zero return 400 `validation_error`. A draft reconciled with a bank line must keep its line on that bank account. Booked and canceled entries cannot be changed here.\n\nReturns 409 when the entry is not a draft, when Numina's bookkeeping agent is working on it, when you send `lines` for a foreign-currency draft, or when you send an `attachment_id` and the entry is linked to an invoice, already has a different document, or the document is already on another entry.","tags":["Journal Entries"],"security":[{"oauth2":["journal:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Journal entry ID, as returned when it was created","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Journal entry ID, as returned when it was created","name":"id","in":"path"}],"requestBody":{"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateJournalEntryRequest"}}}},"responses":{"200":{"description":"The updated draft","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalEntry"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The entry cannot take this change. Retry in a few minutes if the agent is the cause.","content":{"application/json":{"examples":{"conflict_1":{"summary":"Only draft entries can be changed","value":{"error":"conflict","error_description":"Only draft entries can be changed"}},"conflict_2":{"summary":"Numina's bookkeeping agent is working on this entry. Try again in a few minutes.","value":{"error":"conflict","error_description":"Numina's bookkeeping agent is working on this entry. Try again in a few minutes."}},"conflict_3":{"summary":"This draft is in a foreign currency, and lines sent here are in DKK. Change its lines in Numina.","value":{"error":"conflict","error_description":"This draft is in a foreign currency, and lines sent here are in DKK. Change its lines in Numina."}},"conflict_4":{"summary":"This entry is linked to an invoice, which is its document.","value":{"error":"conflict","error_description":"This entry is linked to an invoice, which is its document."}},"conflict_5":{"summary":"This entry already has a document. Take it off first with attachment_id null.","value":{"error":"conflict","error_description":"This entry already has a document. Take it off first with attachment_id null."}},"conflict_6":{"summary":"This document is already on another journal entry.","value":{"error":"conflict","error_description":"This document is already on another journal entry."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"delete":{"summary":"Delete draft journal entry","description":"Deletes a draft. Its bank links and suggestions go with it; its document stays and becomes unlinked. Booked entries cannot be deleted.","tags":["Journal Entries"],"security":[{"oauth2":["journal:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Journal entry ID, as returned when it was created","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Journal entry ID, as returned when it was created","name":"id","in":"path"}],"responses":{"204":{"description":"Deleted"},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The entry is booked, or Numina's bookkeeping agent is working on it. Retry in a few minutes if the agent is the cause.","content":{"application/json":{"examples":{"conflict_1":{"summary":"Booked entries cannot be deleted","value":{"error":"conflict","error_description":"Booked entries cannot be deleted"}},"conflict_2":{"summary":"Numina's bookkeeping agent is working on this entry. Try again in a few minutes.","value":{"error":"conflict","error_description":"Numina's bookkeeping agent is working on this entry. Try again in a few minutes."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/journal-entries/{id}/book":{"post":{"summary":"Book journal entry","description":"Books this one draft to the ledger. Each call is your own approval of the entry: Numina does not review it, so make sure a person at your company has checked it or has approved the rules your system books by. It works whether or not Numina AI and Autopilot are on, and books nothing else. A booked entry is final: it can only be corrected in Numina, with reversing entries.\n\nNot available to Fuld Service customers (403 `booking_not_available`), whose books Numina's bookkeepers keep.\n\nThe entry needs a document: upload one first with POST /v1/journal-entries/{id}/attachments, or put one on it with `attachment_id` on PUT. An entry linked to an invoice needs no other document. Booking does not wait for Numina to read the document.\n\nThe date decides the accounting year. A date after the latest accounting year creates the following year. A closed year, a date before the company's first accounting date, or an earlier date no accounting year covers returns 400. So does a line without an account, a line with a zero amount, or lines that do not sum to zero, also once VAT is split out.\n\nSafe to retry: booking an entry that is already booked returns it unchanged with 200.","tags":["Journal Entries"],"security":[{"oauth2":["journal:book"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Journal entry ID, as returned when it was created","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Journal entry ID, as returned when it was created","name":"id","in":"path"}],"responses":{"200":{"description":"The booked entry","content":{"application/json":{"schema":{"$ref":"#/components/schemas/JournalEntry"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, the company is archived, or the company is on Fuld Service","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}},"booking_not_available_3":{"summary":"Your bookkeepers at Numina book for this company, so entries cannot be booked through the API. Create drafts instead.","value":{"error":"booking_not_available","error_description":"Your bookkeepers at Numina book for this company, so entries cannot be booked through the API. Create drafts instead."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The entry has no document (`attachment_required`), was canceled, or Numina's bookkeeping agent is working on it (`conflict`; retry in a few minutes).","content":{"application/json":{"examples":{"attachment_required_1":{"summary":"Upload a document to this entry before booking it.","value":{"error":"attachment_required","error_description":"Upload a document to this entry before booking it."}},"conflict_2":{"summary":"This entry was canceled and cannot be booked","value":{"error":"conflict","error_description":"This entry was canceled and cannot be booked"}},"conflict_3":{"summary":"Numina's bookkeeping agent is working on this entry. Try again in a few minutes.","value":{"error":"conflict","error_description":"Numina's bookkeeping agent is working on this entry. Try again in a few minutes."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/journal-entries/{id}/attachments":{"post":{"summary":"Upload attachment","description":"Uploads an attachment (PDF, PNG, JPG, or JPEG) and links it to the specified transaction, a draft or a booked one, as long as it has no attachment yet (409 attachment_exists otherwise, even for an empty attachment). To give an existing empty attachment its file, use POST /v1/attachments/{id}/files. An entry linked to an invoice, or one Numina's bookkeeping agent is working on, returns 409 conflict. A file whose content does not match its declared type returns 400 invalid_file. The attachment is stored and can be viewed in the Numina UI. Numina does not read the file unless run_ocr or run_bookkeeping is \"true\".","tags":["Attachments"],"security":[{"oauth2":["journal:write","documents:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Journal entry ID","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Journal entry ID","name":"id","in":"path"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"File to upload (PDF, PNG, JPG, or JPEG; max 10MB)"},"run_bookkeeping":{"type":"string","enum":["true","false"],"description":"If \"true\", Numina reads the file (OCR) and then runs the AI bookkeeping agent against the linked draft when the company has Numina AI on. The agent may change the draft's lines. What happens next depends on your scopes. Without `journal:book`, the agent only reviews and never books. With `journal:book`, Autopilot can book the draft if Autopilot is on and its conditions are met, including that the draft is fully matched to a bank line. To book a specific entry, use POST /v1/journal-entries/{id}/book. The agent does not run on a booked entry. Defaults to \"false\"."},"run_ocr":{"type":"string","enum":["true","false"],"description":"If \"true\", Numina reads the file (OCR) so its amounts, dates and parties show in the UI and the document becomes searchable. Defaults to \"false\", or \"true\" when run_bookkeeping is \"true\"; \"false\" together with run_bookkeeping \"true\" is rejected."}},"required":["file"]}}}},"responses":{"201":{"description":"Attachment uploaded successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateAttachmentResponse"}}}},"400":{"description":"Bad Request - The upload is missing, too large, or not a valid file","content":{"application/json":{"examples":{"missing_file_1":{"summary":"No file was uploaded","value":{"error":"missing_file","error_description":"No file was uploaded"}},"file_too_large_2":{"summary":"File size must be less than 10MB","value":{"error":"file_too_large","error_description":"File size must be less than 10MB"}},"invalid_file_3":{"summary":"The file's content does not match its type","value":{"error":"invalid_file","error_description":"The file's content does not match its type"}},"upload_error_4":{"summary":"Unexpected field","value":{"error":"upload_error","error_description":"Unexpected field"}},"invalid_request_5":{"summary":"Invalid parameters","value":{"error":"invalid_request","error_description":"Invalid parameters"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The entry already has an attachment (`attachment_exists`), is linked to an invoice, or Numina's bookkeeping agent is working on it (`conflict`; retry in a few minutes)","content":{"application/json":{"examples":{"attachment_exists_1":{"summary":"Transaction already has an attachment. Cannot overwrite existing attachments.","value":{"error":"attachment_exists","error_description":"Transaction already has an attachment. Cannot overwrite existing attachments."}},"conflict_2":{"summary":"This entry is linked to an invoice, which is its document.","value":{"error":"conflict","error_description":"This entry is linked to an invoice, which is its document."}},"conflict_3":{"summary":"Numina's bookkeeping agent is working on this entry. Try again in a few minutes.","value":{"error":"conflict","error_description":"Numina's bookkeeping agent is working on this entry. Try again in a few minutes."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/attachments/{id}/files":{"post":{"summary":"Add files to an empty attachment","description":"Uploads the files of an attachment (bilag) that has none yet, including on booked transactions: documenting a posting does not change it. Send up to 10 files at once; they become the pages of the one attachment. An attachment that already has files is refused with 409 attachment_not_empty, so a retried request never adds the files twice. GET /v1/ledger-entries shows attachment_id and attachment_number per entry. Numina does not read the files unless run_ocr is \"true\".","tags":["Attachments"],"security":[{"oauth2":["documents:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Attachment id: `id` from GET /v1/attachments, or attachment_id from GET /v1/ledger-entries","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Attachment id: `id` from GET /v1/attachments, or attachment_id from GET /v1/ledger-entries","name":"id","in":"path"}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"files":{"type":"array","items":{"type":"string","format":"binary"},"description":"1–10 files (PDF, PNG, JPG or JPEG; max 10MB each), in page order. All files must have the same type, and each file's content must match its declared type."},"run_ocr":{"type":"string","enum":["true","false"],"description":"If \"true\", Numina reads the files (OCR) so the document becomes searchable. Defaults to \"false\"."}},"required":["files"]}}}},"responses":{"201":{"description":"Files added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddAttachmentFilesResponse"}}}},"400":{"description":"Bad Request - The upload is missing, too large, or not a valid file","content":{"application/json":{"examples":{"missing_file_1":{"summary":"No file was uploaded","value":{"error":"missing_file","error_description":"No file was uploaded"}},"file_too_large_2":{"summary":"File size must be less than 10MB","value":{"error":"file_too_large","error_description":"File size must be less than 10MB"}},"invalid_file_3":{"summary":"The file's content does not match its type","value":{"error":"invalid_file","error_description":"The file's content does not match its type"}},"upload_error_4":{"summary":"Unexpected field","value":{"error":"upload_error","error_description":"Unexpected field"}},"invalid_request_5":{"summary":"Invalid parameters","value":{"error":"invalid_request","error_description":"Invalid parameters"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"409":{"description":"Conflict - The attachment already has files","content":{"application/json":{"examples":{"attachment_not_empty_1":{"summary":"The attachment already has files. Only an empty attachment can be given files.","value":{"error":"attachment_not_empty","error_description":"The attachment already has files. Only an empty attachment can be given files."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/attachments":{"get":{"summary":"List documents","description":"Lists the company's documents, newest first. Use `is_empty=true` to find bilag still missing their files, and `attachment_number` to look one up by its bilag number.","tags":["Attachments"],"security":[{"oauth2":["documents:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"integer","minimum":1,"description":"Only bilag with this number. Numbers restart each accounting year, so this can return one per year.","example":185},"required":false,"description":"Only bilag with this number. Numbers restart each accounting year, so this can return one per year.","name":"attachment_number","in":"query"},{"schema":{"type":"string","enum":["true","false"],"description":"Only bilag with or without files"},"required":false,"description":"Only bilag with or without files","name":"is_empty","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100,"description":"Maximum number of results (default 100, max 500)","example":100},"required":false,"description":"Maximum number of results (default 100, max 500)","name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0,"description":"Number of results to skip","example":0},"required":false,"description":"Number of results to skip","name":"offset","in":"query"}],"responses":{"200":{"description":"Documents","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListAttachmentsResponse"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}},"post":{"summary":"Upload document to the inbox","description":"Uploads a document (PDF, PNG, JPG, or JPEG; max 10MB) without a journal entry, so it waits in the inbox. Put it on a draft later with `attachment_id` on PUT /v1/journal-entries/{id}. Numina reads it only with run_ocr \"true\", and the bookkeeping agent does not run on it. Uploading a file the company already has returns the existing document with 200 and `duplicate: true`.","tags":["Attachments"],"security":[{"oauth2":["documents:write"]},{"oauth2":["write:draft"]},{"oauth2":["write:ledger"]},{"apiKey":[]}],"requestBody":{"content":{"multipart/form-data":{"schema":{"type":"object","properties":{"file":{"type":"string","format":"binary","description":"File to upload (PDF, PNG, JPG, or JPEG; max 10MB). Its content must match its declared type."},"run_ocr":{"type":"string","enum":["true","false"],"description":"If \"true\", Numina reads the file (OCR) so the document becomes searchable. Defaults to \"false\"."}},"required":["file"]}}}},"responses":{"200":{"description":"The company already has this file","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxUploadResponse"}}}},"201":{"description":"Uploaded","content":{"application/json":{"schema":{"$ref":"#/components/schemas/InboxUploadResponse"}}}},"400":{"description":"Bad Request - The upload is missing, too large, or not a valid file","content":{"application/json":{"examples":{"missing_file_1":{"summary":"No file was uploaded","value":{"error":"missing_file","error_description":"No file was uploaded"}},"file_too_large_2":{"summary":"File size must be less than 10MB","value":{"error":"file_too_large","error_description":"File size must be less than 10MB"}},"invalid_file_3":{"summary":"The file's content does not match its type","value":{"error":"invalid_file","error_description":"The file's content does not match its type"}},"upload_error_4":{"summary":"Unexpected field","value":{"error":"upload_error","error_description":"Unexpected field"}},"invalid_request_5":{"summary":"Invalid parameters","value":{"error":"invalid_request","error_description":"Invalid parameters"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/attachments/{id}":{"get":{"summary":"Get document","tags":["Attachments"],"security":[{"oauth2":["documents:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Attachment id: `id` from GET /v1/attachments, or attachment_id from GET /v1/ledger-entries","example":"550e8400-e29b-41d4-a716-446655440000"},"required":true,"description":"Attachment id: `id` from GET /v1/attachments, or attachment_id from GET /v1/ledger-entries","name":"id","in":"path"}],"responses":{"200":{"description":"The document","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AttachmentDetail"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/vat-codes":{"get":{"summary":"List VAT codes","description":"Returns all VAT codes for the authenticated company.","tags":["Accounts"],"security":[{"oauth2":["setup:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"responses":{"200":{"description":"List of VAT codes","content":{"application/json":{"schema":{"$ref":"#/components/schemas/GetVatCodesResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/trial-balance":{"get":{"summary":"Get trial balance (saldobalance)","description":"Balance per account at the end of a date; the balances always sum to zero. Balance-sheet accounts are closing balances, including everything carried forward from earlier years (posted primo for closed years, calculated for years not yet closed). P&L accounts are accounting-year-to-date; the current year's result is not yet transferred to equity. Drafts are excluded. Amounts are DKK; debit is positive, credit is negative (so income and liabilities are negative).","tags":["Balances"],"security":[{"oauth2":["reports:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Balances at the end of this date (YYYY-MM-DD). Defaults to today in Denmark (Europe/Copenhagen).","example":"2026-06-30"},"required":false,"description":"Balances at the end of this date (YYYY-MM-DD). Defaults to today in Denmark (Europe/Copenhagen).","name":"date","in":"query"},{"schema":{"type":"string","enum":["true","false"],"description":"Include balance-sheet and P&L accounts whose balance is zero (default: false)"},"required":false,"description":"Include balance-sheet and P&L accounts whose balance is zero (default: false)","name":"include_zero_balances","in":"query"}],"responses":{"200":{"description":"Trial balance","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TrialBalance"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No accounting year contains the date","content":{"application/json":{"examples":{"accounting_year_not_found_1":{"summary":"No accounting year contains to (2026-03-31).","value":{"error":"accounting_year_not_found","error_description":"No accounting year contains to (2026-03-31)."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/account-balances":{"get":{"summary":"Get account movement per period","description":"Net movement per account per calendar month, quarter or year. Opening entries (primo) are not movement and are excluded. Returns both balance-sheet and P&L accounts unless statement is set. With balance-sheet accounts included (the default), from and to must be in the same accounting year; statement=profit_loss may span years. Balance-sheet accounts also have opening (primo) and closing balances: opening_balance + net_change_total = closing_balance. Subtotal rows are not included. Drafts are excluded. Amounts are DKK; debit is positive, credit is negative (so income and liabilities are negative).","tags":["Balances"],"security":[{"oauth2":["reports:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Start of the range (inclusive)","example":"2026-01-01"},"required":true,"description":"Start of the range (inclusive)","name":"from","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"End of the range (inclusive)","example":"2026-03-31"},"required":true,"description":"End of the range (inclusive)","name":"to","in":"query"},{"schema":{"type":"string","enum":["month","quarter","year"],"default":"month","description":"Calendar period size (default: month)"},"required":false,"description":"Calendar period size (default: month)","name":"granularity","in":"query"},{"schema":{"type":"string","enum":["balance_sheet","profit_loss"],"description":"Only accounts on this statement (default: both, balance sheet and P&L)"},"required":false,"description":"Only accounts on this statement (default: both, balance sheet and P&L)","name":"statement","in":"query"},{"schema":{"type":"string","enum":["true","false"],"description":"Include accounts without entries in the range and, for the balance sheet, without a balance (default: false)"},"required":false,"description":"Include accounts without entries in the range and, for the balance sheet, without a balance (default: false)","name":"include_empty_accounts","in":"query"}],"responses":{"200":{"description":"Account movement","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountBalances"}}}},"400":{"description":"Bad Request - Invalid query, or balance-sheet accounts over more than one accounting year","content":{"application/json":{"examples":{"invalid_request_1":{"summary":"Invalid query parameters","value":{"error":"invalid_request","error_description":"Invalid query parameters"}},"range_crosses_accounting_year_2":{"summary":"Balance-sheet balances need from and to in the same accounting year. Split the range at the year start, or set statement=profit_loss for P&L movement across years.","value":{"error":"range_crosses_accounting_year","error_description":"Balance-sheet balances need from and to in the same accounting year. Split the range at the year start, or set statement=profit_loss for P&L movement across years."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - No accounting year contains the date","content":{"application/json":{"examples":{"accounting_year_not_found_1":{"summary":"No accounting year contains to (2026-03-31).","value":{"error":"accounting_year_not_found","error_description":"No accounting year contains to (2026-03-31)."}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/ledger-entries":{"get":{"summary":"List booked ledger entries","description":"The posted entries in a date range, ordered by date and posting number: VAT is its own entry, as in the reports. Entries reversed by a correction or cancellation are excluded; a corrected transaction shows its current entries. Drafts are excluded. Amounts follow the ledger sign: debit positive, credit negative.","tags":["Ledger entries"],"security":[{"oauth2":["journal:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"First entry date (inclusive, YYYY-MM-DD)","example":"2026-01-01"},"required":true,"description":"First entry date (inclusive, YYYY-MM-DD)","name":"from","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","description":"Last entry date (inclusive, YYYY-MM-DD)","example":"2026-12-31"},"required":true,"description":"Last entry date (inclusive, YYYY-MM-DD)","name":"to","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":1000,"default":1000,"description":"Entries per page (default and max: 1000)"},"required":false,"description":"Entries per page (default and max: 1000)","name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0,"description":"Pagination offset; pass next_offset from the last page"},"required":false,"description":"Pagination offset; pass next_offset from the last page","name":"offset","in":"query"}],"responses":{"200":{"description":"A page of booked entries","content":{"application/json":{"schema":{"$ref":"#/components/schemas/LedgerEntries"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/bank-transactions":{"get":{"summary":"List bank lines","description":"Bank lines as the bank reported them, newest first, with the journal entries each is reconciled with.","tags":["Bank"],"security":[{"oauth2":["bank:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid","description":"Only this bank account (from GET /v1/bank-accounts)"},"required":false,"description":"Only this bank account (from GET /v1/bank-accounts)","name":"bank_account_id","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-01-01"},"required":false,"name":"from_date","in":"query"},{"schema":{"type":"string","pattern":"^\\d{4}-\\d{2}-\\d{2}$","example":"2026-12-31"},"required":false,"name":"to_date","in":"query"},{"schema":{"type":"number","nullable":true,"description":"Signed, in the line's currency: negative is money out"},"required":false,"description":"Signed, in the line's currency: negative is money out","name":"amount_min","in":"query"},{"schema":{"type":"number","nullable":true},"required":false,"name":"amount_max","in":"query"},{"schema":{"type":"string","maxLength":200,"description":"Text in the payment reference or counterparty name"},"required":false,"description":"Text in the payment reference or counterparty name","name":"text","in":"query"},{"schema":{"type":"string","enum":["unreconciled","reconciled"],"description":"Only lines in this state. Omit for all."},"required":false,"description":"Only lines in this state. Omit for all.","name":"state","in":"query"},{"schema":{"type":"integer","minimum":1,"maximum":500,"default":100,"description":"Maximum number of results (default 100, max 500)","example":100},"required":false,"description":"Maximum number of results (default 100, max 500)","name":"limit","in":"query"},{"schema":{"type":"integer","nullable":true,"minimum":0,"default":0,"description":"Number of results to skip","example":0},"required":false,"description":"Number of results to skip","name":"offset","in":"query"}],"responses":{"200":{"description":"Bank lines","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ListBankTransactionsResponse"}}}},"400":{"description":"Bad Request - Invalid input or validation failed","content":{"application/json":{"examples":{"validation":{"summary":"Validation error","value":{"error":"validation_error","error_description":"Journal entries do not balance","details":[{"field":"lines","message":"Sum of amounts must equal zero."}]}},"invalidRequest":{"summary":"Invalid request","value":{"error":"invalid_request","error_description":"Invalid request body","details":[{"field":"lines.0.amount","message":"Invalid input: expected number, received string"}]}},"missingReference":{"summary":"A referenced account, VAT code or contact does not exist","value":{"error":"invalid_request","error_description":"One or more referenced resources do not exist","details":[{"field":"account_number","message":"Account number is invalid"}]}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/bank-transactions/{id}":{"get":{"summary":"Get bank line","tags":["Bank"],"security":[{"oauth2":["bank:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"parameters":[{"schema":{"type":"string","format":"uuid"},"required":true,"name":"id","in":"path"}],"responses":{"200":{"description":"The bank line","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BankTransaction"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/me":{"get":{"summary":"Check credentials","description":"Returns the company, scopes and type of these credentials. Needs no scope, so use it to test a key or token.","tags":["Company"],"security":[{"oauth2":[]},{"apiKey":[]}],"responses":{"200":{"description":"The current credentials","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Me"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"404":{"description":"Not Found - The resource does not exist in this company","content":{"application/json":{"example":{"error":"not_found","error_description":"Not found"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/company":{"get":{"summary":"Get company","description":"The company this API key or connection opens.","tags":["Company"],"security":[{"oauth2":["setup:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"responses":{"200":{"description":"The company","content":{"application/json":{"schema":{"$ref":"#/components/schemas/Company"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}},"/v1/accounting-years":{"get":{"summary":"List accounting years","tags":["Company"],"security":[{"oauth2":["setup:read"]},{"oauth2":["read:ledger"]},{"apiKey":[]}],"responses":{"200":{"description":"The company's accounting years","content":{"application/json":{"schema":{"$ref":"#/components/schemas/AccountingYears"}}}},"401":{"description":"Unauthorized - Invalid or missing authentication","content":{"application/json":{"examples":{"missing":{"summary":"No credentials","value":{"error":"unauthorized","error_description":"Missing Authorization header"}},"invalidToken":{"summary":"Invalid or expired OAuth token","value":{"error":"invalid_token","error_description":"The access token is invalid or has been revoked"}},"invalidApiKey":{"summary":"Invalid API key","value":{"error":"invalid_token","error_description":"The API key is invalid or has been revoked"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"403":{"description":"Forbidden - Missing scope, or the company is archived","content":{"application/json":{"examples":{"insufficient_scope_1":{"summary":"The request requires all of the following scopes: write:ledger","value":{"error":"insufficient_scope","error_description":"The request requires all of the following scopes: write:ledger","scope":"write:ledger"}},"company_archived_2":{"summary":"This company is archived","value":{"error":"company_archived","error_description":"This company is archived"}}},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"429":{"description":"Too Many Requests - Rate limit exceeded","content":{"application/json":{"example":{"error":"rate_limit_exceeded","error_description":"Too many requests, please try again later."},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}},"500":{"description":"Internal Server Error","content":{"application/json":{"example":{"error":"internal_error","error_description":"An unexpected error occurred"},"schema":{"$ref":"#/components/schemas/ErrorResponse"}}}}}}}}}