Fiscal periods
Period lifecycle — lock, close, year-end, opening balances, FX revaluation. Async via the operations substrate.
Endpoints
GET/api/v1/companies/:companyId/fiscal-periods— List fiscal periods (räkenskapsår).POST/api/v1/companies/:companyId/fiscal-periods/:id/close— Close a fiscal period (IRREVERSIBLE per BFL 5 kap 8 §).POST/api/v1/companies/:companyId/fiscal-periods/:id/currency-revaluation— Run FX revaluation for the fiscal period.POST/api/v1/companies/:companyId/fiscal-periods/:id/lock— Lock a fiscal period (no new entries can be posted into it).POST/api/v1/companies/:companyId/fiscal-periods/:id/opening-balances— Generate opening-balance verifikation for the next fiscal period.POST/api/v1/companies/:companyId/fiscal-periods/:id/year-end— Execute year-end closing (currency revaluation + closing entry).
GET /api/v1/companies/:companyId/fiscal-periods {#get-fiscal-periods-list}
fiscal-periods.list · scope reports:read
List fiscal periods (räkenskapsår).
Returns every fiscal period for the company ordered by period_start DESC. is_closed=true means bokslut has been signed; locked_at non-null means writes are blocked at the DB-trigger level.
Use when: You need to find the active period before booking, build a year-selector UI, or audit the period-lock history.
Don't use for: Creating, locking, or closing periods — those land in Phase 4 (POST /fiscal-periods/{id}/lock, :close, :year-end). Use the dashboard or wait for Phase 4.
Pitfalls
- previous_period_id chains the bokslut continuity (BFNAR 2013:2). A null value on a non-first period is a data-quality red flag.
- A period can be locked but not closed (löpande bokföring of the new year while bokslut work continues on the prior year — see BFL 5 kap 2 § for the löpande bokföring deadline).
- BFL 3 kap caps a single fiscal period at 18 months. First-year exceptions are allowed.
Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no
Example response
{
"data": [
{
"id": "fp_2026",
"name": "Räkenskapsår 2026",
"period_start": "2026-01-01",
"period_end": "2026-12-31",
"is_closed": false,
"locked_at": null
}
],
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/fiscal-periods/:id/close {#post-fiscal-periods-close}
fiscal-periods.close · scope bookkeeping:write
Close a fiscal period (IRREVERSIBLE per BFL 5 kap 8 §).
Sets is_closed=true + closed_at on the period. Pre-requisites: period must be locked (call /lock first) AND year-end closing must have been executed (call /year-end first). Sync. The DB blocks any subsequent JE inserts.
Use when: Final step in the year-end flow: lock → year-end → close. Closing freezes the period for BFL 7 kap retention.
Don't use for: Locking a period (use /lock). Running the year-end closing entry (use /year-end). UNDOING a close (not supported — irreversible).
Pitfalls
- Idempotency-Key is mandatory.
- IRREVERSIBLE. Once is_closed=true, the period is read-only forever (BFL 5 kap 8 § + 7 kap).
- Pre-conditions: locked + closing_entry_id present. Otherwise the call returns CONFLICT.
Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: no
Example response
{
"data": {
"id": "a8f1…",
"is_closed": true,
"closed_at": "2026-05-12T14:30:00Z"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/fiscal-periods/:id/currency-revaluation {#post-fiscal-periods-currency-revaluation}
fiscal-periods.currency-revaluation · scope bookkeeping:write
Run FX revaluation for the fiscal period.
Re-rates open foreign-currency AR (1510) and AP (2440) at the closing date's Riksbanken rate and posts the SEK delta to 3960 (valutakursvinst) / 7960 (valutakursförlust). Returns 202 with operation_id. Idempotent per-period: the engine throws if a revaluation has already been posted for the same fiscal_period_id.
Use when: Before /year-end if your books have open foreign-currency receivables or payables. /year-end also runs this internally, so you only need to call it separately when you want the FX-only entry without the full closing.
Don't use for: Re-running on the same period (CURRENCY_REVALUATION_ALREADY_EXISTS). Revaluing a closed period (the trigger blocks JE writes to closed periods).
Pitfalls
- Idempotency-Key is mandatory.
- Engine returns null if no open foreign-currency items exist — the operation succeeds with result.revaluation_entry_id=null.
- as_of_date defaults to period_end if omitted.
Risk: high · Idempotent: yes · Reversible: yes · Dry-run supported: no
Example response
{
"data": {
"operation_id": "0e9c…",
"type": "fiscal_periods.currency_revaluation",
"status": "succeeded",
"poll_url": "/api/v1/operations/0e9c…",
"webhook_event": "operation.completed"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/fiscal-periods/:id/lock {#post-fiscal-periods-lock}
fiscal-periods.lock · scope bookkeeping:write
Lock a fiscal period (no new entries can be posted into it).
Sets locked_at on the period. Refuses if uncategorised business transactions remain in the period — they must be bokfört first. The DB trigger blocks JE inserts into locked periods; locking is the application-level pre-step before /close. Sync.
Use when: Finishing a period and you want to stop new postings. Step 1 of a three-step year-end flow: lock → year-end → close.
Don't use for: Locking an already-closed period (no-op). Bypassing the uncategorised-transactions guard — categorise or mark-private first.
Pitfalls
- Idempotency-Key is mandatory.
- A period with uncategorised business transactions cannot be locked; the response surfaces the count.
- Locking is reversible until /close. The unlock endpoint is not in v1; use the dashboard.
Risk: high · Idempotent: yes · Reversible: yes · Dry-run supported: no
Example response
{
"data": {
"id": "a8f1…",
"locked_at": "2026-05-12T14:00:00Z",
"is_closed": false
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/fiscal-periods/:id/opening-balances {#post-fiscal-periods-opening-balances}
fiscal-periods.opening-balances · scope bookkeeping:write
Generate opening-balance verifikation for the next fiscal period.
Reads the closed period's trial balance, filters to BAS class 1–2 accounts with non-zero closing balance, and posts an opening verifikation (status=posted) onto the next_period_id. Sync. The path id is the CLOSED period; body.next_period_id is the target.
Use when: After /year-end + /close on a period, generate the IB into the next period so the new year starts with the correct balance sheet.
Don't use for: Posting opening balances on a manually-edited basis (use POST /journal-entries with source_type=manual). Re-running on the same target period (will produce duplicate IB entries).
Pitfalls
- Idempotency-Key is mandatory.
- next_period_id must reference the SAME company and must NOT already have an IB entry. The engine throws if it does.
- Only class 1 (assets) and 2 (equity/liabilities) flow into the IB; class 3-8 are zeroed by the closing entry.
Risk: high · Idempotent: yes · Reversible: yes · Dry-run supported: no
Example request
{
"next_period_id": "7b3a…"
}
Example response
{
"data": {
"opening_entry_id": "4d2a…",
"voucher_series": "A",
"voucher_number": 1,
"next_period_id": "7b3a…"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}
POST /api/v1/companies/:companyId/fiscal-periods/:id/year-end {#post-fiscal-periods-year-end}
fiscal-periods.year-end · scope bookkeeping:write
Execute year-end closing (currency revaluation + closing entry).
Async-operation endpoint. Runs the year-end closing flow: currency revaluation (FX gains/losses to 3960/7960), then posts the closing entry that zeroes class 3-8 onto årets resultat (2099 for AB, the relevant eget-kapital account in the 2010-2019 range for enskild firma — the engine resolves which based on company.entity_type). Returns 202 with operation_id; subscribe to operation.completed or poll /v1/operations/{id}.
Use when: After /lock and a passing /compliance/check?type=year_end_readiness, you want to run the closing entry. This is step 2 of the lock → year-end → close flow.
Don't use for: Re-running year-end (per-period idempotent — fails if closing_entry_id is already set). Closing the period (use /close after year-end succeeds).
Pitfalls
- Idempotency-Key is mandatory.
- Period must pass year_end_readiness checks (no drafts, no unexplained voucher gaps, trial balance balanced). The engine re-validates and aborts if not.
- Closing entry is itself a verifikation (posted) — the period must NOT already be closed.
Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: no
Example response
{
"data": {
"operation_id": "0e9c…",
"type": "fiscal_periods.year_end",
"status": "succeeded",
"poll_url": "/api/v1/operations/0e9c…",
"webhook_event": "operation.completed"
},
"meta": {
"request_id": "req_…",
"api_version": "2026-05-12"
}
}