---
title: "Validate (dry-run) or submit (live) a filing action"
method: POST
path: "/api/v1/actions/execute"
tags: ["Actions"]
---

# Validate (dry-run) or submit (live) a filing action

`POST /api/v1/actions/execute`

## Request

- Base URL: `https://www.apier.no`
- URL: `https://www.apier.no/api/v1/actions/execute`
- Auth: HTTP bearer

Two discriminated behaviours on the same endpoint, selected by the `?dry_run=true` query parameter.

**Dry-run mode (`?dry_run=true`)** — validates a filing-action payload against five preflight checks WITHOUT submitting anything to Altinn / Skatteetaten / NAV. Returns 200 with a `DryRunOutcome` body listing each check's pass/fail; 200 is the SUCCESS path even when individual checks failed (the dry-run itself succeeded — check verdicts are content, not transport).

The five checks (run in order, all reported, never short-circuited):
  1. company_exists — does `org_number` exist in Brønnøysundregistrene?
  2. system_user_authorised — is there an active System User delegation for this consumer + this `org_number`?
  3. scopes_delegated — does the delegation carry the upstream Maskinporten scopes the action requires? (For mva_melding: both `altinn:instances.read` and `altinn:instances.write` — the Altinn 3 submission scope pair from RequiredScopesByAction, which Skatteetaten names in place of the retired 2022 scope `skatteetaten:mvameldinginnsending`; they are end-user-system scopes obtained at ID-porten login, so in 2026 a human authenticates with ID-porten to submit the return the agent prepared. Distinct from the `skatteetaten:mva` tier-access baseline shown on /v1/auth/permissions; a_melding live submission is paused — transitioning to Skatteetaten direct integration; dry-run validation remains supported.)
  4. data_format_valid — does the payload pass the per-action Zod schema?
  5. deadline_in_future — is the obligation deadline still in the future at clock time?

**Live-execute mode (`?dry_run` omitted or any value other than `true`)** — submits the filing to Altinn 3 (which routes to the Skatteetaten / NAV service owner). v1 supports `mva_melding` only; `a_melding` live submission is paused (transitioning to Skatteetaten direct integration) and returns 403 PLAN_INSUFFICIENT. MOCK-GATED: with `ALTINN_MODE=mock` (the default — see `/docs/capability-status`) the submission is served by Apier's deterministic mock Altinn adapter rather than a real government call; the response envelope is shaped identically to a live submission, but `altinn_receipt_id` is prefixed `MOCK-ALT-` and the receipt's `government_response_raw` carries a nested `_mock: true` marker. The live adapter activates only once `ALTINN_MODE=live` is configured for a production deployment.

Live-execute applies THREE additional contracts over dry-run:
  - `Idempotency-Key` header is REQUIRED (a missing key returns 400 IDEMPOTENCY_KEY_REQUIRED — network retries without the same key would double-submit).
  - `X-Approval-Token` header is REQUIRED. The token is a single-use opaque string minted via `POST /api/v1/auth/approval-token` and consumed atomically via the `consume_approval_token_atomic` RPC. Rejected tokens map to APPROVAL_TOKEN_INVALID / APPROVAL_TOKEN_USED / APPROVAL_TOKEN_EXPIRED / APPROVAL_TOKEN_MISMATCH; a NOT_FOUND row collapses to APPROVAL_TOKEN_INVALID for existence-disclosure protection.
  - The five dry-run preconditions MUST pass. Failures return 422 VALIDATION_FAILED with the failed check list; the approval token is NOT consumed on this path so the agent can fix inputs and retry without burning a token.

Successful live-execute returns 200 with a HMAC-SHA256-signed receipt envelope (see `SignedReceipt` schema). The signature is HMAC over `canonicalJSON(receipt-without-signature)`; the hashed domain INCLUDES `signature_algorithm` so a downgrade-attack tag flip fails verification.

DISCRIMINATED PERIOD FORMAT — per `action_type`: mva_melding accepts `YYYY-Tn` (n = 1..6) for bimonthly terminer OR `YYYY-A` for annual filers OR `YYYY-MM` for monthly. a_melding is `YYYY-MM` only.

IDEMPOTENCY — `Idempotency-Key` is OPTIONAL on dry-run and REQUIRED on live-execute. When present, the (consumer, endpoint, key) tuple reserves a 24-hour replay slot: a re-send with the same key + same body returns the original response verbatim with `Idempotent-Replay: true`. A re-send with the same key + different body returns 422 IDEMPOTENCY_KEY_MISMATCH; a concurrent in-flight dupe returns 409 IDEMPOTENCY_IN_PROGRESS + Retry-After. Replays are recorded in audit_log with `was_idempotent_replay=true`. The replay audit row carries the SHA-256 hash of the Idempotency-Key (never the plaintext value) and the endpoint pattern, so forensic queries can distinguish retry storms from genuine duplicate intent and join replay history against `idempotency_keys.key_hash` byte-for-byte.

BODY SIZE — bounded at 256 KB. Beyond that the request returns 413 PAYLOAD_TOO_LARGE.

