Menu

Dimensions

Cost-centre / project dimensions and their values for tagging journal lines.

Endpoints


GET /api/v1/companies/:companyId/dimensions

dimensions.list · scope reports:read

List dimensions (kostnadsställe/projekt) with their values.

Returns the company's dimension registry: SIE #DIM entries keyed by sie_dim_no (1 = Kostnadsställe, 6 = Projekt; both always exist): with the registered values (#OBJEKT) nested under each dimension. Dimensions are ordered by sort_order, values by code. Line-level tags on journal entries reference these values as {"<sie_dim_no>":"<code>"} in the dimensions map.

Use when: You need the valid dimension value codes before tagging journal-entry lines with a cost centre or project, or you are rendering a dimension picker.

Don't use for: Filtering reports (pass the dimension filter to the report endpoints once available) or reading which lines carry a tag (read the journal entries themselves).

Pitfalls

  • Dimension value codes are STRINGS and case-sensitive: "P001", not 1.
  • sie_dim_no is the key used in journal_entry_lines.dimensions, NOT the dimension row id.
  • is_active=false values are historical (archived): do not tag new lines with them.
  • resets_annually=true (dim 1) means balances reset each fiscal year; dim 6 (projekt) accumulates across years.

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

Response fields

NameType
dimensions[].idstring
dimensions[].sie_dim_nonumber
dimensions[].namestring
dimensions[].parent_sie_dim_nonumber | null
dimensions[].resets_annuallyboolean
dimensions[].is_systemboolean
dimensions[].is_activeboolean
dimensions[].sort_ordernumber
dimensions[].valuesobject[]

Example response

