---
title: "Read consumer's own audit trail for a company"
method: GET
path: "/api/v1/company/{org}/audit"
tags: ["Company"]
---

# Read consumer's own audit trail for a company

`GET /api/v1/company/{org}/audit`

## Request

- Base URL: `https://www.apier.no`
- URL: `https://www.apier.no/api/v1/company/{org}/audit`
- Auth: HTTP bearer

Returns the slice of the immutable Sporingslogg (audit_log table) that THIS consumer wrote against the URL's org_number. Cross-consumer visibility is structurally impossible — the reader filters on `consumer_id = <authed-consumer>` AND `org_number = <url-org>` as a parameterized binding before any other predicate, and rows belonging to any other consumer never appear regardless of scope, role, or operator status. Useful for consumers reconciling what their own agents did (reading every `delegation.create` / `gov_api.call` row an agent generated for a company — see the closed `action` enum below for the full vocabulary), AI-agent operators debugging filing flows (filter on `initiated_by=agent`), and dispute defense (Skatteforvaltningsforskriften and bokføringsloven both impose retention obligations on the agent of record; this endpoint provides the forensic trail). The response surfaces `correlation_id` (joins to provenance_log, evaluation_snapshots, compliance_state_events from the same request), `initiated_byschema_version` (the OpenAPI contract version at write time so a future schema break is traceable). Sensitive keys in the `details` JSONB are AUTO-REDACTED to `[REDACTED]` by the read-side scrubber (deny-list pattern: any key containing token / secret / password / bearer / authorization / jwt / api_key / private_key / credential / client_secret / refresh_token, case-insensitive); the write-side redactor is the primary defence and the read-side scrubber is belt-and-suspenders for any pre-correlation-id rows. Pagination is keyset on (timestamp DESC, id DESC) with `limit + 1` lookahead — `next_before` and `next_before_id` from the previous response form the cursor for the next page; rows with identical timestamps tiebreak deterministically on id so a paged scan never duplicates or skips. Empty result for a valid (consumer, org) pair returns 200 with the full envelope `data: { data: [], pagination: { limit, has_more: false } }` (NEVER 404 — that would leak existence across consumers). Query parameters validated by Zod at route boundary: `limit` is `z.coerce.number().int().min(1).max(200).default(50)` — string→number coercion of valid numeric inputs (`?limit=50` → `50`) IS part of the contract because URL query parameters arrive as strings via `URLSearchParams`; non-numeric or out-of-range inputs (`?limit=abc`, `?limit=0`, `?limit=999`) fail validation explicitly rather than silently normalising; `before` is `z.string().datetime({ offset: true })`; `before_id` is `z.string().uuid()`; `since` and `until` are `z.string().datetime({ offset: true })` (the `{ offset: true }` modifier accepts ISO 8601 with explicit `Z` or `+HH:MM` offset — bare local times are rejected); `action` and `initiated_by` are closed enums (see schemas). Validation uses `safeParse` returning a structured 400 response with `error_code: VALIDATION_FAILED` and per-field `details[]` on any failure — no swallowed errors, no silent dropping of invalid fields. Source:.

## Path parameters

- `org` string, required

## Query parameters

