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
Imports
Bulk async ingest: SIE files (Fortnox/Visma/BL/SpeedLedger/Bokio migrations) and bank statements (12 formats).
Endpoints
POST/api/v1/companies/:companyId/imports/bank: Import a bank-file (CSV / XML / CAMT053).POST/api/v1/companies/:companyId/imports/bank/:id/undo: Undo a bank file import: delete the rows it created that are still unbooked.POST/api/v1/companies/:companyId/imports/sie: Import a SIE4 file.POST/api/v1/companies/:companyId/imports/sie/:id/resume: Resume an interrupted SIE import from where it stopped.POST/api/v1/companies/:companyId/imports/sie/:id/undo: Undo an SIE import by batch storno: every entry it posted is reversed, nothing is deleted.POST/api/v1/companies/:companyId/imports/sie/upload: Reserve a direct SIE upload.POST/api/v1/companies/:companyId/imports/skattekonto-file: Import a skattekontoutdrag file (Skatteverket tax account statement) into the skattekonto rows.
POST /api/v1/companies/:companyId/imports/bank
imports.bank · scope transactions:write
Import a bank-file (CSV / XML / CAMT053).
Accepts a bank statement file (UTF-8 / UTF-16 / Windows-1252, up to 10 MB) as multipart/form-data. Auto-detects the bank format (SEB, Swedbank, Handelsbanken, Nordea, Nordea Business, Lansforsakringar, Lunar, ICA Banken, Skandia, Wise transaction history, Wise balance statement, CAMT053, generic CSV) or honors a format override. Parses transactions, ingests them into the transactions table (NOT into journal entries: see BFL note in pitfalls), and emits transaction.synced events. Returns operation_id for polling.
Use when: Importing a bank statement export for a period. Common with PSD2 bank connections that don't auto-sync, or for legacy bank accounts.
Don't use for: SIE bookkeeping import (use /imports/sie). Auto-bank sync (use the enable-banking extension). Single-transaction creation (use POST /transactions/ingest with a 1-element array).
Pitfalls
- File size cap: 10 MB. Larger files require splitting client-side.
formatquery parameter is optional; auto-detection works for all supported banks. Passformatonly to force a specific format. Accepted values: seb, swedbank, handelsbanken, nordea, nordea_business, lansforsakringar, ica_banken, skandia, lunar, northmill, wise, wise_statement, generic_csv, camt053.- Wise transaction-history rows with refunded or unknown statuses, unknown directions, or different source and target currencies are rejected instead of guessed. Import the matching per-currency Wise balance statements.
- Duplicate detection is by external_id (composed from format + date + description + amount + row index, or the camt.053 entry reference / Wise transfer id where the file carries one); a re-import of the same file typically deduplicates rather than creating doubles.
- BFL 5 kap 6-7 §§ note: this endpoint creates
transactionsrows (the underlag for a verifikation), NOT verifikationer themselves. The verifikation content requirements are in BFL 5 kap 6-7 §§; until each transaction is matched to an invoice/supplier-invoice (POST /transactions/{id}/match-*) or categorised (POST /transactions/{id}/categorize), the bookkeeping obligation isn't discharged. A successful import here means the data is ingested: not booked. - A successful import returns operation_id; poll /operations/{id} for the final ingested/duplicates/errors counts.
Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: no
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
format | "nordea" | "nordea_business" | "seb" | "swedbank" | "handelsbanken" | "lansforsakringar" | "ica_banken" | "skandia" | "lunar" | "northmill" | "wise" | "wise_statement" | "generic_csv" | "camt053" | no | Force this bank file format instead of auto-detection. Omit to auto-detect. |
Response fields
| Name | Type |
|---|---|
operation_id | string |
type | "import.bank" |
status | "queued" |
poll_url | string |
Example response
{
"data": {
"operation_id": "op_a8f1…",
"type": "import.bank",
"status": "queued",
"poll_url": "/api/v1/operations/op_a8f1…",
"webhook_event": "operation.completed"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/imports/bank/:id/undo
imports.bank.undo · scope transactions:write
Undo a bank file import: delete the rows it created that are still unbooked.
Hard-deletes every transaction the import created that is still unbooked (ignored rows included) and marks the import undone, so the same file can be imported again. Rows that are booked or linked to a verifikat, and rows with payment match history, are never touched: they are counted in skipped_booked and skipped_match_history, and their verifikat stay as they are (unlink or reverse them separately). Owner/admin only. Idempotent. Dry-runnable (the dry run counts what would be deleted and skipped).
Use when: The wrong file, the wrong account or a duplicate file was imported and its rows should go.
Don't use for: Removing single rows (DELETE /transactions/{id} for manual rows; ignore bank rows), bank-feed rows (they have no import), or SIE imports (POST /imports/sie/{id}/undo).
Pitfalls
- Only a completed import can be undone: 409 BANK_FILE_UNDO_NOT_COMPLETED otherwise.
- Owner or admin only: a member key gets 403 BANK_FILE_UNDO_FORBIDDEN.
- Booked rows survive the undo (skipped_booked > 0): to remove them, reverse their verifikat first, then delete or ignore the rows.
- Imports made before rows were stamped with their import id delete nothing (deleted_transactions 0).
Risk: high · 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 | Description |
|---|---|---|
bank_file_import_id | string | |
deleted_transactions | number | |
skipped_booked | number | Booked or verifikat-linked rows left untouched. |
skipped_match_history | number | Unbooked rows kept because their match history is append-only. |
Example response
{
"data": {
"bank_file_import_id": "9a8b…",
"deleted_transactions": 212,
"skipped_booked": 3,
"skipped_match_history": 0
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/imports/sie
imports.sie · scope bookkeeping:write
Import a SIE4 file.
Accepts a SIE4 file (CP437 / Windows-1252 / UTF-8 auto-detected, up to 50 MB) as the request body, parses it, checks for duplicate imports by file-hash, and replays every #VER + #TRANS into the company's bookkeeping. Returns an operation_id immediately: poll GET /api/v1/operations/{id} for status + final result. The byte-equivalent dashboard route at /api/import/sie/execute backs the same lib helper, so a SIE imported via v1 matches what the dashboard would produce.
Use when: Migrating bookkeeping data from another system (Fortnox, Bokio, Visma) into Accounted, restoring from a backup .se file, or recreating a period from an archive.
Don't use for: Bank transaction CSV/XML imports (use POST /imports/bank). Single-voucher creation (use POST /journal-entries). Importing into a period that already has posted entries: SIE imports run on a fresh period.
Pitfalls
- Body content-type must be multipart/form-data with either a
filefield carrying the .se / .sie / .si file, orstoragePathandfilenamefields from the signed-upload endpoint. - Files up to 50 MB use POST /imports/sie/upload, then upload bytes to Storage and submit storagePath + filename. Inline multipart is limited by the hosting gateway.
- An identical retry returns the same execution. Deliberate replacement requires options.onExistingPeriod=replace and options.supersedesImportId naming the reviewed predecessor, and uses a new batch after storno.
- The operation can take 1-5 minutes for multi-year files. The HTTP response returns immediately with operation_id; poll /operations/{id} every ~2s for status.
- Chunks are visible while importing. Filing and export are held until completion. Undo uses batch storno and retains accounting history.
- Account mappings are generated server-side from the file's #KONTO records (plus stored per-company overrides). By default the file's account names are carried into the chart, renaming existing accounts whose names differ: pass options.updateAccountNames=false to keep BAS default names.
Risk: high · Idempotent: yes · Reversible: yes · Dry-run supported: no
Response fields
| Name | Type |
|---|---|
operation_id | string |
type | "import.sie" |
status | "queued" |
poll_url | string |
Example response
{
"data": {
"operation_id": "7ce97122-264e-49ca-a795-e01dc77425e7",
"type": "import.sie",
"status": "queued",
"poll_url": "/api/v1/operations/7ce97122-264e-49ca-a795-e01dc77425e7"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/imports/sie/:id/resume
imports.sie.resume · scope bookkeeping:write
Resume an interrupted SIE import from where it stopped.
Re-queues a paused or interrupted durable SIE import (or an interrupted undo) so the worker continues from the last committed chunk; entries already posted are not posted again. A finished run (completed, undone, failed) is answered unchanged. Allowed to the user who ran the import and to owners/admins. Idempotent. Dry-runnable.
Use when: An SIE import stopped part-way (state paused, or running with no progress) and should continue.
Don't use for: Starting a new import (POST /imports/sie) or cancelling one (POST /imports/sie/{id}/undo).
Pitfalls
- Another user's import needs an owner or admin key: 403 FORBIDDEN otherwise.
- Legacy imports made before durable jobs return 409 SIE_IMPORT_LEGACY_REVIEW_REQUIRED.
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 | Description |
|---|---|---|
import_id | string | |
action | "undo" | "resume" | |
state | string | The job state after the request, e.g. undoing, running, undone, completed. |
phase | string | null | |
accepted | true |
Example response
{
"data": {
"import_id": "7ce9…",
"action": "resume",
"state": "running",
"phase": "vouchers",
"accepted": true
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/imports/sie/:id/undo
imports.sie.undo · scope bookkeeping:write
Undo an SIE import by batch storno: every entry it posted is reversed, nothing is deleted.
Queues a batch storno of the import: each verifikat the import posted gets a reversing entry (BFL 5 kap 5 §) and the originals stay in the ledger. Asynchronous: the answer carries state "undoing" and the worker finishes in the background (state "undone"). Refused while another SIE run is active, while a reversal of an imported voucher is already in progress, while an imported voucher has a live correction, or when the period is closed or locked. Owner/admin only. Idempotent. Dry-runnable (the dry run counts the entries that would be reversed).
Use when: An SIE file was imported into the wrong company or year, or with a wrong mapping, and its entries must be cancelled before a corrected import.
Don't use for: Removing single vouchers (POST /journal-entries/{id}/reverse), bank file imports (POST /imports/bank/{id}/undo), or legacy imports made before durable jobs (409 SIE_IMPORT_LEGACY_REVIEW_REQUIRED: review them in the app).
Pitfalls
- Nothing is deleted: the ledger keeps both the imported entries and their reversals, and voucher numbers are never reused.
- A closed or locked period, another active run or a live correction returns 409 SIE_IMPORT_ACTION_CONFLICT with details.reason.
- Owner or admin only: a member key gets 403 FORBIDDEN.
- Undoing an already undone import answers its state unchanged.
Risk: high · 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 | Description |
|---|---|---|
import_id | string | |
action | "undo" | "resume" | |
state | string | The job state after the request, e.g. undoing, running, undone, completed. |
phase | string | null | |
accepted | true |
Example response
{
"data": {
"import_id": "7ce9…",
"action": "undo",
"state": "undoing",
"phase": "undo",
"accepted": true
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/imports/sie/upload
imports.sie.upload · scope bookkeeping:write
Reserve a direct SIE upload.
Upload exact bytes to uploadUrl with PUT, then submit storagePath and filename to POST /imports/sie. The upload URL expires after two hours.
Use when: Importing files larger than the function request limit.
Don't use for: Booking a voucher: this only reserves storage.
Pitfalls
- Use the returned URL once with PUT and application/octet-stream. Submit the returned storagePath after upload completes.
Risk: low · Idempotent: no · Reversible: yes · Dry-run supported: no
Request body
| Name | Type | Required |
|---|---|---|
filename | string | yes |
size | number | yes |
Response fields
| Name | Type |
|---|---|
storagePath | string |
uploadUrl | string |
filename | string |
Example request
{
"filename": "export.se",
"size": 10485760
}
Example response
{
"data": {
"storagePath": "company/sie-intake/upload.se",
"uploadUrl": "https://storage.example/upload",
"filename": "export.se"
}
}
POST /api/v1/companies/:companyId/imports/skattekonto-file
imports.skattekonto-file · scope transactions:write
Import a skattekontoutdrag file (Skatteverket tax account statement) into the skattekonto rows.
Parses the statement (the CSV export or legacy .skv from Skatteverket's Skattekonto e-tjänst, sent as base64), deduplicates it server-side against the skattekonto rows already stored, inserts the new events (source file_import), promotes upcoming rows the statement proves settled and skips duplicates. Books nothing: the rows are booked afterwards through the skattekonto rules, like synced rows. A file already imported is refused; a file naming another organisation number or not summing is refused unless confirmed. Idempotent. Dry-runnable: the dry run parses and counts and writes nothing.
Use when: The company has no Skatteverket connection (self-hosted, or not yet connected) and the skattekonto should be reconciled and booked from the statement file.
Don't use for: Companies with a Skatteverket connection (the hourly sync fetches the same events), bank statements (POST /imports/bank), or booking the rows (the skattekonto booking tools).
Pitfalls
- Send the file bytes base64-encoded in content_base64, up to about 3 MB of file; the filename matters for legacy .skv detection.
- A file already imported answers 409 SKATTEKONTO_FILE_DUPLICATE with details.import_id.
- A header organisation number that is not the company's answers 409 SKATTEKONTO_FILE_ORG_NUMBER_MISMATCH: check the file, then resend with confirm_org_number_mismatch=true.
- A statement whose saldo markers do not sum (filtered, truncated or edited) answers 409 SKATTEKONTO_FILE_SUM_MISMATCH: resend with confirm_sum_mismatch=true only if the gap is understood.
- A file that is not a skattekontoutdrag answers 400 SKATTEKONTO_FILE_NOT_RECOGNIZED: a bank CSV is never accepted here.
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 | Description |
|---|---|---|---|
filename | string | yes | The file name as downloaded, e.g. "Kontoutdrag 556677-8899 2026-05-03--2026-08-01.csv". |
content_base64 | string | yes | The file's bytes, base64-encoded (any encoding Skatteverket exports; decoded server-side). |
confirm_org_number_mismatch | boolean | no | Import even though the file names another organisation number than the company's. |
confirm_sum_mismatch | boolean | no | Import even though opening saldo plus the events does not equal the closing saldo. |
Response fields
| Name | Type | Description |
|---|---|---|
import_id | string | |
imported | number | New skattekonto rows stored. |
duplicates | number | Events already stored, skipped. |
promoted | number | Upcoming rows the statement proves settled, flipped to booked status. |
errors | number | Rows that could not be written. |
date_from | string | |
date_to | string | |
closing_saldo | number | null | |
file_hash | string | |
variant | "csv" | "skv" | |
row_count | number |
Example request
{
"filename": "Kontoutdrag 556677-8899 2026-05-03--2026-08-01.csv",
"content_base64": "U2thdHRla29udG8…"
}
Example response
{
"data": {
"import_id": "1f0c…",
"imported": 14,
"duplicates": 2,
"promoted": 1,
"errors": 0,
"date_from": "2026-05-03",
"date_to": "2026-08-01",
"closing_saldo": 23490,
"file_hash": "9a1b…",
"variant": "csv",
"row_count": 17
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}