Sandbox

Guided MVA write-loop rehearsal (mock-gated, real HITL mechanics)

Customer-facing rehearsal of the full production MVA write loop, driving the guided walkthrough on /sandbox. Composes the PRODUCTION modules — pending-action state machine, approval-token atomic consume, receipt signer/persister — with two deliberate differences from /api/v1/actions/execute: the human-in-the-loop suspension branch is ALWAYS ON route-locally (production gates it behind the HITL_ENFORCE flag, fail-safe OFF), and the Altinn submitter is the HARDCODED mock (ALTINN_MODE is never read), so this surface can NEVER reach a government system. Receipts carry MOCK-ALT-… ids and government_response_raw._mock: true.

TWO BRANCHES, selected by the X-Approval-Token header:

No token — the validated request is staged as a REAL pending_actions row (15-minute TTL, readable via GET /api/v1/actions/pending/{id}) and suspended with 412 HITL_PENDING_APPROVAL. The 412 is idempotency-cached: re-sending with the SAME Idempotency-Key replays the SAME pending_action_id (the idempotency replay guarantee), never a second pending row.

Token present — the token (minted via the production POST /api/v1/auth/approval-token, bound to (org_number, action_id: "filing.submit")) is consumed EXACTLY ONCE through the same atomic CAS RPC production uses (consume_approval_token_atomic); there is no bypass path. On success the mock submit → HMAC-sign → persist pipeline runs with the production two-row audit pattern (submit_mva_started pre-insert, then submit_mva / submit_mva_failed outcome rows, all carrying details.surface: "sandbox_rehearsal"). Rejected tokens return the production error codes verbatim: APPROVAL_TOKEN_INVALID (existence-disclosure-safe NOT_FOUND collapse) / APPROVAL_TOKEN_USED / APPROVAL_TOKEN_EXPIRED / APPROVAL_TOKEN_MISMATCH.

AUTH — a REAL Apier API key is required (the synthetic apier_sandbox_test_* bearer is rejected 401: every step writes real rows keyed to your account). Required scope read:actions — the same scope as the production execute surface; default signup keys (read:*) qualify. Unlike the other /v1/sandbox/* routes this endpoint is scope-gated and idempotency-wrapped, because it writes REAL pending/audit/receipt rows — it is the deliberate stateful exception on the sandbox surface.

?dry_run is NOT supported here (400 VALIDATION_FAILED) — the walkthrough's validation step targets the production dry-run, which reports all five precondition checks. The rehearsal deliberately skips the precondition engine so consumers without an active delegation can still rehearse the write mechanics.

?simulate_error=<missing_delegation|invalid_token|validation_error|scope_missing> returns a canned production-shaped error envelope BEFORE any state is written — rehearse your error handling without dirtying loop state. The upstream-failure simulation tokens are deliberately absent: the submitter here is the local deterministic mock.

Every response carries _meta.is_sandbox: true, and EVERY response schema on this operation requires the marker — the 401 / 409 / 412 / 429 wrap the shared production contracts (same headers, same envelope, composed via allOf) with the marker requirement added, so the schema contract matches the wire contract on all nine documented statuses. Provenance: per the sandbox carve-out, NO provenance_log row is written for rehearsal responses; _meta.response_hash is still stamped for client-side verification.

post/api/v1/sandbox/rehearsal/execute

Request

  • Base URL: https://www.apier.no
  • URL: https://www.apier.no/api/v1/sandbox/rehearsal/execute
  • Auth: HTTP bearer

Query parameters

simulate_error'missing_delegation' | 'invalid_token' | 'validation_error' | 'scope_missing'

Error simulation for agent testing. The parameter's presence (any value, valid or not) routes around the idempotency layer entirely, and a valid value short-circuits BEFORE any state is written — no pending row, no token consume, no audit row, no idempotency reservation; a supplied Idempotency-Key is neither required nor reserved, so the same key stays fresh for the real staging call and returns the canned production-shaped explainer envelope: missing_delegation → 403 AUTH_MISSING_DELEGATION; invalid_token → 401 AUTH_EXPIRED_TOKEN; validation_error → 400 VALIDATION_FAILED; scope_missing → 403 SCOPE_MISSING. Unknown values fail 400 VALIDATION_FAILED. The upstream-failure tokens other sandbox surfaces accept (altinn_timeout, invalid_certificate, malformed_payload, upstream_5xx) are deliberately absent here: the rehearsal's submitter is the local deterministic mock, so a canned government-upstream failure would misrepresent what this surface can do.

Headers

Idempotency-Keystring required

REQUIRED on every rehearsal call — both branches write state (a pending_actions row, or a token consume + persisted receipt). The staging 412 is cached against this key, so a re-send with the same key replays the SAME pending_action_id. The approved execute MUST use a NEW key: it is a distinct operation from the suspension, and reusing the staging key would replay the cached 412 instead of executing.

X-Approval-Tokenstring

Single-use approval token minted via POST /api/v1/auth/approval-token with action_id: "filing.submit". ABSENT → the request is staged and suspended with 412. PRESENT → consumed exactly-once via the atomic CAS; invalid/replayed/expired/mismatched tokens are rejected with the production 403 codes. Presence-only routing — validation happens exclusively in the consume RPC.

Request body

org_numberstring required

9-digit Norwegian organisation number. On the token branch it must match the org_number the approval token was minted for, or the consume returns 403 APPROVAL_TOKEN_MISMATCH.

action_type'mva_melding' required

Closed to mva_melding — the one action the rehearsal (and the production live branch) supports. The REST literal, never the MCP tool name.

periodstring required

MVA period — bimonthly terminer (YYYY-Tn, n=1..6), annual (YYYY-A), or monthly (YYYY-MM). Same grammar as the production execute surface.

Response

Token consumed and the mock submit → sign → persist pipeline completed. Same data shape as the production live-execute 200 minus the reliability envelope (execution_guarantee / outcome — the rehearsal composes no orchestrator). altinn_receipt_id is the deterministic MOCK-ALT-… id; signed_receipt.government_response_raw._mock is true; signed_receipt.audit_log_id + signed_receipt.correlation_id are the keys into the append-only audit evidence the walkthrough's final step renders.

successtrue required

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.