{"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.** Send `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.** Debit 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 `write:ledger`. Each call is your own approval: Numina does not review the entry, and booking is final.\n\n**Pagination.** List 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.** Errors 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.** Counted per client IP over 15 minutes, separately for each group. Responses carry `RateLimit-*` headers, and going over returns 429. Write to support@numina.app if you need more.\n\n- Reads: 100.\n- Writes: 50. This covers creating, changing and deleting journal entries, and all writes to contacts, products and invoices, including booking and sending an invoice.\n- Uploads: 300, shared by `POST /v1/journal-entries/{id}/attachments`, `POST /v1/attachments` and `POST /v1/attachments/{id}/files`.\n- Booking a journal entry: 300.\n\n`POST /v1/journal-entries` is what limits the flow above: about 50 entries per 15 minutes per IP. Putting an inbox document on a draft with PUT is a second write, which makes it about 25.\n\n**Retries.** `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 lines and 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). Developer apps are not self-serve yet: write 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 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","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"]},"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. Credit notes and offers are created in Numina; this API only creates invoices.","example":"invoice"},"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 has an outstanding balance","example":false},"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_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_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"},"created_at":{"type":"string","nullable":true,"description":"Creation timestamp (ISO 8601, UTC)","example":"2026-01-15T10:30:00Z"}},"required":["id","invoice_number","type","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","items","memo","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":"Account number for the sales revenue account (from GET /v1/accounts)","example":1000},"vat_code":{"type":"string","description":"VAT code (e.g. 'U25'). Omit for 0% VAT. The VAT rate is derived from the code. Use GET /v1/vat-codes to list available codes.","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}},"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"},"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","sales_account_number","items"]},"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"}}},"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"},"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","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"},"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"},"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"}}},"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":"Invoice payment from customer"},"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)"},"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":"Invoice payment from customer"},"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/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. VAT rate is automatically derived from the specified vat_code.\n\n**book: true** 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`, and a date in a closed accounting year or before the first accounting date gets 400, both before anything is written.\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\n**Idempotency:** send 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"}}}}}}},"/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. 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}/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\n**Idempotency:** send 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/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/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\n**Idempotency:** send 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. Take the id from `bank_account_id` on a bank line; there is no endpoint that lists bank accounts."},"required":false,"description":"Only this bank account. Take the id from `bank_account_id` on a bank line; there is no endpoint that lists 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"}}}}}}}}}