The dry-run response body's `outcome.disclaimer` carries the agent contract: a passing dry-run is NOT a guarantee of submission success — government systems may reject otherwise-valid submissions for reasons outside Apier's view (transient outages, server-side validations beyond format).

## Query parameters

- `dry_run` string

## Headers

- `Idempotency-Key` string
- `X-Approval-Token` string

## Request body

- union: Discriminated union on `action_type` for the top-level request shape. The payload remains intentionally permissive at the transport layer (`type: object`) so malformed payloads still reach the dry-run engine and surface as `data_format_valid=false` on the documented 200 path. The discriminator mapping points at the full request-body schemas so generated clients can still enforce the top-level `action_type` / `period` / `org_number` contract; per-action payload conformance is reported via the `data_format_valid` check, never as a 400.
  - DryRunExecuteMvaRequest: Request body for `POST /api/v1/actions/execute?dry_run=true` with `action_type: "mva_melding"`. Discriminated branch — referenced from the operation's `oneOf` request body schema. Extracted as a named component so the discriminator mapping points at a schema that ACTUALLY contains the `action_type` discriminator property.: `payload` is intentionally permissive (`type: object`) at the transport layer so a malformed payload reaches the dry-run engine and emerges as `data_format_valid: false` in the 200 response — the engine's per-action shape validation IS one of the 5 reportable checks by contract; gating it at the OpenAPI request-body schema would force generated clients to short-circuit those failures into 400s and break the all-checks-reported promise. The expected payload shape is documented separately at `#/components/schemas/MvaMeldingPayload` for SDK type generation.
    - `org_number` string, required: 9-digit Norwegian organisation number.
    - `action_type` 'mva_melding', required: MVA-melding (Norwegian VAT return).
    - `period` string, required: MVA period: `YYYY-Tn` (n=1..6 bimonthly terminer) | `YYYY-A` (annual) | `YYYY-MM` (monthly).
    - `payload` object, required: MVA-melding payload. The dry-run engine's `data_format_valid` check (one of the 5 reportable checks) evaluates per-action shape conformance against `#/components/schemas/MvaMeldingPayload`. Transport-level validation only requires `type: object` so the engine can run all 5 checks regardless of payload validity — a malformed payload returns 200 with `outcome.checks[?check=='data_format_valid'].passed === false`, NOT 400.
  - DryRunExecuteAMeldingRequest: Request body for `POST /api/v1/actions/execute?dry_run=true` with `action_type: "a_melding"`. Discriminated branch — referenced from the operation's `oneOf` request body schema.: `payload` is intentionally permissive (`type: object`) at the transport layer so a malformed payload reaches the dry-run engine and emerges as `data_format_valid: false` in the 200 response — see `DryRunExecuteMvaRequest.payload` for the full rationale.
    - `org_number` string, required: 9-digit Norwegian organisation number.
    - `action_type` 'a_melding', required: A-melding (Norwegian employer report).
    - `period` string, required: A-melding period: `YYYY-MM` calendar month.
    - `payload` object, required: A-melding payload. The dry-run engine's `data_format_valid` check evaluates per-action shape conformance against `#/components/schemas/AMeldingPayload`. Transport-level validation only requires `type: object` so the engine can run all 5 checks regardless of payload validity — a malformed payload returns 200 with `outcome.checks[?check=='data_format_valid'].passed === false`, NOT 400.

## Response `200`

Operation completed. Two discriminated body shapes: dry-run path returns `{data: {dry_run: true, outcome: DryRunOutcome}}`; live-execute path returns `{data: {dry_run: false, altinn_receipt_id, receipt_id, submitted_at, signed_receipt: SignedReceipt}}`. Discriminate on `data.dry_run`.

