Validate (dry-run) or submit (live) a filing action
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):
- company_exists — does org_number exist in Brønnøysundregistrene?
- system_user_authorised — is there an active System User delegation for this consumer + this org_number?
- 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.)
- data_format_valid — does the payload pass the per-action Zod schema?
- 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).
Request
- Base URL: https://www.apier.no
- URL: https://www.apier.no/api/v1/actions/execute
- Auth: HTTP bearer
Query parameters
Mode discriminator. When "true", the endpoint runs in dry-run mode (validation only, no upstream submission). When omitted or any other value, the endpoint runs in live-execute mode (submission to Altinn / Skatteetaten / NAV). Live-execute requires Idempotency-Key AND X-Approval-Token headers AND a fully-passing dry-run precondition gate; see the operation description for the full contract.
Headers
Idempotency-Key. REQUIRED for live-execute (?dry_run=true omitted); a missing or empty value returns 400 IDEMPOTENCY_KEY_REQUIRED — network retries without the same key would double-submit. OPTIONAL on dry-run (?dry_run=true) — when present the standard dedupe semantics apply (same key + same body replays in 24h with Idempotent-Replay: true; same key + different body returns 422 IDEMPOTENCY_KEY_MISMATCH; concurrent in-flight dupe returns 409 IDEMPOTENCY_IN_PROGRESS). The schema mirrors #/components/parameters/IdempotencyKeyHeader exactly (1–255 printable ASCII). The header is described inline rather than $ref'd so the dual-mode requirement is documented at the operation level. Was previously maxLength 256 with no pattern, which widened the contract vs the runtime enforcement; generated SDK clients could ship values the server would 400. Replays return the cached response and are recorded in audit_log with was_idempotent_replay=true — a forensic flag that distinguishes retry storms from genuine duplicate intent. The replay audit row carries details.idempotency_key_hash (SHA-256 hex of the raw header value, byte-equal to idempotency_keys.key_hash so forensic joins on hash work), details.endpoint_pattern, and details.replay_age_ms. The raw key VALUE is never persisted — only the hash. Each replay carries its own correlation_id from the current request (not collapsed into the original).
Single-use approval token. REQUIRED for live-execute; ignored on dry-run. Mint via POST /api/v1/auth/approval-token after a human operator confirms the action.
Consumption semantics (correction per consume_approval_token_atomic actual behavior — only the success path runs the CAS UPDATE on approval_tokens.used_at):
Token NOT consumed (retry-safe with same token, within TTL):
- 422 VALIDATION_FAILED — live preconditions failed before the consume RPC ran. Fix the inputs and resubmit with the same token.
- 403 APPROVAL_TOKEN_EXPIRED — RPC returned EXPIRED, no UPDATE fired. Token is unchanged (still NULL used_at) but unusable due to elapsed TTL. Mint a fresh one — not because the original was burned, but because it's past its expiry.
- 403 APPROVAL_TOKEN_MISMATCH (ORG_MISMATCH / ACTION_MISMATCH) — RPC returned mismatch, no UPDATE fired. Token still has NULL used_at. The agent COULD theoretically retry with a request matching the token's binding, but practically minting a fresh token bound to the actually-required action_id + org_number is the safer remediation.
- 500 EXECUTION_FAILED PRE-CONSUME — the consume RPC itself threw an infrastructure error (DB unreachable, RPC contract drift) before reaching the CAS UPDATE. The agent can retry with the same token.
Token IS consumed (single-use rule — retries require a fresh token):
- 200 (success) — happy path, the CAS UPDATE fired and used_at is now set.
- 403 APPROVAL_TOKEN_USED — a PRIOR successful consume already ran the UPDATE. This response confirms the row is in used_at IS NOT NULL state; the original consumer of that token is upstream of this 403.
- 500 EXECUTION_FAILED POST-CONSUME — a route invariant violation fired after the CAS UPDATE already succeeded. Token row is already updated; the request that invoked the CAS is the one that consumed it.
- 502 GOVERNMENT_API_ERROR — submit / sign / persist step failed AFTER the consume CAS succeeded. Single-use-on-failure rule applies — the token is burned by the successful CAS, the downstream failure does NOT roll it back. Mint a fresh token.
The SDK client cannot distinguish pre-consume from post-consume EXECUTION_FAILED from the response alone — when in doubt, the submit_mva_started audit row is the operator-side signal: if it exists for the correlation_id, the consume succeeded and the token is burned. EXPIRED and MISMATCH branches are never consumptive at the RPC layer. The single-use-on-failure rule applies only after a successful consume. A token that does not exist answers APPROVAL_TOKEN_INVALID, so the response never discloses whether a token exists.
Request body
Response
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.
Example response
{
"data": {
"signed_receipt": {
"org_number": "999999999",
"government_response_raw": {
"hash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
},
"government_response_hash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef"
}
},
"_meta": {
"rulebook_version": "v1.0.0",
"source": "apier.no",
"data_source": "Brønnøysund Enhetsregisteret + Apier Universal Rulebook",
"schema_version": "1.0.0",
"legal_basis": "NLOD — public registry reuse",
"response_timestamp": "2026-04-24T12:00:00.000Z",
"response_hash": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
"source_snapshot_id": "brreg-2026-04-24T02:00:00.000Z",
"cost_nok": 0.5,
"wallet_remaining_nok": 9.5,
"spend_cap_remaining_nok": 12.5
}
}Changes
No recorded changes to this operation across all 1 revision of this API.