Bearer tokens

In production both services require an OIDC JWT bearer token on every request — there are no anonymous endpoints:

  curl https://api.example.com/ledger/v1/balances?… \
  -H "Authorization: Bearer <JWT>" \
  -H "X-Tenant-ID: org-84120557"
  

In local development (dev/test profiles) OIDC is disabled and the tenant falls back to a configured development tenant — you can call the API without a token.

Tenant scoping

Send the organization id in the X-Tenant-ID header on every request. If the header is missing or blank, the API responds 400. The token must grant access to that same organization, otherwise the API responds 403.

Permissions

Permissions live in the JWT claim https://sirk.app/permissions and follow the pattern {orgId}:{action}:{resource}:

OperationRequired permission
Reads — balances, lines, subledgers, journal, reports, event inquiry, rule sets, live event stream{orgId}:read:ledger
Writes — record transactions and corrections, submit events and batches, workflow tasks, tax computation{orgId}:write:ledger
Registry reads — list or fetch dimension entities (parties, employees, assets, projects, categories){orgId}:read:registry
Registry writes — create, update or delete dimension entities{orgId}:write:registry
Filings — submit skattemelding / årsregnskap to Altinn, Skatteetaten or Brønnøysundregistrene{orgId}:submit:filings
Administration — failed-events DLQ (list, read, reprocess, discard), dimension purge, workflow-task deletion, tenant task flags{orgId}:admin:ledger

Holding write:ledger implies read:ledger, and write:registry implies read:registry. The ledger and registry namespaces are independent — write:ledger implies neither registry permission — and neither submit:filings nor admin:ledger is implied by anything; they must be granted explicitly.

The organization id is taken from the X-Tenant-ID header (or, if absent, from the token’s https://sirk.app/organizations claim). The token must also list that organization in its https://sirk.app/organizations claim.

Only exact {orgId}:{action}:{resource} grants are honoured — there is no organization-level bridge.

WebSocket authentication

The live event stream at ws://…/ledger/v1/events is open at the HTTP layer; authentication happens in the first message, where the client sends its token as part of the subscribe request. See Journal & event stream.

Failure responses

StatusBodyCause
400{"error":"missing_tenant", …}Missing or blank X-Tenant-ID (and no organization in the token to fall back to)
403{"error":"not_a_member", …}The token’s https://sirk.app/organizations claim does not contain the addressed tenant
403{"error":"missing_permission","detail":"{orgId}:{action}:{resource}"}Token lacks the required permission — detail names exactly which one