Menu

Fiscal periods

Fiscal years and their lifecycle: create, edit, lock, unlock, close, year-end, opening balances, FX revaluation, and klarmarkering of years closed in a previous system.

Endpoints


GET /api/v1/companies/:companyId/fiscal-periods

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, editing, locking or closing periods: use POST /fiscal-periods, PATCH /fiscal-periods/{id}, POST /fiscal-periods/{id}/lock, /unlock, /close and /year-end.

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

Response fields

NameType
fiscal_periods[].idstring
fiscal_periods[].namestring
fiscal_periods[].period_startstring
fiscal_periods[].period_endstring
fiscal_periods[].is_closedboolean
fiscal_periods[].closed_atstring | null
fiscal_periods[].locked_atstring | null
fiscal_periods[].previous_period_idstring | null
fiscal_periods[].created_atstring
fiscal_periods[].duration_daysnumber
fiscal_periods[].exceeds_18_monthsboolean

Example response

{
  "data": {
    "fiscal_periods": [
      {
        "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"
  }
}

GET /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/ixbrl

arsredovisning.ixbrl · scope reports:read

The K2 årsredovisning as an inline XBRL (XHTML) document.

Generates the iXBRL document Bolagsverket's digital filing takes (K2, Bolagsverket taxonomy), for the live draft or a frozen version (version_id). The XHTML is also the human-readable document. It is not a filing: digital filing goes through connected software with the fastställelseintyg signed by BankID, which this API does not do.

Use when: Archiving the digital document, or validating it with external tools.

Don't use for: The pre-flight result as JSON (GET .../arsredovisning/ixbrl/validate) or a printable copy (GET .../arsredovisning/pdf).

Pitfalls

  • K2 aktiebolag only.
  • The live draft is refused while an SIE import is unfinished (409); a frozen version stays readable.
  • proposed_dividend (whole SEK) applies to the live draft only.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Query parameters

NameTypeRequiredDescription
version_idstringnoA frozen version (annual_report_version_id); omit for the live draft.
proposed_dividendnumbernoLive draft only: proposed dividend in whole SEK for the resultatdisposition.

Example response

{
  "_note": "Returns application/xhtml+xml as a binary download."
}

GET /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/ixbrl/validate

arsredovisning.validate-ixbrl · scope reports:read

Pre-flight the generated iXBRL årsredovisning against Bolagsverket's kontrollera rules.

Generates the K2 inline XBRL document (the live draft, or a frozen version with version_id) and runs the local mirror of Bolagsverket's kontrollera checks on it, plus a generation dry run and the 5 MB size limit. Issues carry the Bolagsverket code where one exists (e.g. 1107 missing signers) or ACC-*. Nothing is sent to Bolagsverket. Read-only.

Use when: Before a digital filing, or to see why the iXBRL document is not ready.

Don't use for: The ÅRL/K2 completeness checks per stage (gnubok_validate_arsredovisning), or the document itself (GET .../arsredovisning/ixbrl, v1 only).

Pitfalls

  • K2 aktiebolag only: the iXBRL generator does not produce K3 documents.
  • A validation with issues still answers 200; read ok and error_count.
  • An unknown version_id answers 404 NOT_FOUND.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Query parameters

NameTypeRequiredDescription
version_idstringnoA frozen version (annual_report_version_id); omit for the live draft.
proposed_dividendnumbernoLive draft only: proposed dividend in whole SEK for the resultatdisposition.

Response fields

NameType
okboolean
issuesobject[]
error_countnumber
warning_countnumber
generated_bytesnumber
entry_pointstring
periodobject
annual_report_version_idstring | null

Example request

{
  "fiscal_period_id": "7c2b…"
}

Example response

{
  "data": {
    "ok": false,
    "issues": [
      {
        "code": "1107",
        "severity": "error",
        "message": "Underskrifter saknas."
      }
    ],
    "error_count": 1,
    "warning_count": 0,
    "generated_bytes": 84211,
    "entry_point": "k2-ab-risbs-2024-09-12",
    "period": {
      "start": "2026-01-01",
      "end": "2026-12-31"
    },
    "annual_report_version_id": null
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

GET /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/pdf

arsredovisning.pdf · scope reports:read

The årsredovisning as PDF: the live draft or a frozen version.

Renders the årsredovisning for the räkenskapsår (K2 or K3 template by the framework) as the dashboard does. Without version_id it is the live draft from the books, named ...-utkast.pdf; with version_id it is that immutable version with the signatures recorded on it, named ...-papperskopia.pdf once signed. Nothing is sent to Bolagsverket.

Use when: A printable copy for the board to sign on paper, the archive, or a review.

Don't use for: The inline XBRL document for digital filing (GET .../arsredovisning/ixbrl) or the report as JSON (gnubok_preview_arsredovisning).

Pitfalls

  • The live draft is refused while an SIE import is unfinished (409); a frozen version stays readable.
  • An unknown version_id answers 404 NOT_FOUND.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Query parameters

NameTypeRequiredDescription
version_idstringnoA frozen version (annual_report_version_id); omit for the live draft.

Example response

{
  "_note": "Returns application/pdf as a binary download."
}

GET /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/signatures

arsredovisning.list-signatories · scope reports:read

The årsredovisning signer roster and its signatures.

The current signer slots for the räkenskapsår: the unbound pending roster while one exists, otherwise the slots bound to the latest version with their status, signing date, method and evidence reference. The signature_id values are what PATCH and DELETE .../signatures/{signatureId} take; finalizing a version may create new slots bound to it. Read-only.

Use when: Before adding a signer (avoid duplicates) or to find the slot to record a signature on after a version is finalized.

Don't use for: The report content (gnubok_preview_arsredovisning) or the version list (gnubok_list_arsredovisning_versions).

Pitfalls

  • Declined slots are listed too; they do not appear in the document.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Response fields

NameTypeDescription
signatures[].signature_idstring
signatures[].fiscal_period_idstring
signatures[].annual_report_version_idstring | nullNull while the slot is on the unbound roster.
signatures[].rolestring
signatures[].signer_namestring
signatures[].status"pending" | "signed" | "declined"
signatures[].signed_atstring | null
signatures[].signing_method"paper_original" | "advanced_e_signature" | "bankid" | "bolagsverket" | null
signatures[].evidence_referencestring | null
signatures[].evidence_recorded_atstring | null
signatures[].created_atstring

Example response

{
  "data": {
    "signatures": [
      {
        "signature_id": "5b1c…",
        "fiscal_period_id": "7c2b…",
        "annual_report_version_id": null,
        "role": "Styrelseledamot",
        "signer_name": "Anna Andersson",
        "status": "pending",
        "signed_at": null,
        "signing_method": null,
        "evidence_reference": null,
        "evidence_recorded_at": null,
        "created_at": "2027-03-02T09:00:00Z"
      }
    ]
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

GET /api/v1/companies/:companyId/fiscal-periods/:id/opening-balances/split-per-project

opening-balances.split-per-project-preview · scope reports:read

Preview splitting a year's ingående balanser per project from the previous year's tagged closing balances.

For the year's IB verifikat, computes the per-project split of each balance sheet account from the previous fiscal year's closing balances per object of the dimensions that carry across years (registry resets_annually = false: projekt, dimension 6). Each such account becomes one line per project plus one untagged remainder (the current IB minus the projects, any sign); the account total never changes. Accounts without a project balance last year, and the VAT accounts (26xx), stay untouched. Answers current and proposed lines per account, which accounts change, unresolved project codes, and blocked (code and Swedish message) when the year is closed, locked, behind the lock date or has a bokslut. Read-only.

Use when: A project-filtered ledger opens at zero for a year whose IB was booked before project balances were carried (an IB imported or closed earlier), and the user asks how project balances carry into the new year.

Don't use for: Changing amounts in the IB (POST /fiscal-periods/{id}/opening-balances/correct) or a year closed after project balances were carried: the year-end already splits the IB per project.

Pitfalls

  • The basis is the previous year in Accounted as it stands: if that year is still open (source_period_closed false), later bookings there change the split.
  • A previous year whose own IB was untagged only carries the projects' movements of that year.
  • An account whose IB already carries a different project split (an IB imported with SIE #OIB, or split by hand) is skipped with skip_reason existing_split, never overwritten.
  • unresolved_dimensions lists codes missing from the registry: the apply refuses until they exist.

Risk: low · Idempotent: yes · Reversible: no · Dry-run supported: no

Response fields

NameTypeDescription
fiscal_period_idstring
fiscal_period_namestring | null
journal_entry_idstring | nullThe IB verifikat the split corrects in place.
voucherstring | null
source"previous_year"
source_fiscal_period_idstring | null
source_fiscal_period_namestring | null
source_period_closedboolean | nullFalse while the previous year is still open: its balances may still move.
accumulating_dimensionsstring[]SIE numbers of the dimensions whose balances carry (resets_annually = false), e.g. ["6"].
method"inline_rattelse"
accounts_to_changenumber
accounts_unchangednumber
accounts_skippednumber
changed_amount_seknumberWhat the split moves in SEK: the largest side (debit or credit) of the lines struck or added. Priced by an API key's unattended-commit ceiling.
can_applyboolean
blockedobject | nullWhy the split cannot run now: the code the apply answers, with the Swedish message.
unresolved_dimensionsobject[]
dimension_valuesobject[]
accountsobject[]
fingerprintstringPass as expected_fingerprint to apply exactly this split.

Example response

{
  "data": {
    "fiscal_period_id": "7b3a…",
    "fiscal_period_name": "2026",
    "journal_entry_id": "4d2a…",
    "voucher": "A1",
    "source": "previous_year",
    "source_fiscal_period_id": "5e91…",
    "source_fiscal_period_name": "2025",
    "source_period_closed": true,
    "accumulating_dimensions": [
      "6"
    ],
    "method": "inline_rattelse",
    "accounts_to_change": 1,
    "accounts_unchanged": 0,
    "accounts_skipped": 0,
    "changed_amount_sek": 1800,
    "can_apply": true,
    "blocked": null,
    "unresolved_dimensions": [],
    "dimension_values": [
      {
        "sie_dim_no": "6",
        "code": "P1",
        "name": "Kv. Eken",
        "is_active": true
      }
    ],
    "accounts": [
      {
        "account_number": "1470",
        "account_name": "Pågående arbeten",
        "total": 1800,
        "status": "change",
        "skip_reason": null,
        "current_lines": [
          {
            "journal_entry_line_id": "9c0f…",
            "debit_amount": 1800,
            "credit_amount": 0,
            "amount": 1800,
            "dimensions": {},
            "line_description": "IB 1470"
          }
        ],
        "proposed_lines": [
          {
            "debit_amount": 1300,
            "credit_amount": 0,
            "amount": 1300,
            "dimensions": {
              "6": "P1"
            },
            "line_description": "Ingående balans: Pågående arbeten"
          },
          {
            "debit_amount": 500,
            "credit_amount": 0,
            "amount": 500,
            "dimensions": {},
            "line_description": "Ingående balans: Pågående arbeten"
          }
        ]
      }
    ],
    "fingerprint": "3f9a…"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods

fiscal-periods.create · scope bookkeeping:write

Create a fiscal year (räkenskapsår).

Creates a räkenskapsår and links it into the continuity chain (previous_period_id, BFNAR 2013:2). The year must be at most 18 months, end on a month end, and start on the 1st unless it becomes the earliest year (BFL 3 kap. 1 and 3 §§). It must be contiguous with its neighbours: appended, it starts the day after the latest year ends; filling a gap, it also ends the day before the next year starts; prepended, it ends the day before the earliest year starts. A still-open prior year does not block: the 201 carries a PRIOR_FISCAL_YEAR_STILL_OPEN warning. Idempotent. Dry-runnable.

Use when: The company needs the next räkenskapsår to book in (e.g. January arrives), or an earlier year must exist before its SIE file or opening balances can be imported.

Don't use for: Closing the prior year (run the year-end), or changing an existing year (PATCH /fiscal-periods/{id}).

Pitfalls

  • A start that does not continue the preceding year answers 400 FISCAL_PERIOD_NOT_CONTIGUOUS with details.expected_start (or details.expected_end): retry with that date.
  • Overlapping an existing year answers 409 FISCAL_PERIOD_OVERLAP.
  • An enskild firma normally runs the calendar year; a brutet räkenskapsår needs Skatteverket's permission.
  • There is no delete: a wrong year can be re-dated with PATCH only while nothing is posted in it.

Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
namestringyesDisplay name, e.g. "Räkenskapsår 2027".
period_startstringyesFirst day, YYYY-MM-DD.
period_endstringyesLast day, YYYY-MM-DD (a month end).

Response fields

NameType
fiscal_periodobject

Example request

{
  "name": "Räkenskapsår 2027",
  "period_start": "2027-01-01",
  "period_end": "2027-12-31"
}

Example response

{
  "data": {
    "fiscal_period": {
      "id": "a8f1…",
      "name": "Räkenskapsår 2027",
      "period_start": "2027-01-01",
      "period_end": "2027-12-31",
      "is_closed": false,
      "closed_at": null,
      "closed_externally": false,
      "locked_at": null,
      "previous_period_id": "5c2e…",
      "created_at": "2026-12-01T09:00:00Z"
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/narrative

arsredovisning.update-narrative · scope bookkeeping:write

Edit the årsredovisning texts: förvaltningsberättelse, resultatdisposition, disclosure notes and K3 note texts.

Saves the narrative overrides for a räkenskapsår's årsredovisning, as the dashboard's text step does. Only the fields sent change; null clears a field back to the generated text. Covers the förvaltningsberättelse (description, important_events), the resultatdisposition text and proposed_dividend, the årsstämma date and decision, the ÅRL 5 kap. disclosures (long-term debt, ställda säkerheter, eventualförpliktelser), koncernförhållanden, a manual medelantal anställda, K3 note texts (note_overrides replaces the whole object) and the K3 kassaflödesanalys omission. A save clears the narrative confirmation in the compliance profile. Allowed after the period is closed; refused once a Bolagsverket submission is registrerad. Idempotent. Dry-runnable.

Use when: Writing or correcting the texts of the årsredovisning before a version is frozen for signing.

Don't use for: The compliance profile answers (PATCH .../arsredovisning/compliance), the figures (they come from the books), or freezing the document (POST .../arsredovisning/versions).

Pitfalls

  • A version already frozen keeps its text: freeze a new version after editing.
  • agm_disposition_outcome alternative_decision needs agm_disposition_decision text (400 VALIDATION_ERROR).
  • parent_company_org_number is an organisationsnummer or a foreign registration id; a personnummer is refused.
  • Registered at Bolagsverket: 409 ARSREDOVISNING_REGISTERED.
  • Saving clears narrative_confirmed: confirm again with PATCH .../compliance {"narrative_confirmed": true}.

Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
descriptionstring | nullnoFörvaltningsberättelse: allmänt om verksamheten.
important_eventsstring | nullnoFörvaltningsberättelse: väsentliga händelser under räkenskapsåret.
resultatdispositionstring | nullnoStyrelsens förslag till resultatdisposition, as text.
proposed_dividendnumber | nullnoFöreslagen utdelning in SEK (rounded to öre); null clears it.
agm_datestring | nullnoÅrsstämmans datum, YYYY-MM-DD (fills the fastställelseintyg).
long_term_debt_over_five_yearsnumber | nullnoÅRL 5:13 §: long-term debt falling due after more than five years, SEK.
securities_pledgedstring | nullnoÅRL 5:14 §: ställda säkerheter.
contingent_liabilitiesstring | nullnoÅRL 5:15 §: eventualförpliktelser.
parent_company_namestring | nullnoModerföretagets namn (koncernförhållanden).
parent_company_org_number"" | string | nullnoModerföretagets organisationsnummer or foreign registration id; never a personnummer.
parent_company_citystring | nullnoModerföretagets säte.
medelantal_anstallda_overridenumber | nullnoÅRL 5:20 §: medelantal anställda as a whole number; null uses the computed average.
member_count_changestring | nullnoÅRL 6 kap. 3 § (ekonomisk förening): change in the number of members during the year.
insatser_repayable_next_yearnumber | nullnoÅRL 6 kap. 3 § (ekonomisk förening): insatser to repay next fiscal year, SEK; 0 when none.
forlagsinsatser_dividend_rightstring | nullnoÅRL 6 kap. 3 § (ekonomisk förening): the distribution right förlagsinsatser carry.
forlagsinsatser_redeemable_two_yearsnumber | nullnoÅRL 6 kap. 3 § (ekonomisk förening): förlagsinsatser redeemable within two years, SEK; 0 when none.
long_term_debt_over_five_years_confirmedbooleanno
securities_pledged_confirmedbooleanno
contingent_liabilities_confirmedbooleanno
parent_company_confirmedbooleanno
agm_disposition_outcome"proposal_approved" | "alternative_decision" | nullnoWhat the årsstämma decided about the resultatdisposition.
agm_disposition_decisionstring | nullnoThe årsstämma's alternative decision, required with alternative_decision.
note_overridesobjectnoK3 note texts by note key; the whole object is replaced, blank or null resets a note.
omit_kassaflodesanalysbooleanno
kassaflodesanalys_omission_confirmedbooleanno

Response fields

NameType
narrative_idstring
fiscal_period_idstring
descriptionstring | null
important_eventsstring | null
resultatdispositionstring | null
proposed_dividendnumber | null
agm_datestring | null
long_term_debt_over_five_yearsnumber | null
securities_pledgedstring | null
contingent_liabilitiesstring | null
parent_company_namestring | null
parent_company_org_numberstring | null
parent_company_citystring | null
medelantal_anstallda_overridenumber | null
agm_disposition_outcome"proposal_approved" | "alternative_decision" | null
agm_disposition_decisionstring | null
note_overridesobject
omit_kassaflodesanalysboolean
updated_atstring

Example request

{
  "description": "Bolaget bedriver konsultverksamhet inom IT.",
  "agm_date": "2027-05-20"
}

Example response

{
  "data": {
    "narrative_id": "1f0e…",
    "fiscal_period_id": "7c2b…",
    "description": "Bolaget bedriver konsultverksamhet inom IT.",
    "agm_date": "2027-05-20",
    "updated_at": "2027-03-02T09:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/signatures

arsredovisning.add-signatory · scope bookkeeping:write

Add a board member or the VD to the årsredovisning signer roster.

Adds a pending signer slot (role and name) to the roster for the räkenskapsår. ÅRL 2 kap. 7 § requires every ordinarie styrelseledamot and the VD, if any, to sign. This records who is to sign; it signs nothing and involves no BankID. Any roster change clears the signer roster confirmation. Answers 201 with the slot. Idempotent. Dry-runnable.

Use when: Setting up who signs, before finalizing a version (POST .../arsredovisning/versions action finalize).

Don't use for: Recording that someone signed (PATCH .../signatures/{signatureId}) or filing with Bolagsverket.

Pitfalls

  • The same role and name twice on the unbound roster answers 409 ARSREDOVISNING_SIGNER_ALREADY_EXISTS.
  • signer_name is the name as registered at Bolagsverket; a personnummer is refused (400).
  • Confirm the roster afterwards: PATCH .../compliance {"signer_roster_confirmed": true}.

Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
role"Styrelseledamot" | "Styrelseordförande" | "VD" | "Verkställande direktör"yesStyrelseledamot, Styrelseordförande, VD or Verkställande direktör.
signer_namestringyesFull name as registered at Bolagsverket. Never a personnummer.

Response fields

NameTypeDescription
signature_idstring
fiscal_period_idstring
annual_report_version_idstring | nullNull while the slot is on the unbound roster.
rolestring
signer_namestring
status"pending" | "signed" | "declined"
signed_atstring | null
signing_method"paper_original" | "advanced_e_signature" | "bankid" | "bolagsverket" | null
evidence_referencestring | null
evidence_recorded_atstring | null
created_atstring

Example request

{
  "role": "Styrelseledamot",
  "signer_name": "Anna Andersson"
}

Example response

{
  "data": {
    "signature_id": "5b1c…",
    "fiscal_period_id": "7c2b…",
    "annual_report_version_id": null,
    "role": "Styrelseledamot",
    "signer_name": "Anna Andersson",
    "status": "pending",
    "signed_at": null,
    "signing_method": null,
    "evidence_reference": null,
    "evidence_recorded_at": null,
    "created_at": "2027-03-02T09:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/versions

arsredovisning.create-version · scope bookkeeping:write

Freeze an immutable årsredovisning version: a draft snapshot, or the version that gets signed.

Builds the årsredovisning from a complete read of the books (refused while an SIE import is unfinished) and stores it as an immutable version with its content hash. action snapshot stores a draft; action finalize requires every signing-stage check to pass, marks the version ready_for_signature, supersedes an earlier ready or signed version and binds the signer roster to it. certificate_signer names who signs the fastställelseintyg. expected_content_hash (from a dry run) makes the call refuse when the content has changed since. Answers 201 with the version. Idempotent. Dry-runnable: the dry run answers the content hash and the validation counts.

Use when: The texts, compliance answers and signer roster are done and the document is to be signed (finalize), or a checkpoint of the draft is wanted (snapshot).

Don't use for: Filing with Bolagsverket (dashboard only, BankID), recording signatures (PATCH .../signatures/{signatureId}) or reading the document (GET .../arsredovisning/pdf).

Pitfalls

  • Statements that do not tie, or (finalize) any signing-stage error, answer 409 ARSREDOVISNING_INCOMPLETE with the validation in details: run gnubok_validate_arsredovisning with stage signing first.
  • finalize needs a confirmed signer roster (PATCH .../compliance {"signer_roster_confirmed": true}) and at least one signer.
  • Content changed since the hash you pass: 409 ARSREDOVISNING_CONTENT_CHANGED; dry-run again and review.
  • A new finalize supersedes the previous ready_for_signature version; signatures recorded on it do not carry over.
  • Refused while an SIE import is unfinished: complete or undo it first.

Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
action"snapshot" | "finalize"yessnapshot saves a draft version; finalize freezes the version for signing (ready_for_signature).
certificate_signerobjectnoWho signs the fastställelseintyg (a board member or the VD).
expected_content_hashstringnoThe content_hash a dry run answered; the version is refused when the content no longer matches.

Response fields

NameTypeDescription
annual_report_version_idstring
version_numbernumber
status"draft" | "ready_for_signature" | "signed" | "filed" | "registered" | "superseded"
frameworkstring
content_hashstringSHA-256 of the frozen content (signature dates excluded).
taxonomy_versionstring | null
entry_pointstring | null
finalized_atstring | null
created_atstring

Example request

{
  "action": "finalize",
  "certificate_signer": {
    "first_name": "Anna",
    "last_name": "Andersson",
    "role": "Styrelseledamot"
  }
}

Example response

{
  "data": {
    "annual_report_version_id": "4e7d…",
    "version_number": 2,
    "status": "ready_for_signature",
    "framework": "k2",
    "content_hash": "9f86d081…",
    "taxonomy_version": "2024-09-12",
    "entry_point": "k2-ab-risbs-2024-09-12",
    "finalized_at": "2027-03-02T09:00:00Z",
    "created_at": "2027-03-02T09:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/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

Response fields

NameType
idstring
is_closedtrue
closed_atstring

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/close-external

fiscal-periods.close-external · scope bookkeeping:write

Mark a migrated fiscal year as closed in the previous system (klarmarkera).

Closes and locks an imported historical räkenskapsår whose bokslut was done in the previous bookkeeping software, without a closing entry here, and writes the decision to the audit log. Only for migrated years: the year must have ended, have no closing entry in Accounted, and hold imported verifikat, no verifikat, or balance-sheet-only verifikat with the next year's IB already posted. Unbooked bank transactions in the year block it, as for a lock. Undo with reopen-external. Idempotent. Dry-runnable.

Use when: After an SIE migration, the earlier years show as pending bokslut although their bokslut was done in the old system.

Don't use for: Closing a year bookkept in Accounted: run the year-end (POST /fiscal-periods/{id}/year-end), which transfers the result and carries the balances forward.

Pitfalls

  • A year bookkept here with result accounts answers 409 FISCAL_PERIOD_CLOSE_EXTERNAL_NATIVE_BOOKKEEPING: run the year-end instead.
  • A running year answers 409 FISCAL_PERIOD_CLOSE_EXTERNAL_NOT_ENDED.
  • Unbooked bank transactions answer 400 PERIOD_HAS_UNBOOKED_TRANSACTIONS with the count in details.reason.

Risk: high · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Response fields

NameType
idstring
namestring
period_startstring
period_endstring
is_closedboolean
closed_atstring | null
closed_externallyboolean
locked_atstring | null
previous_period_idstring | null
created_atstring

Example response

{
  "data": {
    "id": "a8f1…",
    "name": "Räkenskapsår 2024",
    "period_start": "2024-01-01",
    "period_end": "2024-12-31",
    "is_closed": true,
    "closed_at": "2026-09-25T09:00:00Z",
    "closed_externally": true,
    "locked_at": "2026-09-25T09:00:00Z",
    "previous_period_id": "5c2e…",
    "created_at": "2026-12-01T09:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/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

Request body

NameTypeRequired
as_of_datestringno

Response fields

NameType
operation_idstring
type"fiscal_periods.currency_revaluation"
status"queued" | "running" | "succeeded" | "failed"
poll_urlstring
webhook_event"operation.completed"

Example request

{
  "as_of_date": "2026-12-31"
}

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

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: POST /fiscal-periods/{id}/unlock lifts it.

Risk: high · Idempotent: yes · Reversible: yes · Dry-run supported: no

Response fields

NameType
idstring
locked_atstring
is_closedboolean

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

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

Request body

NameTypeRequired
next_period_idstringyes

Response fields

NameType
opening_entry_idstring
voucher_seriesstring
voucher_numbernumber
next_period_idstring

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/opening-balances/correct

opening-balances.correct · scope bookkeeping:write

Correct a year's ingående balanser by storno: the full corrected IB replaces the old one.

Books the corrected IB verifikat from the lines given (the complete IB, not a difference), stornoes the old IB verifikat and relinks the year to the new one (BFL 5 kap 5 §: nothing posted is edited). The new verifikat's text references the one it corrects. With cascade=true the per-account change is also carried into every later year's IB; years that are closed, locked, behind the lock date or have a bokslut are skipped and reported. Only for an open, unlocked year with an IB and no bokslut. Idempotent. Dry-runnable: the dry run previews the new verifikat and the per-account change and writes nothing.

Use when: The IB booked for a year was wrong (a typo, a balance the previous system corrected later).

Don't use for: A year without an IB (POST /fiscal-periods/{id}/opening-balances/manual), a single wrong line in an open year (POST /journal-entries/{id}/strike-lines on the IB verifikat corrects inside the same verifikat), or a year that is locked, closed or has a bokslut (unwind those first).

Pitfalls

  • Send the COMPLETE corrected IB: accounts left out end at zero.
  • A year without an IB answers 409 OB_CORRECT_NO_EXISTING; one with a posted bokslut 409 OB_CORRECT_YEAR_END_EXISTS.
  • A company lock date on or after the year's start answers 409 OB_COMPANY_LOCK_DATE.
  • If the storno or relink fails the new IB is stornoed again and 500 OB_CORRECT_FAILED names both entry ids: the year keeps its old IB.
  • cascade is best effort per later year: read cascade.skipped in the response and correct those years by hand.

Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
linesobject[]yesThe IB per account. Zero rows are dropped; debit must equal credit.
cascadebooleannoAlso carry the per-account change into every later year's IB. Default false.

Response fields

NameTypeDescription
journal_entry_idstring
voucher_seriesstring | null
voucher_numbernumber | null
reversed_entry_idstringThe old IB verifikat, now stornoed.
fiscal_period_idstring
lines_creatednumber
total_debitnumber
total_creditnumber
cascadeobject (optional)

Example request

{
  "lines": [
    {
      "account_number": "1930",
      "amount": 84250.5
    },
    {
      "account_number": "1510",
      "debit_amount": 12500,
      "credit_amount": 0
    },
    {
      "account_number": "2440",
      "amount": -9800
    },
    {
      "account_number": "2081",
      "amount": -25000
    },
    {
      "account_number": "2099",
      "amount": -61950.5
    }
  ],
  "cascade": true
}

Example response

{
  "data": {
    "journal_entry_id": "8c1e…",
    "voucher_series": "A",
    "voucher_number": 42,
    "reversed_entry_id": "4d2a…",
    "fiscal_period_id": "7b3a…",
    "lines_created": 5,
    "total_debit": 96750.5,
    "total_credit": 96750.5,
    "cascade": {
      "corrected": [],
      "skipped": []
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/opening-balances/manual

opening-balances.set-manual · scope bookkeeping:write

Book a fiscal year's ingående balanser (IB) from explicit lines, for a company new to Accounted.

Posts the IB verifikat (source_type opening_balance, series A, dated the year's first day) through the bookkeeping engine and links it to the year, as the dashboard's opening balance import does. Lines are balance sheet accounts only (class 1-2), zero rows dropped, at least two left, debit equal to credit. BAS accounts missing from the chart are activated. Refused when the year already has an IB, is closed or locked, or starts on or before the company lock date. Idempotent. Dry-runnable: the dry run previews the verifikat and writes nothing.

Use when: The company moved from another system without an SIE file and its first year in Accounted needs the balances from the previous system's balansräkning.

Don't use for: Carrying the IB forward from a year closed in Accounted (POST /fiscal-periods/{id}/opening-balances, or the year-end which does it), an SIE migration (POST /imports/sie brings its own IB), or changing an IB already booked (POST /fiscal-periods/{id}/opening-balances/correct).

Pitfalls

  • A year that already has an IB answers 409 OB_PERIOD_ALREADY_HAS_BALANCES with details.existingEntryId: correct it instead.
  • Class 3-8 accounts answer 400 OB_PNL_ACCOUNT, class 0 and 9 400 OB_NON_BALANCE_SHEET_ACCOUNT: an IB holds balance sheet accounts only, earlier years' results sit in equity (20xx).
  • Debit and credit must match to the öre: 400 OB_UNBALANCED with details.diff.
  • A company lock date on or after the year's start answers 409 OB_SET_COMPANY_LOCK_DATE; a closed or locked year 400 OB_PERIOD_CLOSED or OB_PERIOD_LOCKED.
  • The IB is a posted verifikat: it is never edited or deleted, only corrected by storno through /opening-balances/correct.

Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
linesobject[]yesThe IB per account. Zero rows are dropped; debit must equal credit.

Response fields

NameType
journal_entry_idstring
voucher_seriesstring | null
voucher_numbernumber | null
fiscal_period_idstring
entry_datestring
lines_creatednumber
total_debitnumber
total_creditnumber

Example request

{
  "lines": [
    {
      "account_number": "1930",
      "amount": 84250.5
    },
    {
      "account_number": "1510",
      "debit_amount": 12500,
      "credit_amount": 0
    },
    {
      "account_number": "2440",
      "amount": -9800
    },
    {
      "account_number": "2081",
      "amount": -25000
    },
    {
      "account_number": "2099",
      "amount": -61950.5
    }
  ]
}

Example response

{
  "data": {
    "journal_entry_id": "4d2a…",
    "voucher_series": "A",
    "voucher_number": 1,
    "fiscal_period_id": "7b3a…",
    "entry_date": "2026-01-01",
    "lines_created": 5,
    "total_debit": 96750.5,
    "total_credit": 96750.5
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/opening-balances/split-per-project

opening-balances.split-per-project · scope bookkeeping:write

Split a year's ingående balanser per project, inside the same IB verifikat (inline rättelse).

Applies the split GET /fiscal-periods/{id}/opening-balances/split-per-project previews: for each changing account the IB verifikat's lines are struck and replaced by one line per project plus an untagged remainder, inside the same verifikat through the inline rättelse (BFL 5 kap 5 §; the struck lines are kept in the rättelse log with who and when). Account totals never change. Every project code is checked against the dimension registry first; archived projects are kept. Only for an open, unlocked year after the lock date and without a bokslut; there is no storno fallback, because a storno-corrected IB carries no project tags. A split already in place answers applied=false and writes nothing. Idempotent. Dry-runnable: the dry run answers the preview.

Use when: The user wants project balances carried into a year whose IB was booked as one line per account, after reviewing the preview.

Don't use for: Changing IB amounts (POST /fiscal-periods/{id}/opening-balances/correct), or a locked or closed year: open it first.

Pitfalls

  • Pass expected_fingerprint from the preview: if the IB or the previous year changed since, 409 OB_SPLIT_PROPOSAL_CHANGED instead of a different split.
  • A closed year answers 409 OB_SPLIT_PERIOD_CLOSED, a locked one 409 OB_SPLIT_PERIOD_LOCKED, a lock date covering the IB 409 OB_COMPANY_LOCK_DATE, a posted bokslut 409 OB_CORRECT_YEAR_END_EXISTS.
  • Project codes missing from the registry answer 409 OB_SPLIT_DIMENSION_UNRESOLVED with details.unresolved.
  • An account with a foreign-currency IB line or a line-level underlag link, or whose IB already carries a different project split, is skipped (accounts_skipped), never forced.
  • With nothing to change the apply answers 200 applied=false, but a dry run (and MCP staging) answers 409 OB_SPLIT_NOTHING_TO_DO: there is nothing to approve.
  • Over 100 new lines run as several inline rättelser. A failure after one committed answers the error with details.accounts_changed and details.rattelse_log_ids; the books stay consistent and a rerun continues from there.

Risk: high · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
expected_fingerprintstringnofingerprint from the preview you reviewed. The split is refused if it changed since.

Response fields

NameTypeDescription
fiscal_period_idstring
journal_entry_idstringThe IB verifikat, corrected in place (same id).
appliedbooleanFalse when the IB already matched the split: nothing was written.
accounts_changedstring[]
accounts_skippedobject[]
lines_strucknumber
lines_addednumber
rattelse_log_idsstring[]
fingerprintstring

Example request

{
  "expected_fingerprint": "3f9a…"
}

Example response

{
  "data": {
    "fiscal_period_id": "7b3a…",
    "journal_entry_id": "4d2a…",
    "applied": true,
    "accounts_changed": [
      "1470"
    ],
    "accounts_skipped": [],
    "lines_struck": 1,
    "lines_added": 2,
    "rattelse_log_ids": [
      "b7d2…"
    ],
    "fingerprint": "3f9a…"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/reopen-external

fiscal-periods.reopen-external · scope bookkeeping:write

Undo klarmarkera: reopen a year marked closed in the previous system.

Reopens and unlocks a räkenskapsår that close-external closed, and writes the decision to the audit log. Only while that close is still the klarmarkera one: a year closed by a year-end run in Accounted is never reopened. Typical need: the prior-year SIE file was wrong and must be replaced. Idempotent. Dry-runnable.

Use when: A year was marked closed in the previous system by mistake, or its imported contents must be replaced.

Don't use for: Reopening a year closed by a year-end here (not possible), or unlocking a locked year (POST /fiscal-periods/{id}/unlock).

Pitfalls

  • An open year answers 409 PERIOD_REOPEN_NOT_CLOSED.
  • A year closed by a year-end run here answers 409 PERIOD_REOPEN_NOT_EXTERNAL.
  • The lock is cleared too: lock or klarmarkera the year again once the correction is done.

Risk: high · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Response fields

NameType
idstring
namestring
period_startstring
period_endstring
is_closedboolean
closed_atstring | null
closed_externallyboolean
locked_atstring | null
previous_period_idstring | null
created_atstring

Example response

{
  "data": {
    "id": "a8f1…",
    "name": "Räkenskapsår 2024",
    "period_start": "2024-01-01",
    "period_end": "2024-12-31",
    "is_closed": false,
    "closed_at": null,
    "closed_externally": false,
    "locked_at": null,
    "previous_period_id": "5c2e…",
    "created_at": "2026-12-01T09:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/unlock

fiscal-periods.unlock · scope bookkeeping:write

Unlock a locked (not closed) fiscal year.

Clears locked_at so the year accepts postings again, and writes the unlock to the audit log (BFNAR 2013:2 behandlingshistorik). A closed year is never unlocked: past a close, corrections go into an open year as storno. Re-lock with POST /fiscal-periods/{id}/lock after the correction. Idempotent. Dry-runnable.

Use when: The user asked to correct something in a locked year, or a year-end must run on a year that was locked beforehand.

Don't use for: Getting a booking past a lock without the user asking for that correction, or reopening a closed year (a year marked closed in a previous system: POST /fiscal-periods/{id}/reopen-external).

Pitfalls

  • A closed year answers 409 PERIOD_UNLOCK_CLOSED; an unlocked one 409 PERIOD_UNLOCK_NOT_LOCKED.
  • The company-wide lock date (bookkeeping_locked_through) is a separate lock this does not touch.

Risk: high · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Response fields

NameType
idstring
namestring
period_startstring
period_endstring
is_closedboolean
closed_atstring | null
closed_externallyboolean
locked_atstring | null
previous_period_idstring | null
created_atstring

Example response

{
  "data": {
    "id": "a8f1…",
    "name": "Räkenskapsår 2027",
    "period_start": "2027-01-01",
    "period_end": "2027-12-31",
    "is_closed": false,
    "closed_at": null,
    "closed_externally": false,
    "locked_at": null,
    "previous_period_id": "5c2e…",
    "created_at": "2026-12-01T09:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/fiscal-periods/:id/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

Response fields

NameType
operation_idstring
type"fiscal_periods.year_end"
status"queued" | "running" | "succeeded" | "failed"
poll_urlstring
webhook_event"operation.completed"

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"
  }
}

PATCH /api/v1/companies/:companyId/fiscal-periods/:id

fiscal-periods.update · scope bookkeeping:write

Rename or re-date an open fiscal year.

Sparse update of an open, unlocked räkenskapsår: name, period_start, period_end. The name can change at any time on an open year. The dates can move also when the year already holds verifikat, as long as nothing it holds is affected: every verifikat and draft stays inside the new dates, posted opening balances keep the start, and no lock, year-end work, filing or SIE hold covers the change. New dates follow the same BFL 3 kap. rules as create (18-month cap, month-end end, 1st-of-month start unless it is the earliest year, calendar year for an enskild firma), may not overlap another year and keep the chain to the neighbouring years. Idempotent. Dry-runnable.

Use when: A year was created with the wrong dates or name, for example a first year that starts a day after the registration date at Bolagsverket.

Don't use for: Moving verifikat between years, changing a locked or closed year, or re-dating a year whose annual report or declaration is already filed.

Pitfalls

  • A re-date that would affect what the year holds answers 409 FISCAL_PERIOD_DATES_BLOCKED; details.blockers names every reason (for example entries_outside with the first and last date outside the new dates). Send only name to rename.
  • A locked year answers 409 FISCAL_PERIOD_UPDATE_LOCKED, a closed one 409 FISCAL_PERIOD_UPDATE_CLOSED.
  • Only the earliest year can move its start and only the latest year its end: a boundary shared with a neighbouring year answers 400 FISCAL_PERIOD_NOT_CONTIGUOUS.

Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequired
namestringno
period_startstringno
period_endstringno

Response fields

NameType
idstring
namestring
period_startstring
period_endstring
is_closedboolean
closed_atstring | null
closed_externallyboolean
locked_atstring | null
previous_period_idstring | null
created_atstring

Example request

{
  "period_end": "2027-06-30"
}

Example response

{
  "data": {
    "id": "a8f1…",
    "name": "Räkenskapsår 2027",
    "period_start": "2027-01-01",
    "period_end": "2027-06-30",
    "is_closed": false,
    "closed_at": null,
    "closed_externally": false,
    "locked_at": null,
    "previous_period_id": "5c2e…",
    "created_at": "2026-12-01T09:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

PATCH /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/compliance

arsredovisning.update-compliance · scope bookkeeping:write

Answer the årsredovisning compliance questions and record the confirmations.

Sparse update of the compliance profile the ÅRL, K2 and K3 checks read: publikt bolag, likvidation, listed securities, moderföretag and group size, foreign branch, crypto, share-based payments, convertibles, building revenue share, deferred tax, reporting currency, revisionsberättelse, dividend prudence. narrative_confirmed, k2_assessment_confirmed and signer_roster_confirmed record (true) or withdraw (false) the user's confirmations with a timestamp. Answers the recomputed eligibility, validation and capabilities. Idempotent. Dry-runnable.

Use when: The validation (gnubok_validate_arsredovisning) reports an unanswered compliance question or a missing confirmation.

Don't use for: The document texts (POST .../arsredovisning/narrative) or the signer roster itself (POST .../arsredovisning/signatures).

Pitfalls

  • An unanswered question is null, never false: false is a legal assertion that the condition does not apply.
  • signer_roster_confirmed asserts the roster matches the board and VD registered at Bolagsverket; any later roster change clears it.
  • is_parent_company false also clears parent_group_size and prepares_consolidated_accounts.
  • Send at least one field (400 VALIDATION_ERROR otherwise).

Risk: medium · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
is_public_limited_companyboolean | nullno
is_in_liquidationboolean | nullno
securities_traded_on_regulated_marketboolean | nullno
is_parent_companyboolean | nullno
parent_group_size"none" | "small" | "large" | nullno
prepares_consolidated_accountsboolean | nullno
has_foreign_branchboolean | nullno
has_crypto_assetsboolean | nullno
has_share_based_paymentsboolean | nullno
has_convertible_debtboolean | nullno
building_revenue_share_pctnumber | nullno
has_material_deferred_taxboolean | nullno
reporting_currency"SEK" | "EUR"no
auditor_report_requiredboolean | nullno
auditor_report_includedbooleanno
dividend_prudence_confirmedboolean | nullno
narrative_confirmedbooleannoThe user has reviewed the förvaltningsberättelse texts.
k2_assessment_confirmedbooleannoThe user confirms the K2 eligibility assessment.
signer_roster_confirmedbooleannoThe user confirms the signer roster matches the board and VD registered at Bolagsverket.

Response fields

NameTypeDescription
profileobjectThe compliance profile answers and confirmation timestamps (annual_report_profile_id, not id).
disclosuresobject
eligibilityobjectK2 eligibility, size classification and digital filing eligibility.
validationobjectDraft-stage validation: ok, error_count, warning_count, issues.
capabilitiesobject
report_summaryobject

Example request

{
  "is_public_limited_company": false,
  "k2_assessment_confirmed": true
}

Example response

{
  "data": {
    "profile": {
      "annual_report_profile_id": "9d3a…",
      "is_public_limited_company": false
    },
    "validation": {
      "stage": "draft",
      "ok": true,
      "error_count": 0,
      "warning_count": 1,
      "issues": []
    },
    "report_summary": {
      "proposed_dividend": 0,
      "distributable_equity": 412000
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

PATCH /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/signatures/:signatureId

arsredovisning.record-signature · scope bookkeeping:write

Record that a signer signed the frozen årsredovisning version, or declined.

status signed records the evidence of a signature made outside the product (a paper original or an advanced e-signature): the ready_for_signature version it is for, the method, where the original is kept (evidence_reference) and the signing time. status declined marks the slot declined. Only a pending slot of this period transitions, once. ÅRL requires each signer to date their signature; the date must fall between the version's finalization and today. Idempotent. Dry-runnable.

Use when: The board has signed the printed or e-signed document and the signatures are to be registered.

Don't use for: Adding signers (POST .../signatures), filing with Bolagsverket, or BankID signing (not available here).

Pitfalls

  • A version that is not ready_for_signature answers 409 ARSREDOVISNING_VERSION_NOT_SIGNABLE.
  • A slot already signed or declined, bound to another version, or of another period answers 409 SIGNATURE_INVALID_TRANSITION.
  • A signed_at before finalization or in the future answers 400 ARSREDOVISNING_SIGNATURE_DATE_INVALID.
  • evidence_reference is archive:<ref>, document:<id> or receipt:<ref>, not free text.
  • Recorded signatures cannot be undone: finalize a new version instead.

Risk: medium · Idempotent: yes · Reversible: no · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Request body

NameTypeRequiredDescription
status"signed" | "declined"yes
annual_report_version_idstringnoRequired for signed: the ready_for_signature version.
signing_method"paper_original" | "advanced_e_signature" | "bankid"noRequired for signed.
evidence_referencestringnoRequired for signed: archive:<ref>, document:<id> or receipt:<ref>.
signed_atstringnosigned only: ISO timestamp, defaults to now.

Response fields

NameTypeDescription
signature_idstring
fiscal_period_idstring
annual_report_version_idstring | nullNull while the slot is on the unbound roster.
rolestring
signer_namestring
status"pending" | "signed" | "declined"
signed_atstring | null
signing_method"paper_original" | "advanced_e_signature" | "bankid" | "bolagsverket" | null
evidence_referencestring | null
evidence_recorded_atstring | null
created_atstring

Example request

{
  "status": "signed",
  "annual_report_version_id": "4e7d…",
  "signing_method": "paper_original",
  "evidence_reference": "archive:AR-2026-1",
  "signed_at": "2027-03-05T10:00:00Z"
}

Example response

{
  "data": {
    "signature_id": "5b1c…",
    "fiscal_period_id": "7c2b…",
    "annual_report_version_id": "4e7d…",
    "role": "Styrelseledamot",
    "signer_name": "Anna Andersson",
    "status": "signed",
    "signed_at": "2027-03-05T10:00:00Z",
    "signing_method": "paper_original",
    "evidence_reference": "archive:AR-2026-1",
    "evidence_recorded_at": null,
    "created_at": "2027-03-02T09:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

DELETE /api/v1/companies/:companyId/fiscal-periods/:id/arsredovisning/signatures/:signatureId

arsredovisning.remove-signatory · scope bookkeeping:write

Remove a signer from the årsredovisning roster before a version binds it.

Deletes a pending signer slot that no version has bound yet. Slots bound to a finalized version stay (they are part of what gets signed): finalize a new version with the corrected roster instead. Any roster change clears the signer roster confirmation. Idempotent. Dry-runnable.

Use when: A signer was added by mistake or has left the board before the version is finalized.

Don't use for: Declining a bound slot (PATCH .../signatures/{signatureId} {"status": "declined"}).

Pitfalls

  • A bound, signed or declined slot answers 409 ARSREDOVISNING_SIGNER_ROSTER_LOCKED.
  • Takes no body.

Risk: low · Idempotent: yes · Reversible: yes · Dry-run supported: yes

Query parameters

NameTypeRequiredDescription
dry_runstringnotrue (any case) previews the write without committing it, like the X-Dry-Run: true header. Any other value commits.

Response fields

NameType
signature_idstring
deletedtrue

Example response

{
  "data": {
    "signature_id": "5b1c…",
    "deleted": true
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}