Menu
Cookbooks
API reference
- Overview
- Companies
- Customers
- Invoices
- Articles
- Suppliers
- Supplier invoices
- Supplier payment files
- Expense claims
- Transactions
- Reconciliation
- Bank accounts
- Journal entries
- Voucher gap explanations
- Fiscal periods
- Accounts
- Fixed assets
- Documents
- Inbox items
- Dimensions
- Employees
- Salary runs
- Reports
- Imports
- Compliance check
- Skatteverket
- Peppol
- Webhooks
- Operations
- Health
Documents
Underlag: multipart upload, list and read metadata, signed-URL download (15-min TTL), link to journal entries, delete an unlinked document.
Endpoints
GET/api/v1/companies/:companyId/documents: List documents in the archive, linked or not, newest upload first.GET/api/v1/companies/:companyId/documents/:id: Read one document's metadata and what holds it.GET/api/v1/companies/:companyId/documents/:id/download: Get a time-limited signed download URL for a document.POST/api/v1/companies/:companyId/documents: Upload a document to the WORM archive.POST/api/v1/companies/:companyId/documents/:id/link: Link a document to a journal entry.DELETE/api/v1/companies/:companyId/documents/:id: Delete a document that is not linked to any verifikat.
GET /api/v1/companies/:companyId/documents
documents.list · scope documents:read
List documents in the archive, linked or not, newest upload first.
Returns document metadata (file name, type, size, SHA-256, version, upload source and time) and whether each is the underlag of a verifikat (linked, journal_entry_id). Filter by linked, journal_entry_id or upload date range (uploaded_from/uploaded_to, YYYY-MM-DD, UTC). Current versions only unless current_only=false. Cursor pagination: pass next_cursor back as cursor; null on the last page. Never returns file bytes or extracted text.
Use when: You need the documents not yet attached to anything (linked=false) before matching receipts, the documents of one verifikat, or an inventory for a period.
Don't use for: Downloading a file (GET /documents/{id}/download), reading its text (the Arkiv tools), or the inbox work queue with its extracted totals (GET /inbox-items).
Pitfalls
- linked=false still includes documents pinned to a bank transaction or held by an inbox item: GET /documents/{id} shows what holds one.
- Dates filter the upload time in UTC, not the invoice date on the document.
- The page is in data.documents with data.next_cursor; a cursor that no longer decodes starts from the first page.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
linked | "true" | "false" | no | true = only documents linked to a verifikat, false = only unlinked ones. Default: both. |
journal_entry_id | string | no | Only the documents of this verifikat. |
uploaded_from | string | no | Uploaded on or after this date (UTC). |
uploaded_to | string | no | Uploaded on or before this date (UTC). |
current_only | "true" | "false" | no | false includes superseded versions. Default true. |
cursor | string | no | next_cursor from the previous page. Omit for the first page. |
limit | number | no | Page size, 1-100 (default 50). |
Response fields
| Name | Type |
|---|---|
documents | object[] |
next_cursor | string | null |
Example response
{
"data": {
"documents": [
{
"document_id": "4f1c…",
"file_name": "kvitto-clas-ohlson.pdf",
"mime_type": "application/pdf",
"file_size_bytes": 48213,
"sha256_hash": "9b2e…",
"version": 1,
"is_current_version": true,
"upload_source": "email",
"linked": false,
"journal_entry_id": null,
"journal_entry_line_id": null,
"created_at": "2026-09-02T07:41:10Z"
}
],
"next_cursor": null
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
GET /api/v1/companies/:companyId/documents/:id
documents.get · scope documents:read
Read one document's metadata and what holds it.
Returns the document's metadata, its version chain (original_id, superseded_by_id), the verifikat it is underlag for, the bank transactions it is pinned to (transaction_ids) and the inbox item it arrived through (inbox_item_id). Metadata only: the file is GET /documents/{id}/download.
Use when: You hold a document_id and need to know whether it can be deleted, detached or linked before acting.
Don't use for: Fetching the file (GET /documents/{id}/download) or listing documents (GET /documents).
Pitfalls
- An id from another company answers 404 DOC_NOT_FOUND.
- linked=true means räkenskapsinformation: it can never be deleted, only superseded by a new version.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Response fields
| Name | Type | Description |
|---|---|---|
document_id | string | |
file_name | string | |
mime_type | string | null | |
file_size_bytes | number | null | |
sha256_hash | string | SHA-256 of the stored bytes (the WORM integrity anchor). |
version | number | |
is_current_version | boolean | |
upload_source | string | null | file_upload, camera, email, e_invoice, scan, api or system. |
linked | boolean | True when the document is the underlag of a verifikat (journal_entry_id set). |
journal_entry_id | string | null | |
journal_entry_line_id | string | null | |
created_at | string | Upload time. |
original_id | string | null | |
superseded_by_id | string | null | |
digitization_date | string | null | |
transaction_ids | string[] | Bank transactions this document is pinned to. |
inbox_item_id | string | null | The inbox item the document arrived through, if any. |
Example response
{
"data": {
"document_id": "4f1c…",
"file_name": "kvitto-clas-ohlson.pdf",
"mime_type": "application/pdf",
"file_size_bytes": 48213,
"sha256_hash": "9b2e…",
"version": 1,
"is_current_version": true,
"upload_source": "email",
"linked": false,
"journal_entry_id": null,
"journal_entry_line_id": null,
"created_at": "2026-09-02T07:41:10Z",
"original_id": null,
"superseded_by_id": null,
"digitization_date": null,
"transaction_ids": [],
"inbox_item_id": "1b2c…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
GET /api/v1/companies/:companyId/documents/:id/download
documents.download · scope documents:read
Get a time-limited signed download URL for a document.
Returns a Supabase Storage signed URL valid for 15 minutes. The URL itself is the canonical download: fetch it with any HTTP client; no API key needed on the storage host. Verify file integrity client-side against the returned sha256_hash if your workflow requires it.
Use when: You need the bytes of an archived document (e.g. for OCR, attachment to an email, regulatory export). Always re-fetch the URL before each download: old URLs expire.
Don't use for: Persisting the URL anywhere: it expires. Storing the URL in a webhook payload or audit log makes the audit trail dependent on URL state.
Pitfalls
- The signed URL expires after 15 minutes. Don't cache it beyond the immediate transaction.
- The URL leaks the Supabase Storage origin; this is benign (the signature alone authorizes the read) but rate-limit any forwarding so you don't reveal the storage layout to untrusted callers.
- Each call emits a document.accessed event. Polling this endpoint produces audit noise; cache the URL for its full TTL.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Response fields
| Name | Type |
|---|---|
id | string |
file_name | string |
mime_type | string | null |
sha256_hash | string |
is_current_version | boolean |
download_url | string |
expires_in_seconds | number |
Example response
{
"data": {
"id": "0e9c…",
"file_name": "kvitto-2026-05-12.pdf",
"mime_type": "application/pdf",
"sha256_hash": "8a7f…",
"download_url": "https://…supabase.co/storage/v1/object/sign/…",
"expires_in_seconds": 900
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/documents
documents.upload · scope documents:write
Upload a document to the WORM archive.
Multipart upload of a document (PDF, image or Office file) under the BFL 7 kap retention regime. The bytes are hashed (SHA-256), written to Supabase Storage, and recorded in document_attachments at version=1. Allowed MIME types: application/pdf, image/jpeg, image/png, image/webp, image/heic, image/heif, application/vnd.openxmlformats-officedocument.wordprocessingml.document, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.openxmlformats-officedocument.presentationml.presentation, application/msword, application/vnd.ms-excel, application/vnd.ms-powerpoint, application/vnd.oasis.opendocument.text, application/vnd.oasis.opendocument.spreadsheet, application/vnd.oasis.opendocument.presentation, application/rtf, text/rtf, text/csv. Max size: 10 MB.
Use when: You have a receipt, invoice scan, or supporting document for a posted verifikation and want it archived for the 7-year BFL retention period. Optionally link to a journal entry at upload time via journal_entry_id.
Don't use for: Updating an existing document (no v1 update endpoint; new versions go through the dashboard). Bulk uploads: call once per file.
Pitfalls
- Idempotency-Key is mandatory; multipart retries with the same key replay the cached response.
- Max size 10 MB enforced server-side: DOC_UPLOAD_TOO_LARGE on overrun.
- Only application/pdf / image/jpeg / image/png / image/webp / image/heic / image/heif / application/vnd.openxmlformats-officedocument.wordprocessingml.document / application/vnd.openxmlformats-officedocument.spreadsheetml.sheet / application/vnd.openxmlformats-officedocument.presentationml.presentation / application/msword / application/vnd.ms-excel / application/vnd.ms-powerpoint / application/vnd.oasis.opendocument.text / application/vnd.oasis.opendocument.spreadsheet / application/vnd.oasis.opendocument.presentation / application/rtf / text/rtf / text/csv accepted: DOC_UPLOAD_UNSUPPORTED_TYPE otherwise.
- WORM: once linked to a posted journal entry, the document row cannot be modified or deleted (DB trigger). Upload-then-link is reversible (the document exists with journal_entry_id=null until linked); once linked, treat as immutable.
- Dry-run is not supported on this endpoint: the engine hashes + stores + inserts in one atomic flow.
Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: no
Request body
| Name | Type | Required |
|---|---|---|
file | unknown | no |
upload_source | "file_upload" | "camera" | "email" | "api" | no |
journal_entry_id | string | no |
journal_entry_line_id | string | no |
Response fields
| Name | Type |
|---|---|
id | string |
file_name | string |
mime_type | string | null |
file_size_bytes | number |
sha256_hash | string |
version | number |
is_current_version | boolean |
upload_source | string | null |
journal_entry_id | string | null |
journal_entry_line_id | string | null |
created_at | string |
Example request
{
"file": "<binary>",
"upload_source": "api",
"journal_entry_id": "a8f1…"
}
Example response
{
"data": {
"id": "0e9c…",
"file_name": "kvitto-2026-05-12.pdf",
"mime_type": "application/pdf",
"file_size_bytes": 184320,
"sha256_hash": "8a7f…",
"version": 1,
"is_current_version": true,
"journal_entry_id": "a8f1…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/documents/:id/link
documents.link · scope documents:write
Link a document to a journal entry.
Sets journal_entry_id (and optionally journal_entry_line_id) on an existing document. Optionally stamps the originating invoice_inbox_items row as consumed via inbox_item_id. Use this after /documents upload when the link target was unknown at upload time, or to re-link a stray document. Once the target JE is posted, the document row is effectively immutable per BFL 7 kap retention.
Use when: A document was uploaded without a journal_entry_id (e.g. bulk import) and you now want to attach it to a posted verifikation. Pass inbox_item_id when the document came from the invoice inbox so the item is marked resolved in one call.
Don't use for: Unlinking: no v1 unlink endpoint. The dashboard exposes a manual override; v1 keeps the WORM contract by refusing to revert posted-JE links.
Pitfalls
- Idempotency-Key is mandatory.
- Both the document and the journal_entry_id must belong to the caller's company. NOT_FOUND on mismatch (enumeration hardening).
- Re-linking an already-linked document overwrites the previous journal_entry_id: confirm the old target is what you intend to break.
- inbox_item_id stamping is best-effort: if the stamp fails the document link still succeeds. Use POST /api/v1/companies/:companyId/inbox-items/:id/stamp to stamp independently.
Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: yes
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
dry_run | string | no | true (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits. |
Request body
| Name | Type | Required |
|---|---|---|
journal_entry_id | string | yes |
journal_entry_line_id | string | no |
inbox_item_id | string | no |
Response fields
| Name | Type |
|---|---|
id | string |
journal_entry_id | string |
journal_entry_line_id | string | null |
file_name | string |
Example request
{
"journal_entry_id": "a8f1…"
}
Example response
{
"data": {
"id": "0e9c…",
"journal_entry_id": "a8f1…",
"journal_entry_line_id": null,
"file_name": "kvitto-2026-05-12.pdf"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
DELETE /api/v1/companies/:companyId/documents/:id
documents.delete · scope documents:write
Delete a document that is not linked to any verifikat.
Removes the document row and its stored file. Refused once the document is linked to a journal entry: it is then räkenskapsinformation under BFL 7 kap 2 § and must be kept for 7 years (correct it with a new version instead). The database trigger enforces the same rule. Idempotent. Dry-runnable.
Use when: A duplicate, blank or wrong upload that no verifikat references should go.
Don't use for: Taking a document off a bank transaction (POST /transactions/{id}/detach-document), discarding an inbox item (DELETE /inbox-items/{id}) or anything linked to a verifikat.
Pitfalls
- A linked document returns 409 DOC_DELETE_LINKED, whatever the verifikat's status.
- The file is removed from storage too: this cannot be undone.
- A document pinned to an unbooked bank transaction is not protected by this rule: detach it first if the transaction still needs it.
Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: yes
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
dry_run | string | no | true (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits. |
Response fields
| Name | Type |
|---|---|
document_id | string |
deleted | true |
Example response
{
"data": {
"document_id": "4f1c…",
"deleted": true
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}