- `limit` integer
- `cursor` string
- `before` string, date-time
- `before_id` string, uuid
- `since` string, date-time
- `until` string, date-time
- `action` 'delegation.create' | 'delegation.revoke' | 'approval_token.mint' | 'approval_token.used' | 'api_key.create' | 'api_key.revoke' | 'gov_api.call' | 'registry.upsert' | 'system.cron_run' | 'privacy.dsr' | 'subscription.create' | 'subscription.delete' | 'webhook.delivered' | 'dry_run_execute' | 'risk_elevated_response' | 'risk_elevated_shadow' | 'risk_pattern_override_applied' | 'submit_mva_started' | 'submit_mva' | 'submit_mva_failed' | 'execute_authorization_failed' | 'rate_limit_blocked' | 'idempotency_replay' | 'altinn.list_acting_capacity' | 'brreg.get_company_profile' | 'write_conflict_blocked': Closed enumeration of audit_log action identifiers — single source of truth for both response bodies and query-parameter filters on the audit-trail read surface. Mirrors `AUDIT_ACTIONS` Adding a new value is a one-line append HERE that propagates via $ref to every consumer. Append-only — renaming a value is a /v2 break.
- `initiated_by` 'human' | 'agent' | 'cron' | 'system' | 'unknown': Closed enumeration of `initiated_by` classifier values — single source of truth for both response bodies and query-parameter filters on the audit-trail read surface. Mirrors `INITIATED_BY_VALUEShuman` = write action confirmed via the X-Approval-Token UI flow. `agent` = self-identifying AI client OR an unattended write without approval. `cron` = scheduled job hitting /api/cron/* with the configured CRON_SECRET. `system` = reserved for future internal service-to-service calls. `unknown` = read verbs without other signals, plus the historical-row backfill default for pre-correlation-id rows.

## Response `200`

Page of audit entries for THIS consumer × THIS org, ordered timestamp DESC, id DESC. Pagination via the `next_before` + `next_before_id` cursor pair when `has_more` is true. Empty data array when the consumer has no audit rows for the org (NEVER 404 — that would leak existence across consumers).

- object
  - `success` true, required
  - `data` object, required
    - `data` AuditLogEntry[], required
      - `id` string, uuid, required: Row identifier (audit_log.id) — also the deterministic tiebreaker in the (timestamp, id) ordering used for keyset pagination on the read surface.
      - `timestamp` string, date-time, required: ISO 8601 UTC timestamp when the action was recorded. TIMESTAMPTZ on the column; serialised with explicit timezone.
      - `org_number` string, required: 9-digit Norwegian organisation number this audit row was written for. Non-nullable on this read surface — although the underlying audit_log column IS nullable to allow system-scoped rows (api_key.create, system.cron_run) that have no org context, the GET /api/v1/company/{org}/audit reader filters on `eq('org_number', <url-org>)` so a null value cannot pass through this projection. Generated clients see the tightened contract and do not need a dead null branch. A future endpoint that exposes system-scoped rows would need its own response schema (or wrap AuditLogEntry with `oneOf` to re-permit null).
      - `action` 'delegation.create' | 'delegation.revoke' | 'approval_token.mint' | 'approval_token.used' | 'api_key.create' | 'api_key.revoke' | 'gov_api.call' | 'registry.upsert' | 'system.cron_run' | 'privacy.dsr' | 'subscription.create' | 'subscription.delete' | 'webhook.delivered' | 'dry_run_execute' | 'risk_elevated_response' | 'risk_elevated_shadow' | 'risk_pattern_override_applied' | 'submit_mva_started' | 'submit_mva' | 'submit_mva_failed' | 'execute_authorization_failed' | 'rate_limit_blocked' | 'idempotency_replay' | 'altinn.list_acting_capacity' | 'brreg.get_company_profile' | 'write_conflict_blocked', required: Closed enumeration of audit_log action identifiers — single source of truth for both response bodies and query-parameter filters on the audit-trail read surface. Mirrors `AUDIT_ACTIONS` Adding a new value is a one-line append HERE that propagates via $ref to every consumer. Append-only — renaming a value is a /v2 break.
      - `gov_api_response_status` integer, nullable, required: HTTP status from an upstream government API call, when applicable. Null for non-gov actions.
      - `rule_version` string, nullable, required: Version of the Rulebook under which the action was evaluated, when the action was rule-driven. Null when the action did not invoke the Rulebook.
      - `system_user_id` string, nullable, required: Altinn 3 System User identifier, when the action pertains to one. Null for non-Altinn actions.
      - `correlation_id` string, uuid, required: Request-scope UUID that joins this audit row to other artefacts from the same HTTP request — provenance_log.correlation_id, evaluation_snapshots.correlation_id, compliance_state_events.correlation_id, api_audit_log.correlation_id. Threaded by on every inbound /api/v1/* request; consumers can use it to reconstruct the full forensic slice of one HTTP call across all four logs.
      - `initiated_by` 'human' | 'agent' | 'cron' | 'system' | 'unknown', required: Closed enumeration of `initiated_by` classifier values — single source of truth for both response bodies and query-parameter filters on the audit-trail read surface. Mirrors `INITIATED_BY_VALUEShuman` = write action confirmed via the X-Approval-Token UI flow. `agent` = self-identifying AI client OR an unattended write without approval. `cron` = scheduled job hitting /api/cron/* with the configured CRON_SECRET. `system` = reserved for future internal service-to-service calls. `unknown` = read verbs without other signals, plus the historical-row backfill default for pre-correlation-id rows.
      - `schema_version` string, required: OpenAPI contract version at the moment of the audit write — sourced `info.version` Lets a future schema break be traced to the audit rows that predate / postdate it.
      - `details` object, required: Action-specific JSONB metadata. AUTO-REDACTED at two layers — writer-side before INSERT, and read-side before the row leaves the API. Any key (case-insensitive substring) containing token / secret / password / bearer / authorization / jwt / api_key / private_key / credential / client_secret / refresh_token has its value collapsed to the literal string `[REDACTED]`. Shape varies by action; agents should treat it as opaque unless they recognise the action.
    - `pagination` AuditPagination, required: Keyset-pagination metadata for the audit-trail read surface. The CANONICAL continuation token is `next_cursor` - an opaque HMAC-signed cursor passed back verbatim as `cursor=` for the next page. The legacy keyset tuple - `next_before` + `next_before_id`, the (timestamp, id) pair of the LAST ROW OF THIS RESPONSE'S `data` array, passed back as `before=` AND `before_id=` - keeps working through the deprecation window (Sunset: 2027-01-31T00:00:00Z); requests using it receive Deprecation/Sunset/Link response headers. The runtime only emits continuation fields inside the `has_more && rows.length > 0` guard, so a terminal page lands with all three absent. The schema enforces the legacy cursor as a tuple at THREE complementary layers: (1) `dependentRequired` rejects the half-tuple shape; (2) when `has_more: true`, the continuation fields are REQUIRED via `allOf`/`if/then`; (3) when `has_more: false`, they MUST BE ABSENT.
      - `limit` integer, required: Echo of the page size in effect for this response.
      - `has_more` boolean, required: True when older rows beyond this page exist. Computed via the limit+1 lookahead — the (limit+1)-th row is fetched but not returned; only its presence is surfaced here. When true, both `next_before` and `next_before_id` are required; when false, both MUST BE ABSENT (enforced by the schema-level `allOf`/`if/then` clauses).
      - `next_before` string, date-time: Timestamp of the last row in `data`. Pass back as `before=` for the next page. ABSENT when has_more is false (terminal page); REQUIRED when has_more is true. All-or-nothing tuple companion of `next_before_id`.
      - `next_before_id` string, uuid: Id of the last row in `data`. Pass back as `before_id=` for the next page. ABSENT when has_more is false (terminal page); REQUIRED when has_more is true. All-or-nothing tuple companion of `next_before` — the keyset cursor is (timestamp, id).
      - `next_cursor` string: Opaque HMAC-signed continuation token - the CANONICAL way to fetch the next page. Pass back verbatim as `cursor=`. ABSENT when has_more is false (terminal page); REQUIRED when has_more is true. Tamper-evident: an edited cursor is rejected with 400 CURSOR_INVALID.
  - `_meta` object, required: Metadata included in every response for trust and verifiability.
    - `rulebook_version` string, required: Current Rulebook version. Internal rule-version records carry structured origin metadata (`initial_seed` | `human_review` | `lovdata_drift_response` | `audit_finding` | `customer_question` | `legal_correction`) for operator forensics, but that attribution is deliberately NOT exposed on public responses to avoid leaking operational signal.
    - `data_freshness` string, date-time, required: When this data was last fetched (ISO 8601 UTC).
    - `last_verified` string, date-time, required: When this data was last verified against source (ISO 8601 UTC).
    - `source` string, required: Origin of this data. Defaults to `apier.no`. The agent-readiness probe (`GET /api/v1/health/agent-readiness`) is the one surface that may instead emit `apier.no telemetry` — it does so when at least one of its endpoint rates came from measured live telemetry, and falls back to `apier.no` when every rate is a static default; all other endpoints always emit `apier.no`.
    - `data_source` string: Authoritative origin of the underlying data (listed alongside rulebook_version, data_freshness, last_verified, legal_basis for Rulebook-influenced endpoints). Distinct from `source` (the response origin): `data_source` identifies the authoritative data publisher — e.g. 'Brønnøysund Enhetsregisteret + Apier Universal Rulebook'. Present on registry-backed endpoints.
    - `served_from` 'cache' | 'live' | 'static': Cache-aware endpoints only — 'cache' when the response was served from the local copy, 'live' when the gateway just refreshed from the upstream, 'static' when the payload was computed deterministically from pure code with no DB or upstream read (e.g. /v1/public/deadlines). Absent on endpoints that don't carry this semantic.
    - `stale` boolean: Cache-aware endpoints only — true when the upstream was unreachable and a within-limit cached row was served as a fallback. Absent on normal responses.
    - `schema_version` string, required: OpenAPI contract version at the time of the response; matches `info.version`. Always present on every `/v1/*` response so a future schema break can be traced to the exact rows / audit events that predate / postdate it. Agent-facing machine interfaces (v1/capabilities, workflows.json) may override with their own narrower surface version; all other endpoints inherit the manifest-level version.
    - `legal_basis` string: Legal basis under which the response data is reused. Present on Rulebook-influenced endpoints (v1/company/{org}/obligations, /v1/company/{org}/deadlines). Common value: 'NLOD — public registry reuse'.
    - `rule_count` integer: Rule-evaluation count — total rules evaluated for this request. Present on endpoints that invoke the Rulebook evaluator (v1/company/{org}/obligations).
    - `applicable_count` integer: Count of rules whose evaluation_result is 'applicable'. Present on evaluator endpoints.
    - `not_applicable_count` integer: Count of rules whose evaluation_result is 'not_applicable'. Present on evaluator endpoints.
    - `insufficient_data_count` integer: Count of rules whose evaluation_result is 'insufficient_data' (Tier 2 gated). Present on evaluator endpoints. rule_count = applicable_count + not_applicable_count + insufficient_data_count.
    - `cache_age_ms` integer, nullable: Cache layer: milliseconds since the cached row was computed, or null on a cache miss (served_from === 'live'). Agents use it alongside data_freshness as a staleness signal. Present on endpoints wired to the cache (v1/company/{org}/obligations, /deadlines, /summary, /v1/public/obligations, /v1/public/deadlines).
    - `response_time_ms` integer: Speed signal: total server-side processing time in milliseconds, measured via performance.now() at the route boundary. Mirrored on the `X-Apier-Response-Time-Ms` header so agents can key on it without parsing the body. Present on every endpoint.
    - `response_timestamp` string, date-time, required: RFC 3339 / ISO 8601 UTC timestamp stamped by the response builder at wire-exit. Mirrored on the `provenance_log.response_timestamp` column by a fire-and-forget writer. Distinct from `data_freshness` (when upstream data was fetched) and `last_verified` (when the citation was verified); this is the moment the envelope left the server. Always populated at runtime — deliberately left out of the `required` list to avoid a cascading contract change that would invalidate every pre-existing example envelope in this spec (append-only response contracts). A future revision may promote it into the `required` list alongside a bulk example refresh.
    - `response_hash` string, required: SHA-256 hash of the canonical-JSON form of the envelope with `_meta` stripped before hashing, prefixed with `sha256:`. Mirrored on `provenance_log.response_hash` so a consumer can independently re-hash the bytes they received and compare against what we persisted (integrity receipt). Stripping `_meta` is load-bearing: volatile fields (response_time_ms, cache_age_ms, served_from) and the hash itself would otherwise feed into the digest and break the cache-hit-bytewise-identical invariant.
    - `source_snapshot_id` string: Optional free-text pointer into an upstream sync snapshot the response derives from — e.g. a Brreg sync's `brreg-<ISO>` snapshot id. Absent when the response is not tied to a specific upstream snapshot (capabilities, tools, static deadlines).
    - `is_sandbox` true: Present and `true` only when the response was generated from sandbox fixture data (currently `ALTINN_MODE=sandbox` on `/api/v1/altinn/list-acting-capacity`). Absent (NOT `false`) on every other response — the absent-vs-false distinction is deliberate so downstream agents branching on `if (response._meta.is_sandbox)` cannot be misled into treating non-sandbox responses as positively-confirmed-not-sandbox. Distinct from the `/v1/sandbox/*` `is_sandbox: true` literal in `SandboxMeta` (which carries the sandbox surface tag); this flag rides through the production `/api/v1/altinn/*` surface to forensically tag a sandbox-mode response that an OEM evaluator received.
    - `cost_nok` number: What this call debited from the caller's prepaid credit balance, in NOK, rendered from the integer-øre ledger (50 øre → 0.5; the ledger itself never leaves whole øre and no float arithmetic touches a balance). Present on the 2xx body of every metered company-data read — the same debit that sets the `X-Credits-Cost-Ore` header, so the two never disagree. `0` when nothing was charged: credit enforcement running dark (the response then also carries `X-Credit-Check: shadow`) or the sandbox surface. Absent on unmetered endpoints and on error envelopes — a refunded 5xx/403/404 claims no cost rather than a wrong one. Lives under `_meta`, which `response_hash` excludes, so the provenance receipt is unaffected; the route's ETag is computed before the credit wrapper stamps it, so a conditional request can still short-circuit to 304. Append-only addition.
    - `wallet_remaining_nok` number, nullable: The prepaid credit balance remaining AFTER this call's debit, in NOK (same rendering as `cost_nok`; mirrors the `X-Credits-Balance-Ore` header). `null` when no balance was read: credit enforcement running dark, or the sandbox surface. Present wherever `cost_nok` is. An agent planning a sequence of metered calls can divide this by the per-call price from GET /api/v1/pricing to know how many reads remain before a 402 INSUFFICIENT_CREDITS. Append-only addition.
    - `spend_cap_remaining_nok` number: What the calling key may still spend under its tighter spend cap (Europe/Oslo calendar day or month) AFTER this call's debit, in NOK (same rendering as `cost_nok`; never below 0). Present on the 2xx body of a metered read only when credit enforcement is live AND the key has a daily or monthly cap (set with PATCH /api/v1/account/keys/{id}); absent otherwise. When it reaches 0 the next metered call on this key returns 402 SPEND_CAP_REACHED until the cap resets. Append-only addition.

## Other responses

- `400`: Validation error — invalid org path, malformed query parameter, since > until, or before/before_id supplied without its companion (the keyset cursor is a tuple).
- `401`: Authentication failed — missing, malformed, invalid, or revoked API key.
- `403`: API key is valid but the consumer's scopes do not include `read:audit` (or the wildcard `read:*` which would also grant it). Default keys created via POST /api/v1/admin/keys land with `["read:*"]` (the createApiKey contract accepts only `consumer_id` + `label` — scope assignment is not exposed on the public admin API today) and DO grant this scope; this 403 fires only when the key was issued with a narrower scope set (e.g. `["read:brreg", "read:changes"]`) via an out-of-band operator workflow. Remediation: ask an operator to issue a NEW key that includes `read:audit` (or `read:*`) via POST /api/v1/admin/keys, then revoke the narrower key via DELETE /api/v1/admin/keys/{id} — the public admin API does not expose scope mutation on existing keys.
- `429`: Rate limit reached on at least one of TWO independent dimensions running in parallel: per-(api_key, endpoint) per-minute (the established per-tier scaling — Free / Starter / Professional / Enterprise unlimited) AND per-(api_key, org_number) per-day (bounds runaway-agent failure modes per client org so one org's traffic cannot starve siblings sharing the same key; daily bucket window is one calendar day in Europe/Oslo, DST-aware). The `X-RateLimit-Dimension` header identifies which bucket tripped: `per_key`, `per_org`, or `both` (when the same request exceeds both — the binding Retry-After is the per-org value because the per-org window is hours/days vs the per-minute window). The `Retry-After` header indicates how many seconds to wait before the binding window resets; `X-RateLimit-Limit` / `X-RateLimit-Remaining` / `X-RateLimit-Reset` describe the per-minute bucket state. The per-day per-org limits per tier (sized at ~100× typical daily volume — the dimension exists to bound failure modes, not to monetize) are: Free 1000/org/day, Starter 5000, Professional 25000, Enterprise unlimited. On rare infrastructure-side failures the gateway fails OPEN and passes the request through with `X-RateLimit-Check: skipped` on the response — clients can treat that as a transient signal that quota enforcement was briefly degraded.
- `500`: Unexpected failure reading the audit trail. Internal details never leak in the response body — only error_code AUDIT_QUERY_FAILED and a generic summary; the full failure context goes to Sentry under the request's correlation_id.

---

[API](https://skmtc.dev/apier/apis/apier-no-api.md) · [All operations](https://skmtc.dev/apier/apis/apier-no-api/llms.txt) · [OpenAPI document](https://skmtc.dev/apier/apis/apier-no-api/revisions/9ec19bc87311?raw)
