---
title: "Data Subject Rights query (GDPR Art 15)"
method: POST
path: "/api/v1/privacy/dsr"
tags: ["Privacy"]
---

# Data Subject Rights query (GDPR Art 15)

`POST /api/v1/privacy/dsr`

## Request

- Base URL: `https://www.apier.no`
- URL: `https://www.apier.no/api/v1/privacy/dsr`
- Auth: none required

Zero-auth GDPR Article 15 transparency endpoint. Accepts EXACTLY ONE of `name` (case-insensitive substring search across cached signaturrett / prokura / board_members / innehaver role-holder names) or `org_number` (9-digit Norwegian organisation number). Returns the cached Tier 1 data Apier holds about the requester, with `data_source` / `legal_basis` / `retention_period` annotations on every field — the response is self-describing without the caller needing to hit /privacy or /trust separately.

**No authentication required.** This is a zero-auth surface — Apier does NOT verify that the queried `name` or `org_number` actually belongs to the requester. The endpoint reports what's cached for the submitted query string; Article 15 verification is the requester's responsibility (per Datatilsynet's self-service interpretation for public-register data). Privacy safeguards: (a) **10 requests / 60s sliding window per IP** — prevents bulk-enumeration; (b) **SHA-256-hashed audit-log row per request** — the queried name is never stored in plaintext; (c) **data minimization** — the response is scoped to what Apier actually caches and never enriches with personal data Apier deliberately doesn't store (no birth dates, no addresses, no national identity numbers). The exact response shape varies by query mode: `name` queries return `role_attestations` (one row per (org × role) match — `matched_name` plus the coarse `role` enum `signaturrett` / `prokura` / `board_member` / `innehaver`, no other fields about the matched person); `org_number` queries return `company_records` with every public Brønnøysund column the route exposes (name, entity_type, NACE codes, status, municipality, signaturrett, prokura, board_members, innehaver), each field carrying its own `data_source` / `legal_basis` / `retention_period` triad. **Array-backed columns** (`nace_codes` and the four role-holder lists) are FLATTENED to comma-separated strings on this surface — `DsrField.value` is `string | null` for every key, never an object array, so callers should not expect structured role-holder objects here. The flattening is the documented data-minimisation contract: only the role-holder name appears, never the role label or signing rule.

**Idempotency-Key not supported on this endpoint.** The repo convention is that POST endpoints under `/api/v1/*` support `Idempotency-Key`. DSR is the documented exemption: it's a GDPR Article 15 transparency surface, not a consumer-data mutation surface. Every request is a separate disclosure event that MUST land its own audit row for legal-defensibility purposes — deduplicating replays would let a data subject's repeat queries silently merge into one audit entry, eroding the forensic record Datatilsynet may inspect. The append-only audit row IS the side effect, and intentionally-correct duplicate audit rows on replay accurately reflect that the data subject queried twice. Privacy controls (rate limit, hashed name, hashed IP) are independently enforced and protect against bulk-enumeration without needing dedup.

**Correction path:** Brønnøysund is the upstream source of truth. Corrections must be requested at Brønnøysund — they propagate to Apier at the next sync cycle (within 24 hours). Deletion of cached rows is handled by emailing the privacy contact (see response.how_to_request_correction).

## Request body

- union: Provide EXACTLY ONE of `name` or `org_number`. Both → 400 VALIDATION_FAILED. Neither → 400 VALIDATION_FAILED. The XOR is enforced by the route's Zod refine and encoded in the OpenAPI schema below via `oneOf` (each branch carries its own `additionalProperties: false`, so the outer schema deliberately omits it to avoid spurious validator failures on the example values).
  - object
    - `name` string, required: Display-name search across cached signaturrett, prokura, board_members, and innehaver entries. Case-insensitive substring match (whitespace trimmed before matching). Must contain at least 2 non-whitespace characters — values like `"a b"` or `" Ola "` are accepted (trim → `"a b"` / `"Ola"`); `" "` and `" a "` are rejected (trimmed length below the floor). The pattern `(\S.*){2}` mirrors the route's `z.string().trim().min(2)` semantics exactly. Mutually exclusive with `org_number`.
  - object
    - `org_number` string, required: 9-digit Norwegian organisation number. Returns the company_records row Apier holds about that org. Mutually exclusive with `name`. Note: org_number for AS / ANS / DA is a business identifier (not personal data); for ENK (enkeltpersonforetak) it is personal data per Datatilsynet's interpretation — this endpoint serves both cases identically.

## Response `200`

Annotated DSR response with `data_source`, `legal_basis`, and `retention_period` on every field.

- object
  - `success` true, required
  - `data` object, required
    - `query` union, required: Echoes the request — exactly one of name or org_number, mirrored as a `oneOf` so generated clients see the same XOR shape the request body enforces. The echoed value is post-Zod (whitespace-trimmed for `name`, character-validated for `org_number`); a client that submitted `" Ola "` will see `"Ola"` here.
      - object
        - `name` string, required: Trimmed name as the route saw it (the request's `name` after `z.string().trim()` ran). Always present when the requester queried by name.
      - object
        - `org_number` string, required: 9-digit Norwegian organisation number echoed verbatim from the request. Always present when the requester queried by org_number.
    - `results` object, required
      - `company_records` DsrCompanyRecord[], required: Populated when `org_number` was queried. Each row carries every public-facing field annotated with the GDPR transparency triad.
        - `org_number` string, required: 9-digit Norwegian organisation number — echoes the queried `org_number`. This is the container identifier; the per-field GDPR triad lives inside `fields`.
        - `company_name` string, required: Display name of the legal entity, as cached from Brønnøysund. Container identifier alongside `org_number`.
        - `fields` object, required: Per-field map of every public-facing column on the `companies` row. The nine keys below are emitted on every `org_number` query — `additionalProperties` stays open so a future column addition is an append-only contract change rather than a /v2 break, but no key beyond the enumerated set is emitted today.
          - `name` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
          - `entity_type` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
          - `nace_codes` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
          - `status` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
          - `municipality` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
          - `signaturrett` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
          - `prokura` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
          - `board_members` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
          - `innehaver` DsrField, required: Per-field annotation tuple returned by the DSR endpoint. Carries the GDPR transparency triad inline so the data subject sees `data_source` / `legal_basis` / `retention_period` without cross-referencing /privacy or /trust. Array-backed columns on the underlying `companies` table (`nace_codes`, `signaturrett`, `prokura`, `board_members`) are joined into a comma-separated string by the DSR endpoint before annotation — `value` is therefore `string | null` for every key in the fields map.
            - `value` string, nullable, required: The cached value for this field, joined to a comma-separated string for array-backed columns. `null` when the row exists but the field is empty (e.g. a company with no `municipality`, or a `signaturrett` array with zero entries).
            - `data_source` string, required: Where the data originated. Today always Brønnøysund (data.brreg.no) — Apier does not enrich Tier 1 records from any other source.
            - `legal_basis` string, required: GDPR lawful-basis disclosure for this field. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
            - `retention_period` string, required: Plain-language description of how long this field is retained and the trigger that removes it.
      - `role_attestations` DsrRoleAttestation[], required: Populated when `name` was queried. One attestation per (org × role) match. Each role bucket is scanned independently — a name appearing in signaturrett does NOT cross-contaminate the prokura attestation list.
        - `org_number` string, required: 9-digit Norwegian organisation number of the company where the role match was found.
        - `company_name` string, required: Display name of the company, as cached from Brønnøysund.
        - `role` 'signaturrett' | 'prokura' | 'board_member' | 'innehaver', required: Which role bucket the matched name was found in. Each bucket is scanned independently — a single name can produce multiple attestations across roles (e.g. a board chair who also holds signaturrett returns two attestations on the same company).
        - `matched_name` string, required: Display name as stored on the JSONB row that matched. Returned verbatim so the requester sees the exact value Apier holds — case-canonical correction goes through Brønnøysund (see `how_to_request_correction` on the response envelope).
        - `data_source` string, required: Where this attestation's data originated — today always Brønnøysund (data.brreg.no). Apier does not enrich Tier 1 records from any other source per the data minimisation rule.
        - `legal_basis` string, required: GDPR lawful-basis disclosure for this attestation. References Apier's documented Art 6(1)(f) Legitimate Interest Assessment.
        - `retention_period` string, required: Plain-language description of how long this attestation is retained and the trigger that removes it (typically: until the parent company is deregistered in Brønnøysund, plus a 30-day deletion sync window).
      - `truncated` boolean, required: True when the candidate-row scan hit the per-column LIMIT (100) — Article 15 responses are required to be complete OR honestly indicate when they are not. Pagination support is a follow-up; today the consumer should refine the query (e.g. add a more specific surname) when this flag is true.
    - `privacy_policy_url` string, uri, required: Public privacy policy URL. Built from NEXT_PUBLIC_APP_URL + `/privacy`.
    - `how_to_request_correction` string, required: Static guidance — corrections happen at Brønnøysund (the upstream source); deletions of cached rows are handled by emailing the privacy contact.
    - `expanded_categories_require_org_number` true: Present (and ALWAYS `true`) when only `name` was supplied. Schema locked to `enum: [true]` so generated clients align with the runtime contract — the field is never emitted with `false`; the absence-on-org_number-queries case is signalled by the property being missing entirely, not by `false`. The six expanded categories — `delegations`, `evaluation_snapshots`, `receipts`, `provenance_log`, `changes`, `api_audit_log` — are org-scoped, not name-scoped: a Norwegian person can hold a role on multiple organisations, so disambiguation requires an org_number. The data subject can either provide their org_number directly or first run the name-lookup branch and re-request with a returned org_number from `results.role_attestations[].org_number`.
    - `delegations` object: Append-only, time-bounded Altinn System User authorisations granted by the org_number's authorised signatories. Present on org_number queries. Contains `system_user_id` (an Altinn-side identifier — operator-level personal data per Datatilsynet's interpretation of pseudonymous identifiers) and `scopes` (capability strings). The agent's WHY-this-answer: 's tightening made delegations append-only and time-bounded; the data subject is entitled to know what authorisations are open against their org under GDPR Art 15.
      - `data_source` string, required
      - `legal_basis` string, required
      - `retention_period` string, required
      - `records` object[], required
        - `id` string, uuid, required
        - `org_number` string, required
        - `system_user_id` string, required
        - `scopes` string[], required
        - `valid_from` string, date-time, required
        - `valid_until` string, date-time, nullable, required: Null while the authorisation is open-ended.
        - `created_at` string, date-time, required
      - `hasMore` boolean, required: Pagination signal — true when the row count exceeded the per-helper LIMIT (100). Today the consumer should narrow the query (or wait for cursor pagination).
      - `truncated_due_to_timeout` boolean, required: True when the helper hit its 5 s budget and the records array is empty as a safety measure. The 200 status is preserved either way so the data subject sees a uniform response shape.
    - `evaluation_snapshots` object: Forensic record of every Rulebook evaluation. The agent's WHY: a data subject may have triggered an evaluation with personal data on input — they're entitled to see the audit trail under GDPR Art 15. **Critical data-minimisation contract:** only `inputs_hash` is exposed; the raw `inputs` JSONB is NEVER returned via DSR. The hash is enough for the data subject to match an evaluation against an audit-log row from the same correlation_id; the raw `inputs` recovery is a separate authenticated path through Apier support.
      - `data_source` string, required
      - `legal_basis` string, required
      - `retention_period` string, required
      - `records` object[], required
        - `id` string, uuid, required
        - `correlation_id` string, uuid, required
        - `timestamp` string, date-time, required
        - `endpoint` string, required
        - `rulebook_version` string, required
        - `schema_version` string, required
        - `outcome` unknown, required
        - `inputs_hash` string, required: SHA-256 hash of canonicalized inputs — the raw inputs JSONB is NEVER exposed via DSR (data minimization, GDPR Art 5).
      - `hasMore` boolean, required
      - `truncated_due_to_timeout` boolean, required
    - `receipts` object: Signed submission receipts for filings made via /v1/actions/execute — METADATA ONLY. The agent's WHY: this zero-auth surface applies field minimisation (GDPR Art 5(1)(c), same posture as the authenticated dashboard receipts endpoint): the verbatim government response payload (`government_response_raw`) is NEVER returned here; raw retrieval is a separate authenticated flow. `government_response_hash` (SHA-256 of the FULL upstream payload) lets the data subject verify a payload they already hold, and `government_response_truncated: true` signals that the stored inline projection is the truncated 64-KiB-UTF-8-byte head of a larger payload in the authenticated spill bucket.
      - `data_source` string, required
      - `legal_basis` string, required
      - `retention_period` string, required
      - `records` object[], required
        - `id` string, uuid, required
        - `altinn_receipt_id` string, required
        - `audit_log_id` string, uuid, required
        - `created_at` string, date-time, required
        - `filing_type` string, required
        - `rule_version` string, required
        - `government_response_truncated` boolean, required
        - `government_response_hash` string, required
      - `hasMore` boolean, required
      - `truncated_due_to_timeout` boolean, required
    - `provenance_log` object: SHA-256 hash of every API response served to or about the org_number's records. Linked via audit_log.correlation_id; provenance_log itself has no org_number column. The agent's WHY: a data subject can verify what bytes Apier shipped about them by matching response_hash against an offline copy. Each row's correlation_id links forensically to other audit-chain rows (audit_log, evaluation_snapshots) for the same request — NOT the same correlation_id as this DSR request.
      - `data_source` string, required
      - `legal_basis` string, required
      - `retention_period` string, required
      - `records` object[], required
        - `id` string, uuid, required
        - `correlation_id` string, uuid, required
        - `endpoint` string, required
        - `status_code` integer, required
        - `rulebook_version` string, nullable, required
        - `schema_version` string, required
        - `source_snapshot_id` string, nullable, required
        - `response_timestamp` string, date-time, required
        - `response_hash` string, required
        - `created_at` string, date-time, required
      - `hasMore` boolean, required
      - `truncated_due_to_timeout` boolean, required
      - `truncated_due_to_size` boolean: True when the helper's Step A (audit_log → correlation_ids) hit its 200-distinct-correlation_id cap. The data subject's MOST RECENT 200 distinct correlation_ids were used; older rows exist beyond the cap. reduction from 5000 → 200: keeps Step B's `.in()` IN-list under PostgREST's URL length limit; future hardening via a SECURITY DEFINER RPC can lift the cap. Distinct from `hasMore` (page-100 cap on the final records list) and `truncated_due_to_timeout` (5 s helper budget).
    - `changes` object: Append-only archive of upstream registry changes (Brønnøysund / Altinn / Digdir / Norges Bank) for the org_number's business entity. Filtered at the helper boundary to entity_type IN ('company') so non-org-keyed adapter rows (schema / policy / exchange_rate) cannot leak via 9-digit string collisions on entity_id. The agent's WHY: every detected change to the data subject's org appears here with its before/after JSONB projection. Personal data CAN appear in before_value / after_value / diff when a director or signatory changes (the publisher already strips raw personal fields at the adapter boundary; what remains is the tracked-field projection). Each row's correlation_id links to the audit_log row that triggered the publisher.
      - `data_source` string, required
      - `legal_basis` string, required
      - `retention_period` string, required
      - `records` object[], required
        - `id` string, uuid, required
        - `source` 'brreg', required: Always `brreg` for rows surfaced by DSR — only Brønnøysund-sourced changes carry an org_number-shaped `entity_id` (the helper filters by `entity_type IN ('company')`, which only Brreg emits). Schema-locked via single-element enum so generated clients can rely on the literal value.
        - `entity_type` 'company', required: Always 'company' for rows surfaced by DSR — the helper filters by entity_type IN ('company'). Schema-locked via enum so generated clients can rely on the literal value.
        - `entity_id` string, required
        - `change_type` 'created' | 'updated' | 'deleted', required
        - `field_path` string, nullable, required
        - `before_value` unknown, required
        - `after_value` unknown, required
        - `diff` unknown, required
        - `detected_at` string, date-time, required
        - `source_snapshot_id` string, required
        - `correlation_id` string, uuid, nullable, required: Forensic linkage to the audit_log row that triggered the publisher run. NOT the same correlation_id as the current DSR request.
      - `hasMore` boolean, required
      - `truncated_due_to_timeout` boolean, required
    - `api_audit_log` object: Append-only scope-check audit. Returns rows associated with API keys held by the data subject's consumer. **Current schema:** api_consumers has no org_number column, so the helper cannot link records to an org_number; the response carries an empty records array and an honest data_source explanation. **Future schema:** when api_consumers gains an org_number column, the helper rewrites to perform the three-step JOIN (api_consumers → api_keys → api_audit_log) with explicit .select('id') on the api_keys intermediate so api_keys.key_hash is NEVER selected even for internal IN-list use. **`key_hash` is INTENTIONALLY ABSENT from the record schema** (three-layer defense: helper allow-list, type system, paired data-minimisation positive-control test).
      - `data_source` string, required
      - `legal_basis` string, required
      - `retention_period` string, required
      - `records` object[], required
        - `id` string, uuid, required
        - `api_key_id` string, uuid, required
        - `endpoint` string, required
        - `scope_required` string, required: The scope the endpoint required at the time of the call (e.g. 'read:brreg').
        - `scope_granted` boolean, required: Whether the API key satisfied the required scope.
        - `correlation_id` string, uuid, required
        - `created_at` string, date-time, required
      - `hasMore` boolean, required
      - `truncated_due_to_timeout` boolean, required
      - `truncated_due_to_size` boolean: True when the helper's Step A consumer-cap or intermediate IN-list cap triggers (CASE A path; never set in current CASE B implementation).
  - `_meta` TrustMetadata, 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: 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: 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. Body returned `VALIDATION_FAILED` for any of: (a) both `name` AND `org_number` provided; (b) neither provided; (c) `org_number` not a 9-digit string; (d) `name` falling below 2 non-whitespace characters after trimming (`" "`, `" a "`, etc.); (e) `name` longer than 200 characters; (f) request body not valid JSON; (g) unknown top-level field present (Zod strictObject — No Silent Fallbacks). Per-issue `explanation.details` enumerate each failed field for client diagnostics.
- `429`: Per-IP rate limit reached (10 requests / 60-second sliding window). Retry-After header indicates the seconds until the oldest in-window timestamp expires. Limit is privacy-protected, intentionally lower than Category A's 1000/min — DSR is an Article 15 transparency surface, not a discovery one. Both `Retry-After` and `Cache-Control` headers coexist on this response.
- `500`: Server error during the cached-data lookup or the response-shaping step. The route's outer try/catch funnels every unexpected throw into a structured `SERVER_ERROR` envelope (no stack traces, no raw DB errors). Distinct error codes (`SERVER_ERROR` for both lookup failures and the catch-all path) keep the public contract narrow; the Sentry-side `error_code` (`DSR_LOOKUP_FAILED` / `DSR_UNEXPECTED`) gives operators the granularity for triage.

---

[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)