{
  "data": {
    "dimensions": [
      {
        "id": "0e9c…",
        "sie_dim_no": 1,
        "name": "Kostnadsställe",
        "parent_sie_dim_no": null,
        "resets_annually": true,
        "is_system": true,
        "is_active": true,
        "sort_order": 10,
        "values": [
          {
            "id": "a8f1…",
            "code": "BUTIK",
            "name": "Butiken",
            "is_active": true,
            "start_date": null,
            "end_date": null
          }
        ]
      }
    ]
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/dimensions

dimensions.create · scope bookkeeping:write

Create a custom dimension (e.g. Avdelning, Kund, Fordon).

Adds a dimension to the registry (SIE #DIM). Omit sie_dim_no and the next free number from 20 is used: SIE reserves 1-19 for standardized meanings (1 Kostnadsställe, 6 Projekt, 7 Anställd, ...). parent_sie_dim_no declares an #UNDERDIM hierarchy and must name an existing dimension. Add values afterwards with POST /dimensions/{id}/values. Idempotent. Dry-runnable.

Use when: The company wants to follow up on something beyond kostnadsställe and projekt, and no existing dimension fits.

Don't use for: Adding a cost centre or project code: those are values of the system dimensions 1 and 6 (POST /dimensions/{id}/values).

Pitfalls

  • An explicit sie_dim_no that is taken returns 409 DIMENSION_NUMBER_TAKEN; omit it to get the next free number.
  • Numbers 1-19 have standardized SIE meanings: only use one when the dimension really is that (e.g. 7 Anställd).
  • resets_annually defaults to true (balances reset each fiscal year, like kostnadsställe); set false for things that accumulate, like projekt.

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.

Request body

NameTypeRequiredDescription
namestringyesDisplay name, e.g. "Avdelning".
sie_dim_nonumbernoSIE dimension number. Omit for the next free number from 20.
resets_annuallybooleannoWhether balances reset each fiscal year. Default true.
parent_sie_dim_nonumber | nullnoParent dimension number (#UNDERDIM).

Response fields

NameType
dimensionobject

Example request

{
  "name": "Avdelning"
}

Example response

{
  "data": {
    "dimension": {
      "id": "3c1d…",
      "sie_dim_no": 20,
      "name": "Avdelning",
      "parent_sie_dim_no": null,
      "resets_annually": true,
      "is_system": false,
      "is_active": true,
      "sort_order": 100
    }
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

POST /api/v1/companies/:companyId/dimensions/:id/values

dimensions.values.create · scope bookkeeping:write

Create a dimension value (kostnadsställe/projekt code).

Registers a new value (SIE #OBJEKT) under a dimension: e.g. a new project code under dimension 6. Requires Idempotency-Key (UUID). Supports ?dry_run=true to validate the code format without committing. The :id path segment is the dimension row id (from GET …/dimensions), not the sie_dim_no. Duplicate codes within the dimension return 409 DIMENSION_VALUE_DUPLICATE_CODE.

Use when: A voucher or invoice references a cost centre / project code that does not exist yet and the user has confirmed it should be created.

Don't use for: Renaming or archiving an existing value (dashboard register in v1). Tagging lines: pass the dimensions map on the journal-entry line instead.

Pitfalls

  • Idempotency-Key is mandatory: calls without it return 400 VALIDATION_ERROR.
  • The :id segment is the dimension UUID, not the SIE dimension number.
  • Codes are limited to the strict Fortnox charset (A-Ö, digits, _, +, -; max 20 chars) even though historical imported codes may be looser.
  • code is immutable after creation: there is no rename in v1; create the correct code and archive the wrong one.

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.

Request body

NameTypeRequired
codestringyes
namestringyes
is_activebooleanno
start_datestring | nullno
end_datestring | nullno

Response fields

NameType
idstring | null
dimension_idstring
codestring
namestring
is_activeboolean
start_datestring | null
end_datestring | null
created_atstring | null

Example request

{
  "code": "P001",
  "name": "Villa Almgren tak"
}

Example response

{
  "data": {
    "id": "0e9c…",
    "dimension_id": "a8f1…",
    "code": "P001",
    "name": "Villa Almgren tak",
    "is_active": true,
    "start_date": null,
    "end_date": null,
    "created_at": "2026-07-02T12:00:00Z"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

PATCH /api/v1/companies/:companyId/dimensions/:id

dimensions.update · scope bookkeeping:write

Rename, archive or reorder a dimension.

Sparse update of a dimension: name, is_active (false archives it, hiding it from pickers while history keeps its tags) and sort_order. The system dimensions 1 (Kostnadsställe) and 6 (Projekt) can be archived and reordered but not renamed. sie_dim_no is immutable. Idempotent. Dry-runnable.

Use when: A dimension needs a clearer name, should stop being offered for new tags, or should move in the pickers.

Don't use for: Changing a value (use PATCH /dimensions/{id}/values/{valueId}) or removing a dimension (DELETE).

Pitfalls

  • Renaming a system dimension returns 400 DIMENSION_SYSTEM_RENAME.
  • At least one of name, is_active, sort_order must be sent.

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.

Request body

NameTypeRequiredDescription
namestringno
is_activebooleannofalse archives the dimension.
sort_ordernumberno

Response fields

NameType
idstring
sie_dim_nonumber
namestring
parent_sie_dim_nonumber | null
resets_annuallyboolean
is_systemboolean
is_activeboolean
sort_ordernumber

Example request

{
  "is_active": false
}

Example response

{
  "data": {
    "id": "3c1d…",
    "sie_dim_no": 20,
    "name": "Avdelning",
    "parent_sie_dim_no": null,
    "resets_annually": true,
    "is_system": false,
    "is_active": false,
    "sort_order": 100
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

PATCH /api/v1/companies/:companyId/dimensions/:id/values/:valueId

dimensions.values.update · scope bookkeeping:write

Update a dimension value (rename, archive, set start/end date).

Sparse update of a dimension value (SIE #OBJEKT): name, is_active (false = archive), start_date, end_date. code is immutable: renaming a code would orphan every journal line tagged with it; create a new value and archive the old one instead. Dates are only allowed on accumulating dimensions (resets_annually=false, e.g. dim 6 Projekt): use end_date to close a finished project. Idempotent (mandatory Idempotency-Key) and dry-runnable.

Use when: You need to rename a project/cost-centre, mark a finished project with an end date, or archive (is_active=false) a value that should no longer be used on new lines.

Don't use for: Changing the code (immutable: create + archive instead). Removing an unused value entirely (use DELETE). Tagging lines (pass dimensions on the journal-entry line or invoice).

Pitfalls

  • Idempotency-Key is mandatory.
  • The :id segment is the dimension UUID and :valueId the value UUID (both from GET …/dimensions), not SIE numbers or codes.
  • start_date/end_date return 400 DIMENSION_VALUE_DATES_NOT_ALLOWED on resets_annually dimensions (dim 1 Kostnadsställe).
  • Archived values (is_active=false) still appear in GET …/dimensions and remain valid on historical lines; they are only blocked for NEW tags.

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.

Request body

NameTypeRequired
namestringno
is_activebooleanno
start_datestring | nullno
end_datestring | nullno

Response fields

NameType
idstring
dimension_idstring
codestring
namestring
is_activeboolean
start_datestring | null
end_datestring | null

Example request

{
  "end_date": "2026-08-31",
  "is_active": false
}

Example response

{
  "data": {
    "id": "0e9c…",
    "dimension_id": "a8f1…",
    "code": "P001",
    "name": "Villa Almgren tak",
    "is_active": false,
    "start_date": null,
    "end_date": "2026-08-31"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

DELETE /api/v1/companies/:companyId/dimensions/:id

dimensions.delete · scope bookkeeping:write

Delete a custom dimension nobody has booked on.

Removes a custom dimension and its values. Refused for the system dimensions and for any dimension whose number is tagged on a posted or reversed verifikat line (BFL 7 kap: booked history is never pulled out from under a verifikat). Archive it with PATCH is_active=false instead. Idempotent. Dry-runnable.

Use when: A dimension was created by mistake and nothing has been booked on it.

Don't use for: Retiring a dimension that has been used: archive it (PATCH is_active=false).

Pitfalls

  • A dimension used on any posted line returns 409 DIMENSION_REFERENCED naming it.
  • System dimensions return 400 DIMENSION_SYSTEM_DELETE.

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.

Response fields

NameType
deletedtrue
dimension_idstring

Example response

{
  "data": {
    "deleted": true,
    "dimension_id": "3c1d…"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}

DELETE /api/v1/companies/:companyId/dimensions/:id/values/:valueId

dimensions.values.delete · scope bookkeeping:write

Delete an unreferenced dimension value.

Hard-deletes a dimension value (SIE #OBJEKT) that no journal line references. Values used on posted or reversed verifikat are retained for the BFL 7-year archive and cannot be deleted: the DB trigger blocks it and this endpoint returns 409 DIMENSION_VALUE_REFERENCED. Archive those instead (PATCH is_active=false). Requires Idempotency-Key.

Use when: A project/cost-centre code was created by mistake (typo, duplicate) and has never been used on any booking.

Don't use for: Retiring a project that has bookings: PATCH is_active=false (and optionally end_date) instead. Deleting a whole dimension (not supported).

Pitfalls

  • Idempotency-Key is mandatory.
  • 409 DIMENSION_VALUE_REFERENCED means the value is used on booked verifikat: it can never be deleted, only archived.
  • Deletion is permanent: the code can be re-created afterwards, but the old row id is gone.

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

Response fields

NameType
deletedtrue
idstring

Example response

{
  "data": {
    "deleted": true,
    "id": "0e9c…"
  },
  "meta": {
    "request_id": "req_…",
    "api_version": "2026-05-12"
  }
}