Menu
Cookbooks
API reference
- Overview
- Companies
- Capabilities
- Measures
- Packages
- Unexplained
- Liquidity forecast
- Portfolio
- Customers
- Invoices
- Collections
- Articles
- Set-offs
- Suppliers
- Supplier invoices
- Supplier payment files
- Bank payments
- Expense claims
- Transactions
- Reconciliation
- Bank accounts
- POS sales
- Webshop orders
- Journal entries
- Voucher gap explanations
- Fiscal periods
- Accounts
- Accrual schedules
- Agreements and commitments
- Tax loss carry-forwards
- Fixed assets
- Documents
- Signing requests
- Company registers
- Capital events
- Owner tax
- Inbox items
- Dimensions
- Employees
- Salary runs
- Reports
- Budgets
- Imports
- Compliance check
- Skatteverket
- Peppol
- Webhooks
- Operations
- Pending operations
- Health
- API key
- Service accounts
Budgets
Named budget versions per fiscal year with one default, amounts per resultatkonto (3000-8999), month and dimension tag in the SIE sign (revenue negative), saved all or nothing, filled from last year's actuals or spread over the months. Budget against actual is on the reports page.
Endpoints
GET/api/v1/companies/:companyId/budgets: List the budgets (named versions per fiscal year).GET/api/v1/companies/:companyId/budgets/:id: Read one budget and its amounts per account, month and dimension.POST/api/v1/companies/:companyId/budgets: Create a budget for a fiscal year.POST/api/v1/companies/:companyId/budgets/:id/copy-actuals: Fill a budget with last year's booked result, optionally scaled.POST/api/v1/companies/:companyId/budgets/:id/import: Import a budget file (SIE #PBUDGET, Excel or CSV) into a budget.POST/api/v1/companies/:companyId/budgets/:id/lines: Save amounts in a budget (account, month, dimension cells).POST/api/v1/companies/:companyId/budgets/:id/spread: Spread a yearly amount over the months of a budget.PATCH/api/v1/companies/:companyId/budgets/:id: Rename a budget, make it the default, or archive or restore it.DELETE/api/v1/companies/:companyId/budgets/:id: Delete a budget and its amounts.
GET /api/v1/companies/:companyId/budgets
budgets.list · scope reports:read
List the budgets (named versions per fiscal year).
Returns the company's budgets, oldest first: each is one named version of a fiscal year's budget, and at most one per year is the default that reports read when no budget is named. Archived budgets are left out unless include_archived is true.
Use when: Before reading or changing a budget, to find its budget_id, or to see which version of a year is the default.
Don't use for: The amounts themselves (GET /budgets/{id}) or the comparison with the books (GET /reports/budget-vs-actual).
Pitfalls
- A company without budgets gets an empty list.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
fiscal_period_id | string | no | The fiscal year (fiscal_period_id). |
include_archived | boolean | "true" | "false" | no | Also list archived budgets. Default false. |
Response fields
| Name | Type |
|---|---|
budgets[].budget_id | string |
budgets[].fiscal_period_id | string |
budgets[].name | string |
budgets[].is_default | boolean |
budgets[].archived | boolean |
budgets[].archived_at | string | null |
budgets[].created_at | string |
budgets[].updated_at | string |
Example response
{
"data": {
"budgets": [
{
"budget_id": "7c1d…",
"fiscal_period_id": "a8f1…",
"name": "Budget 2027",
"is_default": true,
"archived": false,
"archived_at": null,
"created_at": "2026-11-02T09:00:00Z",
"updated_at": "2026-11-02T09:00:00Z"
}
]
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
GET /api/v1/companies/:companyId/budgets/:id
budgets.get · scope reports:read
Read one budget and its amounts per account, month and dimension.
Returns the budget and its lines, ordered by account and month, paged (limit, offset; has_more and total_lines say whether more follow). A line is one amount for one resultatkonto, one month and one dimension tag ({} untagged).
Use when: To read or check the amounts of a budget, e.g. before changing them.
Don't use for: Budget against booked result (GET /reports/budget-vs-actual).
Pitfalls
- Amounts are in the SIE sign, debit positive: a revenue budget on class 3 is NEGATIVE (-1200000 for 1,2 Mkr sales), a cost budget positive.
- A month with no line has no budget (0).
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Query parameters
| Name | Type | Required | Description |
|---|---|---|---|
account_number | string | no | A resultatkonto, 3000-8999, as a STRING of 4 digits, never a number. |
limit | number | no | Lines per page, 1-1000 (default 1000). |
offset | number | no | Lines to skip (default 0). |
Response fields
| Name | Type |
|---|---|
budget | object |
lines | object[] |
total_lines | number |
has_more | boolean |
Example response
{
"data": {
"budget": {
"budget_id": "7c1d…",
"fiscal_period_id": "a8f1…",
"name": "Budget 2027",
"is_default": true,
"archived": false,
"archived_at": null,
"created_at": "2026-11-02T09:00:00Z",
"updated_at": "2026-11-02T09:00:00Z"
},
"lines": [
{
"budget_line_id": "0b3e…",
"account_number": "3010",
"month": "2027-01",
"dimensions": {},
"amount": -100000
}
],
"total_lines": 1,
"has_more": false
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/budgets
budgets.create · scope bookkeeping:write
Create a budget for a fiscal year.
Creates an empty, named budget version for one fiscal year. The name is unique within the year. The first budget of a year becomes its default unless is_default is false; is_default true on a later one moves the default to it. Fill it with POST /budgets/{id}/lines, /copy-actuals or /spread. Dry-runnable.
Use when: Starting a year's budget, or a second version of it (a forecast, a scenario).
Don't use for: Changing an existing budget (PATCH /budgets/{id}).
Pitfalls
- A name the year already has (case ignored) answers 409 BUDGET_NAME_TAKEN.
- fiscal_period_id must be one of the company's fiscal years: 404 PERIOD_NOT_FOUND.
Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes · Stage for approval: yes (?stage=true)
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. |
stage | string | no | true stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
fiscal_period_id | string | yes | The fiscal year (fiscal_period_id). |
name | string | yes | The budget's name, unique in its fiscal year ("Budget 2027", "Prognos 2"). |
is_default | boolean | no | Make it the year's default budget, the one reports read. Default: true when the year has no default yet. |
Response fields
| Name | Type |
|---|---|
budget | object |
Example request
{
"fiscal_period_id": "a8f1…",
"name": "Budget 2027"
}
Example response
{
"data": {
"budget": {
"budget_id": "7c1d…",
"fiscal_period_id": "a8f1…",
"name": "Budget 2027",
"is_default": true,
"archived": false,
"archived_at": null,
"created_at": "2026-11-02T09:00:00Z",
"updated_at": "2026-11-02T09:00:00Z"
}
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/budgets/:id/copy-actuals
budgets.copy-actuals · scope bookkeeping:write
Fill a budget with last year's booked result, optionally scaled.
Reads the booked result per resultatkonto and month of the source year (default: the year before the budget's year), with the resultatrapport's exclusions (no bokslut entries), and writes it into the budget month by month: March to March, scaled by percent_change. Each copied account gets every month of the year, so copying again replaces the earlier copy; accounts without actuals are left alone. With dimensions, only the tagged actuals are copied, into lines with the same tag. Dry-runnable.
Use when: Starting a budget from last year's figures, e.g. "last year plus 5 %".
Don't use for: A yearly amount for one account (POST /spread).
Pitfalls
- Without a previous fiscal year, name one with source_fiscal_period_id: 404 BUDGET_SOURCE_YEAR_NOT_FOUND.
- Months are matched by calendar month; a source year that holds a month twice (18 months) gives its later one.
Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes · Stage for approval: yes (?stage=true)
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. |
stage | string | no | true stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
source_fiscal_period_id | string | no | The fiscal year (fiscal_period_id). |
percent_change | number | no | Scale every month by this percentage (5 = +5 %). Default 0. |
dimensions | object | no | Copy only the actuals tagged with this bag, into lines tagged the same. |
account_numbers | string[] | no | Only these accounts. Default: every resultatkonto with actuals. |
Response fields
| Name | Type |
|---|---|
budget_id | string |
inserted | number |
updated | number |
deleted | number |
unchanged | number |
source_fiscal_period_id | string |
accounts | string[] |
Example request
{
"percent_change": 5
}
Example response
{
"data": {
"budget_id": "7c1d…",
"inserted": 24,
"updated": 0,
"deleted": 0,
"unchanged": 0,
"source_fiscal_period_id": "5c2e…",
"accounts": [
"3010",
"5010"
]
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/budgets/:id/import
budgets.import · scope bookkeeping:write
Import a budget file (SIE #PBUDGET, Excel or CSV) into a budget.
Reads a budget file into one budget, all cells or none. A SIE file contributes its #PBUDGET rows (the month's budget per account and object, SIE sign); the rows for months of the budget's fiscal year are read and the file's other years are counted as skipped. A spreadsheet (xlsx, xls, csv, ods) is read in one of two layouts found from its header row: long (konto, månad as YYYY-MM, belopp, optionally dim and kod, or a projekt or kostnadsställe column) or wide (konto and one column per month, headed 2027-01 or jan). Accounts go through the same account mapping as the SIE ledger import, against the company's chart of accounts; an account the chart lacks, a balance account or an object the dimension registry lacks is skipped and named in details.skipped, never guessed. Dry-runnable: the dry run is the preview (what would be read, what is skipped and why, the budget per class and the result).
Use when: Moving a budget from Fortnox (its SIE export carries #PBUDGET) or from a spreadsheet, before following it up with GET /reports/budget-vs-actual.
Don't use for: A few cells (POST /budgets/{id}/lines), or the bookkeeping itself (POST /imports/sie).
Pitfalls
- Send the file bytes base64-encoded in content_base64, up to about 3 MB of file; the filename's extension decides SIE or spreadsheet.
- A spreadsheet's sign is read from its class 3 rows unless sign is sent: revenue positive means revenue_positive. sign_suspect true in the answer means revenue reads as a cost or costs as revenue: check the sign before saving.
- replace true removes the budget's cells the file leaves out; without it the file is written over the budget and other cells stay.
- Nothing for the budget's year answers 400 BUDGET_IMPORT_NOTHING_TO_IMPORT with the skipped lists in details.
Risk: low · Idempotent: yes · Reversible: yes · 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 |
|---|---|---|---|
sign | "debit_positive" | "revenue_positive" | no | debit_positive: the SIE sign (revenue negative). revenue_positive: as the resultatrapport (revenue positive, costs negative). Default: read from the file (revenue positive on class 3 means revenue_positive). Ignored for a SIE file. |
replace | boolean | no | true: the budget's amounts become the file's, and cells the file leaves out are removed. Default false: the file's cells are written over the budget and other cells stay. |
filename | string | yes | The file name with its extension, e.g. "budget-2027.xlsx" or "fortnox.se". |
content_base64 | string | yes | The file's bytes, base64-encoded. |
Response fields
| Name | Type |
|---|---|
budget_id | string |
inserted | number |
updated | number |
deleted | number |
unchanged | number |
format | "sie" | "long" | "wide" |
sign | "debit_positive" | "revenue_positive" |
detected_sign | "debit_positive" | "revenue_positive" | null |
replace | boolean |
rows_read | number |
cells | number |
accounts | number |
mapped_accounts | object[] |
skipped | object |
file_years | object[] |
class_totals | object[] |
result | number |
sign_suspect | boolean |
Example request
{
"filename": "budget-2027.xlsx",
"content_base64": "UEsDBBQ…",
"replace": true
}
Example response
{
"data": {
"budget_id": "7c1d…",
"inserted": 240,
"updated": 0,
"deleted": 0,
"unchanged": 0,
"format": "wide",
"sign": "revenue_positive",
"detected_sign": "revenue_positive",
"replace": true,
"rows_read": 240,
"cells": 240,
"accounts": 20,
"mapped_accounts": [],
"skipped": {
"accounts_not_in_chart": [],
"balance_accounts": [],
"unknown_objects": [],
"rows": 0,
"months_outside_year": 0,
"unreadable": [],
"unreadable_count": 0
},
"file_years": [],
"class_totals": [
{
"account_class": 3,
"amount": 1200000
}
],
"result": 150000,
"sign_suspect": false
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/budgets/:id/lines
budgets.lines.upsert · scope bookkeeping:write
Save amounts in a budget (account, month, dimension cells).
Writes each cell: one resultatkonto (3000-8999), one month (YYYY-MM) and one dimension tag ({} or omitted for untagged). A cell that exists gets the new amount, a new one is added, and amount 0 removes the cell. Every cell is saved or none is. The answer counts inserted, updated, deleted and unchanged cells. Dry-runnable.
Use when: Entering or adjusting budget amounts by hand, or loading a budget prepared elsewhere.
Don't use for: Last year's figures (POST /copy-actuals) or a yearly amount spread over the months (POST /spread).
Pitfalls
- Amounts are in the SIE sign, debit positive: a revenue budget on class 3 is NEGATIVE (-1200000 for 1,2 Mkr sales), a cost budget positive.
- The account must be in the chart of accounts: 404 BUDGET_ACCOUNT_NOT_FOUND lists the missing ones.
- Months must lie in the budget's fiscal year: 400 BUDGET_MONTH_OUTSIDE_YEAR.
- One cell once per call: 400 BUDGET_LINES_DUPLICATE.
- An archived budget is read-only: 409 BUDGET_ARCHIVED.
Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes · Stage for approval: yes (?stage=true)
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. |
stage | string | no | true stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
lines | object[] | yes | The cells to save; every cell is written or none is. |
Response fields
| Name | Type |
|---|---|
budget_id | string |
inserted | number |
updated | number |
deleted | number |
unchanged | number |
Example request
{
"lines": [
{
"account_number": "3010",
"month": "2027-01",
"amount": -100000
},
{
"account_number": "5010",
"month": "2027-01",
"dimensions": {
"6": "P001"
},
"amount": 12000
}
]
}
Example response
{
"data": {
"budget_id": "7c1d…",
"inserted": 2,
"updated": 0,
"deleted": 0,
"unchanged": 0
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/budgets/:id/spread
budgets.spread · scope bookkeeping:write
Spread a yearly amount over the months of a budget.
Writes one account's yearly amount into every month of the budget's fiscal year: evenly (method even), or in proportion to the account's booked months last year (method last_year, so a seasonal account keeps its season). Each month is whole öre and the last month takes the remainder, so the months add up to the amount exactly. A last year that sums to zero falls back to even (fell_back_to_even). Dry-runnable: the preview shows the months.
Use when: The budget for an account is known per year ("3010: 1,2 Mkr").
Don't use for: Different amounts per month (POST /lines).
Pitfalls
- Amounts are in the SIE sign, debit positive: a revenue budget on class 3 is NEGATIVE (-1200000 for 1,2 Mkr sales), a cost budget positive.
- The account's earlier months in the budget (same tag) are replaced.
Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes · Stage for approval: yes (?stage=true)
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. |
stage | string | no | true stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
account_number | string | yes | A resultatkonto, 3000-8999, as a STRING of 4 digits, never a number. |
amount | number | yes | The yearly amount, SIE sign (revenue negative). |
method | "even" | "last_year" | no | even: the same every month. last_year: in proportion to last year's months on the account. Default even. |
dimensions | object | no | Dimension tag of the lines (and of the actuals the profile reads). |
source_fiscal_period_id | string | no | The fiscal year (fiscal_period_id). |
Response fields
| Name | Type |
|---|---|
budget_id | string |
inserted | number |
updated | number |
deleted | number |
unchanged | number |
account_number | string |
method | "even" | "last_year" |
fell_back_to_even | boolean |
months | object[] |
Example request
{
"account_number": "3010",
"amount": -1200000,
"method": "last_year"
}
Example response
{
"data": {
"budget_id": "7c1d…",
"inserted": 12,
"updated": 0,
"deleted": 0,
"unchanged": 0,
"account_number": "3010",
"method": "last_year",
"fell_back_to_even": false,
"months": [
{
"month": "2027-01",
"amount": -80000
}
]
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
PATCH /api/v1/companies/:companyId/budgets/:id
budgets.update · scope bookkeeping:write
Rename a budget, make it the default, or archive or restore it.
Sparse update: name, is_default (true moves the year's default to this budget), archived (true makes it read-only and never the default; false restores it). The fiscal year of a budget never changes. Dry-runnable.
Use when: Choosing which version a year's reports read, renaming one, or putting an old version aside.
Don't use for: Changing amounts (POST /budgets/{id}/lines).
Pitfalls
- Send at least one of name, is_default, archived.
- An archived budget cannot be the default: 409 BUDGET_ARCHIVED.
Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes · Stage for approval: yes (?stage=true)
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. |
stage | string | no | true stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision. |
Request body
| Name | Type | Required | Description |
|---|---|---|---|
name | string | no | The budget's name, unique in its fiscal year ("Budget 2027", "Prognos 2"). |
is_default | boolean | no | true makes it the year's default (the previous default stops being one). |
archived | boolean | no | true archives it (read-only, never the default); false restores it. |
Response fields
| Name | Type |
|---|---|
budget | object |
Example request
{
"is_default": true
}
Example response
{
"data": {
"budget": {
"budget_id": "7c1d…",
"fiscal_period_id": "a8f1…",
"name": "Budget 2027",
"is_default": true,
"archived": false,
"archived_at": null,
"created_at": "2026-11-02T09:00:00Z",
"updated_at": "2026-11-02T09:00:00Z"
}
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
DELETE /api/v1/companies/:companyId/budgets/:id
budgets.delete · scope bookkeeping:write
Delete a budget and its amounts.
Removes the budget and every amount in it. A budget is planning data, not räkenskapsinformation (BFL 7 kap.), so it can be deleted at any time; the audit log keeps who did it. Archiving keeps it instead (PATCH archived=true). Dry-runnable: the preview counts the lines.
Use when: A budget version is no longer wanted.
Don't use for: Putting a version aside but keeping it (PATCH archived=true).
Pitfalls
- A budget of another company, or one already deleted, answers 404 BUDGET_NOT_FOUND.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: yes · Stage for approval: yes (?stage=true)
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. |
stage | string | no | true stages the write for approval instead of committing it: 202 with the pending operation to approve via POST /pending-operations/{id}/approve. Absent or false commits; other values are 400. A reason in the body (why you propose it, at most 500 characters) is shown to the approver as agent_reason and kept with the decision. |
Response fields
| Name | Type |
|---|---|
deleted | true |
budget_id | string |
Example response
{
"data": {
"deleted": true,
"budget_id": "7c1d…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}