Sandbox mirror of /api/v1/actions/execute (API key or sandbox bearer, dry-run + live mock)
The sandbox write-side execute endpoint. Two paths discriminated by ?dry_run=:
Dry-run (?dry_run=true): returns a deterministic validation block describing the five checks, with data.dry_run: true and NO data.altinn_receipt_id. reliability envelope (execution_guarantee + outcome at top level) emitted on every response (universal-emission contract). No approval token required.
Live mock (no dry_run, or dry_run=false): requires approval_token in the body. A successful live mock filing under a sandbox bearer is ALSO recorded in the caller's ephemeral 15-minute session state, so the next same-suffix read of the sandbox /audit, /obligations or /summary merges the Pending→Filed transition over the static fixture (_meta.session_merged). Real API keys and dry-run record nothing; no government upstream is ever called. Runs the full validation gauntlet — presence → prefix → length → hex-only → re-derive → crypto.timingSafeEqual. Returns a deterministic receipt envelope with altinn_receipt_id (HMAC-derived 32-char hex), audit_log_id (UUID v5 from sandbox namespace), signature (HMAC over canonical receipt message), and signature_algorithm: 'HMAC-SHA256-v1'. Top-level + nested _sandbox_marker field flags every sandbox receipt as synthetic.
Sandbox does NOT enforce expires_at on the approval token — sandbox is deterministic-by-construction; SANDBOX_NOW is fixed (2026-05-04T12:00:00+02:00). No DB writes to audit_log / provenance_log / receipts / approval_tokens.
?simulate_error= precedes everything except validation; reserved error orgs (999000901-904) fire BEFORE token validation so an agent can probe the explainer envelope without a valid token. On the dry-run path (?dry_run=true) a reserved error org instead answers 200 with validation.all_passed: false and the check it simulates marked failed, as production's dry-run reports a failing check; and every dry-run validates payload against the per-action schema plus the sandbox magic values, so an invalid payload answers 200 with data_format_valid failed.
Idempotency-Key is required. Mirrors the production write surface so SDK retry code paths work against either endpoint without branching. A missing header returns 400 VALIDATION_FAILED with details.field: 'Idempotency-Key'. Sandbox is deterministic-by-construction (same body produces the same receipt on every call) so replays return verbatim — but unlike production, sandbox does NOT write a was_idempotent_replay=true audit_log row on replay (sandbox writes ZERO production DB rows — the only sandbox write is the ephemeral sandbox_sessions state described above, 15-minute TTL, which a live-mock replay under a sandbox bearer records again; the idempotency-replay audit row is a production-only forensic artifact).
Request
- Base URL: https://www.apier.no
- URL: https://www.apier.no/api/v1/sandbox/actions/execute
- Auth: one of:
- HTTP bearer
- HTTP bearer
Query parameters
Strict z.enum — '1' / 'yes' / '' / garbage all return 400 (empty value rejected by, no silent fallthrough).
Force the sandbox to return a structured error response. tokens (Apier-edge failures) — missing_delegation → 403 AUTH_MISSING_DELEGATION; invalid_token → 401 AUTH_EXPIRED_TOKEN; validation_error → 400 VALIDATION_FAILED; scope_missing → 403 SCOPE_MISSING. upstream-failure tokens — altinn_timeout → 504 EXECUTION_DEADLINE_EXCEEDED (Altinn 3 instances endpoint timed out, response body carries the Altinn RFC 7807 envelope); invalid_certificate → 502 MASKINPORTEN_AUTH_FAILED (Maskinporten OAuth2 error_object — invalid_client); malformed_payload → 422 GOVERNMENT_VALIDATION_REJECTED (Skatteetaten validation errors with nested path[] arrays); upstream_5xx → 502 GOVERNMENT_UNAVAILABLE (generic government 5xx). The 500 INTERNAL_ERROR path is reserved for unexpected internal failures. Accepts BOTH lowercase public tokens (primary contract) AND uppercase internal explainer codes (back-compat). Wins precedence over reserved-org defaults.
Pin the sandbox's now() to a specific Europe/Oslo wall-clock date. Format strictly YYYY-MM-DD (10 chars) — RFC 3339 with time, ISO with offset, locale forms all 400. The date interprets as midnight Europe/Oslo (CET / +01:00 winter, CEST / +02:00 summer — DST-aware via date-fns-tz fromZonedTime, never hardcoded offsets). Honored on the write path (POST /actions/execute computes filed_late against the pinned date) AND on the authenticated /v1/sandbox/company/{org}/deadlines and /obligations reads, where each item's upcoming/overdue temporal status re-derives against the pinned date — the deadline DATES themselves never move (deterministic fixtures). The zero-auth /v1/sandbox/public/* mirror does not parse this knob. When ?as_of= is also present on those reads, as_of wins. Year range 2000..2100; invalid calendar dates (Feb 30, Apr 31) rejected explicitly.
Headers
REQUIRED on /api/v1/sandbox/actions/execute. The HEADER is required and validated at the boundary (1-255 printable ASCII; missing → 400 VALIDATION_FAILED with details.field: 'Idempotency-Key'). What sandbox does NOT do — distinct from production — is enforce single-use REPLAY BINDING via the idempotency_keys table: on production the same key + same body within 24h returns the cached response and emits Idempotent-Replay: true, while sandbox computes the response fresh every call (deterministic-by-construction; same body always returns the same response, so no cache is needed). The contract MATCHES production at the header level so SDK retry code paths work against either surface. § Sandbox Middleware Exemptions still applies: sandbox writes ZERO audit_log rows on replay — the was_idempotent_replay=true forensic row is a production-only artifact.
Optional request-scope UUID v4 that the client can set to thread its own trace across multiple requests. A valid UUID v4 is reused verbatim on the server; anything malformed or absent is replaced with a fresh crypto.randomUUID(). The canonical value is echoed back on the X-Correlation-ID response header AND persisted on every matching audit_log row — so a forensic query can reconstruct "every row from request X" without timestamp-based correlation. Accepted on every /api/v1/* operation via the proxy middleware; no per-operation opt-in required. The header is also emitted on every /api/v1/* response — see the X-Correlation-ID response-header component.
Request body
Response
Success. Body shape on dry_run: { success: true, data: { dry_run: true, org_number, action, validation: {...} }, execution_guarantee, outcome, _meta }. Body shape on live: { success: true, data: { dry_run: false, receipt: {...} }, execution_guarantee, outcome, _sandbox_marker, _meta }. outcome.is_final: true, outcome.requires_followup: false, outcome.success_probability: 0.99, outcome.confidence: 'high' on the happy path. Description aligned with the schema + runtime — dry-run data carries org_number + action echo fields too (the route stamps them so SDK clients can correlate the response to the request without re-reading the body).
Changes
No recorded changes to this operation across all 1 revision of this API.