- object
  - `success` true, required
  - `data` union, required: Discriminated by the `dry_run` property's boolean literal — `true` indicates the dry-run branch (DryRunSuccessData), `false` indicates the live-execute branch (ExecuteSuccessData). Each subschema pins `dry_run` to a single-value enum so SDK generators that don't honour OpenAPI's optional `discriminator` keyword can still discriminate at runtime by reading `data.dry_run`. The explicit `discriminator` block was removed because OpenAPI 3.1 requires the discriminator's property to be a string at runtime, but `dry_run` is boolean; mapping keys `"true"`/`"false"` would never match the JSON values `true`/`false`. The literal-enum approach is spec-compliant AND semantically equivalent to a discriminator for this use case.
    - DryRunSuccessData: Extracted from the inline 200 oneOf so the response can carry an explicit OpenAPI discriminator. The `dry_run: true` literal is the discriminator value SDK generators map to this branch.
      - `dry_run` true, required: Discriminator — confirms the request was processed via the dry-run path.
      - `outcome` DryRunOutcome, required: Top-level dry-run outcome returned to the caller. The `disclaimer` carries the agent contract: a passing dry-run is NOT a guarantee of submission success — government systems may reject otherwise-valid submissions for reasons outside Apier's view. Append-only — `additionalProperties` is intentionally OPEN so future top-level outcome fields can be added without breaking strict-validating clients.
        - `all_passed` boolean, required: True iff every check in `checks[]` passed.
        - `checks` DryRunCheckResult[], required: EXACTLY five entries at v1, in the engine's evaluation order: company_exists, system_user_authorised, scopes_delegated, data_format_valid, deadline_in_future. Order-stable across releases. The fixed-length-tuple invariant — generated clients can rely on positional access.
          - `check` 'company_exists' | 'system_user_authorised' | 'scopes_delegated' | 'data_format_valid' | 'deadline_in_future', required: Closed enum of dry-run check names.
          - `passed` boolean, required: True iff this individual check passed.
          - `error_code` string, nullable, required: Machine-readable error code when `passed` is false; null otherwise. Common values: COMPANY_NOT_FOUND, AUTH_NO_DELEGATION, SCOPE_INSUFFICIENT_FOR_ACTION, VALIDATION_FAILED, DEADLINE_PASSED, COMPANY_LOOKUP_FAILED, AUTH_LOOKUP_FAILED, DEADLINE_LOOKUP_FAILED. Reserved (no longer emitted as of): VALIDATION_PRECONDITION_FAILED — the engine previously emitted this on the deadline check when data_format_valid had failed, but reverted the short-circuit because the deadline lookup uses only org_number / action_type / period (all validated at the route boundary). The code stays in the documented enum so any client that pattern-matched on it continues to typecheck (append-only response contract).
          - `explanation_summary` string, nullable, required: One-line human-readable summary of why this check failed. Null on every passing check EXCEPT the `deadline_in_future` passing-but-noteworthy carveout (the `getDeadline` adapter returned null for the action+period combination, so the check passes with a non-null summary noting the unconstrained state — see `runDeadlineCheckpassed: true` ⇒ `explanation_summary: null`; `passed: false` ⇒ `explanation_summary: <string>`. Norwegian explainer details live on the explainer surface. Field paths only on validation — never values. The SCOPE_INSUFFICIENT_FOR_ACTION branch emits a STATIC summary (consistency); the per-call data lives on `details` instead.
          - `details` object: Optional check-specific structured data. Append-only — `additionalProperties` is intentionally OPEN so future per-check shapes can be added without breaking strict-validating clients. Today two check branches populate this: `scopes_delegated` → SCOPE_INSUFFICIENT_FOR_ACTION emits `action_type` + `missing_scopes` so consumers can programmatically remediate a delegation without parsing `explanation_summary`; `data_format_valid` → VALIDATION_FAILED emits `zod_issues` (redacted Zod issue records — path + code + message, with quoted received-value substrings stripped) so SDK clients can surface the exact field path that failed without inventing their own parser. unrecognized_keys issues surface the client-sent key NAME (one entry per rejected key, secret-shaped names excepted). Absent on passing checks and on most failure branches.: `zod_issues` documented here so the OpenAPI surface matches the runtime contract from.
            - `action_type` 'mva_melding' | 'a_melding': The action_type the check was evaluated against. Mirrors the request body's action_type field. Populated on `scopes_delegated` → SCOPE_INSUFFICIENT_FOR_ACTION.
            - `missing_scopes` string[]: Maskinporten gov-API scopes the consumer's System User delegation is missing for the given action_type. Public identifiers — safe to surface programmatically. EXACT match required at the upstream token level (no wildcard semantics, unlike Apier API-key scopes). Populated on `scopes_delegated` → SCOPE_INSUFFICIENT_FOR_ACTION.
            - `zod_issues` object[]: Redacted Zod issue records from the per-action payload safeParse at the route boundary (Zod runs at boundary; engine consumes the SafeParseResult). Each entry pinpoints the field that failed shape validation. Populated on `data_format_valid` → VALIDATION_FAILED only. The `message` field is auto-redacted of any double- or single-quoted received-value substrings (no internal leakage; Zod's default templating like `Expected number, received string "abc"` gets the value replaced with `<redacted>`) and truncated to 200 chars. The `path` field is the SCHEMA path (e.g. `payload.employees[0].fnr_or_dnr`), not the data — schema paths are NOT PII. The `code` field is Zod's enum tag (`invalid_type`, `too_small`, `unrecognized_keys`, etc.).
              - …
        - `disclaimer` string, required: Fixed-string contract reminder. The exact value is locked; mirrored in /llms-full.txt.
    - ExecuteSuccessData: Extracted from the inline 200 oneOf so the response can carry an explicit OpenAPI discriminator. The `dry_run: false` literal is the discriminator value SDK generators map to this branch. Returned on a successful live-execute. The signed receipt is the integrity record — `signed_receipt.signature` is HMAC-SHA256 over `canonicalJSON(receipt-without-signature)` and includes `signature_algorithm` in the hashed domain (downgrade-tag-flip fails verification). appends `execution_guarantee` + `outcome` to the success envelope.
      - `dry_run` false, required: Discriminator — confirms the request was processed via the live-execute path.
      - `altinn_receipt_id` string, required: Upstream Altinn 3 instance id. Mock mode emits `MOCK-ALT-<16 lowercase hex>`; live mode emits the real Altinn-issued id.
      - `receipt_id` string, uuid, required: Apier-side `receipts` table primary key — the persisted signed receipt's row UUID.
      - `submitted_at` string, date-time, required: RFC 3339 timestamp the upstream accepted the submission. Same value as `signed_receipt.signed_at` (the two always match).
      - `signed_receipt` SignedReceipt, required: HMAC-signed government submission receipt. Returned by `/api/v1/actions/execute` when an action submitted to Altinn / Skatteetaten / NAV produces a receipt. Persisted to the append-only `receipts` table so a future re-verify can confirm Apier issued exactly these bytes at exactly this signing time. Frame this as an integrity receipt, not a proof of legal correctness — Apier signs the wrapper, not the upstream's truth claim. The `government_response_raw` field carries the upstream's response VERBATIM (minimally normalised: timestamps converted from Oslo-local to ISO 8601 with explicit Europe/Oslo offset, keys case-preserved, byte-level content otherwise preserved). Apier does not interpret this payload — it is preserved for defensibility. When the canonicalised form exceeds 256KB the field carries a `LargeGovernmentResponse` pointer instead, with the full bytes on `large_government_responses` joinable via the persisted `large_response_id` column (the join is server-side; the wire form is the same union shape). The `signature` is HMAC-SHA256 over `canonicalJSON(receipt-without-signature)` using the operator-side `RECEIPT_HMAC_SECRET`. The hashed domain INCLUDES `signature_algorithm` — flipping the algorithm tag without recomputing the signature is a downgrade attack and fails verification.
        - `altinn_receipt_id` string, required: Upstream-issued receipt id (Altinn / Skatteetaten / NAV). Forwarded verbatim from the gov API response.
        - `audit_log_id` string, uuid, required: UUID of the `audit_log` row for the action that produced this receipt. Hard FK in the database; the receipt is always preceded by its audit row.
        - `org_number` string, required: 9-digit Norwegian organisation number the receipt belongs to.
        - `filing_type` string, required: Stable machine-readable filing-type identifier (e.g. `mva-melding`, `a-melding`, `skattemelding`). Free TEXT in v1; a future closed enum will be additive.
        - `rule_version` string, required: Rulebook version active at signing time. Frozen on the receipt so a re-verify always reasons against the original rules — not whatever Rulebook version is current at re-verification.
        - `correlation_id` string, uuid, required: Request-scope UUID v4. Joins `audit_log`, `provenance_log`, `evaluation_snapshots`, and `compliance_state_events` on the same forensic axis.
        - `government_response_raw` union, required: The exact JSON received from Altinn / Skatteetaten / NAV on submission. Minimally normalised: timestamps in Norwegian local-time form (`YYYY-MM-DD[ T]HH:mm:ss`) are converted to ISO 8601 with explicit Europe/Oslo offset (`+02:00` CEST / `+01:00` CET, DST-aware via date-fns-tz, never hardcoded). Keys are case-preserved (`MvaPeriode` stays `MvaPeriode`). All other byte-level value content is preserved. Apier does not interpret this payload — it is preserved verbatim for defensibility. When the canonicalised form is >256KB the receipt carries the spill-bucket `LargeGovernmentResponse` pointer here instead; consumers must inspect the shape via the `truncated` discriminator. The inline branch accepts any JSON root (object, array, string, number, boolean, null) — Skatteetaten and Altinn endpoints occasionally return top-level arrays or primitives, and the lib treats `government_response_raw` as `unknown` (preserves verbatim). The branches use `anyOf` (not `oneOf`) because a `LargeGovernmentResponse` instance is also a generic object and would match BOTH branches, failing `oneOf`'s exactly-one constraint — the discriminator the consumer parses is `truncated`, not the JSON-Schema branch. When this receipt was produced by a mock submission (Apier's `ALTINN_MODE=mock` default — see the `/api/v1/actions/execute` operation description), the object carries a nested `_mock: true` boolean as the discoverable signal that no real government system was contacted.
          - object
          - unknown[]
            - unknown
          - string
          - number
          - boolean
          - LargeGovernmentResponse: Compact pointer the receipt row carries on `government_response_raw` when the canonicalised normalised payload exceeded 256KB and was spilled into the `large_government_responses` table. The full bytes live one JOIN away on `large_government_responses.full_payload`; this sentinel is what a forensic peek at the receipt itself sees without joining. Stable shape regardless of upstream payload structure.
            - `truncated` true, required: Discriminator: always true. A non-truncated receipt carries the verbatim payload on `government_response_raw` instead of this sentinel.
            - `byte_length` integer, required: Byte length of the canonicalised normalised payload at signing time. Spill happens only when the canonical form is STRICTLY >256KB; exactly 256KB stays inline. Minimum 262145 (256KB + 1 byte) reflects this invariant — under-threshold payloads cannot legitimately appear in a `LargeGovernmentResponse`.
            - `inline_projection_byte_budget` integer, required: Soft cap on the inline projection size (echoed for transparency). Today: 65536 (64 KB).
            - `hash` string, required: SHA-256 of canonicalJSON(normalised payload), prefixed `sha256:`. Joins to `large_government_responses.response_hash` and matches `receipts.government_response_hash`.
            - `note` string, required: Static guidance pointing at the spill-bucket join — instructive for a human reading the row in isolation.
        - `government_response_hash` string, required: SHA-256 of `canonicalJSON(normalised payload)`, prefixed `sha256:`. Computed against the FULL normalised payload (not the truncated projection) so re-verification can rehash from `large_government_responses.full_payload`.
        - `signed_at` string, date-time, required: RFC 3339 / ISO 8601 timestamp of when the receipt was signed. Caller-supplied (will pass the route's frozen time-source) so the signature is deterministic across retries.
        - `signature_algorithm` 'HMAC-SHA256-v1', required: Versioned algorithm tag for signed submission receipts. v1 today: HMAC-SHA256 over the canonical-JSON form of the receipt body (with the `signature` field excluded; `signature_algorithm` itself IS in the hashed domain — downgrade-attack defence). A future rotation introduces a new value (`HMAC-SHA256-v2` on secret rotation, `Ed25519-v1` on asymmetric migration); historical receipts continue to verify under the algorithm they were signed with because the verifier branches on this column at read time.
        - `signature` string, required: HMAC-SHA256 hex (64 chars). Computed over `canonicalJSON(receipt-without-signature)` using the operator-side `RECEIPT_HMAC_SECRET`. INCLUDES `signature_algorithm` in the hashed domain — downgrade-attack defence.
        - `normalization_rules_applied` string[], required: Diagnostic record of which `normalizeGovernmentResponse` rules fired during signing. Included in the HMAC domain (part of `canonicalJSON(receipt-without-signature)`) so an intermediary cannot rewrite normalization metadata silently — tampering with this list breaks signature verification (Major). Echoed verbatim on the wire for forensic transparency.
      - `execution_guarantee` ExecutionGuarantee, required: Per-call execution provenance. **Where this field appears (clarification, enum-name correction, iter-29 sandbox extension, extension):** - **Production** `POST /api/v1/actions/execute` 200 — live-execute branch only (`data.dry_run === false`). The dry-run branch (`data.dry_run === true`) does NOT carry this field because dry-run skips the orchestrator entirely on the production surface. Post-orchestrator (orchestrator ran, `upstream_healthy` is the authoritative breaker snapshot). - **Production** `POST /api/v1/actions/execute` 502 — `GOVERNMENT_API_ERROR` and the other apier_codes the orchestrator preserves (`EXECUTION_CIRCUIT_OPEN`, `EXECUTION_DEADLINE_EXCEEDED`, `RETRY_BUDGET_EXHAUSTED`, `MASKINPORTEN_AUTH_FAILED`, `ALTINN_DELEGATION_MISSING`, `GOVERNMENT_RATE_LIMITED`, `GOVERNMENT_UNAVAILABLE`, `GOVERNMENT_VALIDATION_REJECTED`, `EXECUTION_FAILED` (post-submit local-failure path: Altinn submit succeeded but signReceipt/persistReceipt threw)). The 10-value subset is exposed as `ApierPostOrchestratorErrorCode`. Post-orchestrator. - **Production** `POST /api/v1/actions/execute` 409 `CONCURRENT_WRITE_IN_FLIGHT` AND 503 `SAFETY_SYSTEM_UNAVAILABLE` — Pre-orchestrator (the conflict-lock gate fires BEFORE consumeApprovalToken / orchestrator). The route synthesizes `execution_guarantee` via `preOrchestratorErrorResponse` so generated SDKs see a uniform shape across status codes. The `upstream_healthy: true` + `circuit_state: "CLOSED"` values are SYNTHETIC defaults — a write conflict / lock-RPC outage tells us nothing about gov-API health, so the breaker snapshot is non-authoritative on these paths. The synthesized values keep the Conflict409Body / SafetyUnavailable503Body schema shape consistent with the post-orchestrator paths; the `X-Apier-Upstream-Healthy` HTTP header is INTENTIONALLY OMITTED on these branches because the spec convention reserves the header for authoritative breaker snapshots only. - **Production** `POST /api/v1/auth/system-user/delegate` 409 `CONCURRENT_WRITE_IN_FLIGHT` AND 503 `SAFETY_SYSTEM_UNAVAILABLE` — same pre-orchestrator synthetic shape. - **Sandbox** `POST /api/v1/sandbox/actions/execute` — EVERY response (200 success including dry_run, 4xx/5xx errors). applies the universal-emission contract on the sandbox mirror so SDK parsers don't have to branch on shape. Sandbox emits fixed values (HAPPY_PATH_* on success, DENIED_* on errors); the orchestrator is NOT invoked because sandbox is deterministic. **Where this field does NOT appear:** Production dry-run responses, production pre-orchestrator validation/scope/idempotency-key/body-bound rejections that do NOT route through `preOrchestratorErrorResponse`, the production `/v1/actions/plan` endpoint (does not exist today), the sandbox approval-token + plan endpoints (which are not action-execution paths), and any other endpoint outside `/v1/actions/execute`, `/v1/auth/system-user/delegate`, and `/v1/sandbox/actions/execute`. Tells the SDK client whether retries fired, whether the upstream answered cleanly, and how long the call took end-to-end (including retry sleeps). Mirrors `ExecutionGuarantee` Append-only on the responses where it appears.
        - `retried` integer, required: How many retries actually fired (0 = first attempt succeeded).
        - `retries_remaining` integer, required: Retries left in the budget when the call returned.
        - `normalized` boolean, required: True iff the response went through the error normalizer (apier_code is the stable code mapped from raw upstream).
        - `upstream_healthy` boolean, required: Snapshot of the circuit-breaker verdict at response time. Boolean projection of `circuit_state` (true iff CLOSED). Typically mirrored on the X-Apier-Upstream-Healthy header for authoritative post-orchestrator responses. Pre-orchestrator synthetic responses (emitted via `preOrchestratorErrorResponse`, e.g., `Conflict409Body` and `SafetyUnavailable503Body`) carry this field for client convenience but do NOT emit the header — the header is reserved for breaker snapshots taken AFTER the orchestrator runs.
        - `circuit_state` 'CLOSED' | 'OPEN' | 'HALF_OPEN', required: Full three-state circuit-breaker snapshot at response time. Distinct from upstream_healthy (boolean projection); preserves the HALF_OPEN probe state which the boolean form would collapse to OPEN. Outcome builder reads this to apply the +0.05 HALF_OPEN probability boost.
        - `upstream_system` 'altinn' | 'skatteetaten' | 'maskinporten' | 'brreg' | 'norges_bank' | 'nav', required: Closed enum of upstream gov systems the reliability layer wraps. Mirrors `UPSTREAMS`
        - `execution_time_ms` integer, required: Wall-clock ms from orchestrator entry to return; includes retry sleeps + jitter.
      - `outcome` Outcome, required: Calibrated agent-facing outcome estimate. Included on the SAME response paths as `ExecutionGuarantee` — see `ExecutionGuarantee.description` for the exact emit-site list. Description updated alongside `ExecutionGuarantee` to surface the sandbox-execute extension. Also emitted on the production 409 `CONCURRENT_WRITE_IN_FLIGHT` and 503 `SAFETY_SYSTEM_UNAVAILABLE` paths via `preOrchestratorErrorResponse` (synthetic values: `is_final: false`, `requires_followup: true`, `followup_action: "retry"`, `success_probability: 0.2`, `confidence: "low"` — the calibration is non-authoritative on these paths because the orchestrator did not run, but the field is present so SDK consumers see a uniform shape across status codes). Sandbox: every response on `POST /api/v1/sandbox/actions/execute` including dry-run and error paths,'s universal-emission contract — sandbox emits fixed values (HAPPY_PATH_* on success, DENIED_* on errors). Norwegian disclaimer: "Estimat basert på historiske data, dry-run resultat og oppstrømssystemets helse. Dette er IKKE en garanti for at handlingen vil lykkes. Estimatet kan endres uten varsel når kalibreringen forbedres med mer data." English: "Estimate based on historical data, dry-run result, and upstream system health. This is NOT a guarantee that the action will succeed. The estimate may change without notice as calibration improves with more data."
        - `success_probability` number, required: Calibrated probability the action will ultimately succeed. Rounded to 2 decimals. NEVER NaN — clamped + validated.
        - `confidence` 'low' | 'medium' | 'high', required: Confidence band on the probability estimate. 'low' when sample count < 30; 'high' when probability > 0.85 with sufficient samples; 'medium' otherwise.
        - `is_final` boolean, required: True when this response represents a terminal state (no follow-up call would change the outcome).
        - `requires_followup` boolean, required: True when the agent should make a follow-up call (poll for receipt, mint new token, etc.).
        - `followup_action` string: Optional machine-readable hint for the follow-up. Only present when requires_followup is true.
  - `_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`: Transport-level validation failed before the engine ran. Possible `error_code` values: `VALIDATION_FAILED` (malformed JSON, top-level body validation failure such as missing `action_type` / invalid `period` format / non-object `payload`, or malformed `Idempotency-Key` header value); `IDEMPOTENCY_KEY_REQUIRED` (live-execute requires the `Idempotency-Key` header; dry-run does not). Per-action payload validation never surfaces here on the dry-run path — those failures arrive on 200 with `data_format_valid=false`.
- `401`: Authentication failed — missing, malformed, invalid, or revoked API key.
- `403`: Authorization or scope failure. Possible `error_code` values: `SCOPE_INSUFFICIENT` / `SCOPE_RESERVED` (API key lacks the `read:actions` scope or holds a reserved-prefix scope); `PLAN_INSUFFICIENT` (live-execute — the action_type is not yet available on the live path; v1 supports `mva_melding` only); `APPROVAL_TOKEN_REQUIRED` (live-execute — `X-Approval-Token` header missing); `APPROVAL_TOKEN_INVALID` (token not recognised — also returned for tokens that never existed, existence-disclosure protection); `APPROVAL_TOKEN_USED` (single-use token already consumed); `APPROVAL_TOKEN_EXPIRED` (token TTL elapsed); `APPROVAL_TOKEN_MISMATCH` (token bound to a different `org_number` or `action_id` — the route maps both `mva_melding` and `a_melding` action_types to the canonical `filing.submit` action_id at consume time via `actionTypeToActionId`; mismatches surface against the action_id binding, not the surface-level action_type); `FULLMAKT_PACKAGE_NOT_GRANTED` (live-execute, flag-gated on `FULLMAKT_PACKAGE_ENFORCEMENT`, default off — the acting systembruker delegation provably lacks a human-verified Altinn access package (tilgangspakke) the action requires. FAIL-OPEN by design: an unknown grant (`granted_packages` NULL), an unverified mapping, or an absent requirement never denies. The 403 body additionally carries a machine-readable `missing_packages` array of `{ urn, label_nb, label_en }` objects naming the absent package(s). Note the approval token was already consumed at validation time — after the customer delegates the package in Altinn, a NEW approval token must be minted before retrying).
- `409`: Two discriminated conflict scenarios — `error_code` is the discriminator. `IDEMPOTENCY_IN_PROGRESS`: another request with the same Idempotency-Key is still mid-flight. `CONCURRENT_WRITE_IN_FLIGHT`: cross-agent write-conflict gate fired — body carries `error: "concurrent_write_in_flight"` (spec literal) + `first_correlation_id` + `retry_after_seconds`. **Branch-specific response header (NOT advertised at the union level because it is variant-specific):** the `CONCURRENT_WRITE_IN_FLIGHT` branch emits `X-Apier-Skip-Idempotency-Cache: 1`; the `IDEMPOTENCY_IN_PROGRESS` branch does NOT. See `#/components/responses/Conflict409` for the dedicated variant-shape with the header documented.
- `412`: 412 Precondition Required emitted by `/api/v1/actions/execute` when the binding government write is suspended pending human approval. The response body contains ONLY `{ pending_action_id, status, expires_at, approval_url }` — never echoes the validated payload, never echoes any caller-supplied string. The `approval_url` is built from the server-configured `NEXT_PUBLIC_APP_URL` — NEVER from the inbound Host / X-Forwarded-Host header (Host-header injection would let an attacker phish the human approver). The consumer's authorised approver then calls POST `/api/v1/actions/pending/{id}/approve` or POST `/api/v1/actions/pending/{id}/reject` (both gated on the reserved `act:approve` scope — held back pending legal review, not default-granted at v1). Enforcement is behind the `HITL_ENFORCE` feature flag (fail-safe OFF — only the exact strings `true` / `1` enable it). When OFF (default), `/api/v1/actions/execute` returns its existing response shapes verbatim and this 412 is NOT emitted. When ON, a binding write (non-dry-run) arriving with no `X-Approval-Token` is staged as a pending_action and suspended with this 412; the consumer's authorised operator approves it, and the approved re-execute carries an `X-Approval-Token` and falls through to the existing submit path (which consumes the token exactly-once and submits). The approved re-execute MUST use a NEW `Idempotency-Key`: it is a distinct operation (the submission), separate from the suspension, so reusing the original key would replay this cached 412 (idempotency on the staging write) instead of submitting. The approve/reject endpoints' `releaseFn` deliberately stays a stub — the approved submit is gated by token consumption on the execute path, not by `releaseFn`.
- `413`: Request body exceeds 256 KB.
- `422`: Three discriminated paths: `IDEMPOTENCY_KEY_MISMATCH` (same Idempotency-Key + different body — the original response is preserved; the wrapper rejects the conflict per RFC draft); `VALIDATION_FAILED` on live-execute when one or more dry-run preconditions failed (the live path runs the same five preflight checks as a precondition gate; failures are reported with `details` listing the failed check names). On the precondition-failure path the approval token is NOT consumed — the agent can fix inputs and retry without burning a token. `GOV_COMMIT_PAYLOAD_MISMATCH` when the Idempotency-Key already committed a government submission with a DIFFERENT body — the durable gov-commit marker outlives the idempotency reservation, so the guard holds even after the reservation expired; mint a NEW key per logical submission. Schema is a oneOf so SDK clients can distinguish the contracts. `ApiError` covers IDEMPOTENCY_KEY_MISMATCH (closed `error_code` enum from the central component); `ExecutePreconditionFailedError` covers the precondition-failure case with a typed `details` array of `{field, message}` entries naming the failed checks. The discriminator is `error_code`.
- `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`: Internal server-side error. Possible `error_code` values: `DRY_RUN_FAILED` (the dry-run engine threw despite its non-throwing contract); `INTERNAL_ERROR` (a runtime invariant violation — e.g. handlePost reached without the outer body-bound wrap); `INTERNAL_SERVER_ERROR` (outer body-bound helper threw an unexpected non-typed error); `EXECUTION_FAILED` (TWO emit-sites: (a) PRE-CONSUME: approval-token consume RPC threw an infrastructure error before the CAS update fired; the approval token is NOT consumed and the caller can retry with the same token. (b) POST-CONSUME: a route invariant violation fired after the CAS already succeeded; the approval token IS consumed and a fresh token is required to retry. The response body alone does not distinguish (a) from (b); when in doubt, mint a fresh token). All captured to Sentry with the correlation_id; clients should retry with the same correlation_id (and same Idempotency-Key if used — the retry is replay-safe) and contact support if persistent.
- `502`: Live-execute — upstream submission failed. Altinn / Skatteetaten / NAV rejected the submission, OR the receipt signing or persistence step threw after a successful upstream call. The approval token IS consumed in this path (single-use-on-failure rule). Clients must mint a fresh approval token before retrying. The audit chain records the failure with `action: submit_mva_failed` and the error class. — `error_code` is the orchestrator's stable apier_code, NOT a fixed `GOVERNMENT_API_ERROR`. Possible values per `ApierErrorCode` enum: `GOVERNMENT_API_ERROR` (default), `GOVERNMENT_RATE_LIMITED`, `GOVERNMENT_UNAVAILABLE`, `GOVERNMENT_VALIDATION_REJECTED`, `MASKINPORTEN_AUTH_FAILED`, `ALTINN_DELEGATION_MISSING`, `EXECUTION_DEADLINE_EXCEEDED`, `EXECUTION_CIRCUIT_OPEN`, `RETRY_BUDGET_EXHAUSTED`, `EXECUTION_FAILED` (post-submit local-failure: Altinn submit succeeded but signReceipt/persistReceipt threw; route emits this code so SDK clients route to reconcile-don't-retry guidance), `ALTINN_ACK_UNPARSEABLE` (the upstream ACKNOWLEDGED at 2xx but the response body was unreadable: the write likely committed, a durable gov-commit marker records the attempt, the envelope instructs manual reconciliation, and clients must NOT auto-retry). SDK clients can route on these distinct codes for distinct remediation paths. Body is `ExecuteReliabilityError` (extends `ApiErrorWithStrictProvenance` with `execution_guarantee` + `outcome`). The orchestrator-derived reliability metadata is now emitted on the failure path, matching the success envelope's contract. — `X-Apier-Upstream-Healthy` is set on this response because the orchestrator ran and produced an authoritative breaker snapshot before the failure surfaced (the breaker may be CLOSED with a transient upstream rejection, or OPEN/HALF_OPEN if cumulative failures crossed the threshold). The header lets the agent gate retries on real-time breaker state without waiting for the next call.
- `503`: Three discriminated `error_code` values for this status — `error_code` is the discriminator. The schemas DIFFER per branch: the `oneOf` below maps each `error_code` to a distinct schema component. Earlier copy claimed all three shared one envelope; that was correct before narrowed the legacy variants and added the conflict-lock SAFETY_SYSTEM_UNAVAILABLE shape with extra fields (`error`, `execution_guarantee`, `outcome`): - `UPSTREAM_UNAVAILABLE` — Idempotency reservation service is temporarily unavailable. Emitted by the `withIdempotency` wrapper when the underlying Supabase RPC for the reservation fails. Fail-CLOSED behaviour, the handler is NOT executed (a duplicate write is strictly worse than a 503; the caller can retry). Clients using `Idempotency-Key` should retry with the SAME key. (.) - `ALTINN_LIVE_SUBMITTER_NOT_IMPLEMENTED` — Server-side rollout misconfiguration: emitted by the `getAltinnSubmitter` adapter selector when `ALTINN_MODE=live` is set in the OPERATOR's environment but the live submitter implementation hasn't been wired (slot reserved). Fail-CLOSED at boot rather than silently submitting via the mock. The route catches the AltinnError thrown by the selector and forwards its structured envelope (summary, why, fix_steps in Norwegian bokmål). API CONSUMERS cannot fix this — `ALTINN_MODE` is a server env var, not a request header — so the remediation is operator-side: contact Apier support with the X-Correlation-ID, OR retry later once the activation gate clears (the operator will have flipped `ALTINN_MODE=mock` until the live submitter ships). Until that happens, the agent should treat this as a transient outage and back off + retry. - `SAFETY_SYSTEM_UNAVAILABLE` — conflict-lock acquire RPC failed. Fail-CLOSED for safety: serving the request without verifying that no other agent is mid-write would risk dual-submission. **This is the only branch that emits `X-Apier-Skip-Idempotency-Cache: 1`** (UPSTREAM_UNAVAILABLE and ALTINN_LIVE_SUBMITTER_NOT_IMPLEMENTED do NOT). See #/components/responses/SafetyUnavailable503 for the dedicated variant shape with the header documented. Clients MUST branch on `error_code` from the body to select the correct generated SDK type — the three branches have different structural shapes per the `oneOf` discriminator.

---

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