Idempotency and safe retries
Make write requests retry-safe with the Idempotency-Key header — at-most-once execution, a 24-hour replay window, and the four reservation outcomes an agent must handle.
[Cite this as: Apier.no Docs v0.1.0 — last updated 2026-06-04]
Autonomous agents retry. A request times out, a worker restarts mid-call, or a network hop drops the response after the server already committed the write — and the agent, seeing no answer, sends it again. For a read that is harmless. For a write that files a VAT return or delegates a system user, a blind retry risks doing the work twice.
Apier closes that gap with the Idempotency-Key header. Send the same key on a
retry and you get the original outcome back — the side effect happens at most
once, no matter how many times the request lands.
The contract
Attach one header to a write request:
Idempotency-Key: 9f1c2a7e-4b6d-4e2a-8c10-5d7b3e9a1f04- Format. 1–255 printable ASCII characters. A UUID or ULID per logical operation is the safest choice — unique, collision-free, easy to log.
- Scope. A key is scoped to your consumer account and the specific endpoint. The same string used on a different endpoint, or by a different consumer, never collides — so you can mint keys locally without coordinating globally.
- Window. A reservation lives for 24 hours. After it expires the key is free to reuse; within the window the original result is replayed.
- Body fingerprint. The server fingerprints the request body with canonical
JSON before comparing.
{ "a": 1, "b": 2 }and{ "b": 2, "a": 1 }are the same logical request, so reordered fields or reformatted whitespace on a retry still replay cleanly instead of being mistaken for a different request.
Which endpoints support it
The header is honoured on Apier's write (POST) endpoints. Read endpoints
(GET) are naturally idempotent and ignore it.
| Endpoint | Idempotency-Key |
|---|---|
POST /api/v1/actions/execute | Required — a missing key returns IDEMPOTENCY_KEY_REQUIRED |
POST /api/v1/altinn/list-acting-capacity | Optional, recommended |
POST /api/v1/auth/system-user/delegate | Optional, recommended |
The live filing path makes the key mandatory on purpose: it is the one call that produces an irreversible government submission, so it refuses to run without the double-submit guard in place. The MVA filing guide walks the full filing flow (approval token, scopes, signed receipt).
The four outcomes
Every keyed request resolves to exactly one of these. Branch on the HTTP status, never on response text.
| Situation | Status | What it means | Key headers |
|---|---|---|---|
| First time this key is seen | the handler's own status (e.g. 200) | The request executed; its response is now cached for 24h. | Idempotent-Replay: false |
| Same key, same body, again | the original status | Replay — the handler did not run again; you get the stored response byte-for-byte. | Idempotent-Replay: true, Idempotent-Replay-Age-Ms: <ms> |
| Same key, still in flight | 409 IDEMPOTENCY_IN_PROGRESS | A concurrent request with this key hasn't finished. Wait and retry. | Retry-After: 2 |
| Same key, different body | 422 IDEMPOTENCY_KEY_MISMATCH | You reused a key for a genuinely different request. Mint a new key. | — |
| Malformed key | 400 VALIDATION_FAILED | The key isn't 1–255 printable ASCII characters. | — |
Worked example
First attempt — the write executes and the response is cached:
POST /api/v1/actions/execute HTTP/1.1
Host: apier.no
Authorization: Bearer apr_test_<your_key_here>
Idempotency-Key: 9f1c2a7e-4b6d-4e2a-8c10-5d7b3e9a1f04
X-Approval-Token: <token-from-approval-step>
Content-Type: application/json
{
"org_number": "999999999",
"action_type": "mva_melding",
"period": "2026-T1",
"payload": {}
}HTTP/1.1 200 OK
Idempotent-Replay: false
Content-Type: application/json
{ "success": true, "receipt": { "...": "..." } }The connection drops before your client reads that response, so the agent retries — same key, same body:
HTTP/1.1 200 OK
Idempotent-Replay: true
Idempotent-Replay-Age-Ms: 1840
Content-Type: application/json
{ "success": true, "receipt": { "...": "..." } }Identical body, no second filing. Idempotent-Replay-Age-Ms tells you how long
ago the original ran — useful for deciding whether to trust a cached result or
surface it to a human for review.
If instead the retry carries a changed body under the same key, Apier refuses it rather than guess which one you meant:
HTTP/1.1 422 Unprocessable Entity
Content-Type: application/json
{ "success": false, "error_code": "IDEMPOTENCY_KEY_MISMATCH", "...": "..." }The full Norwegian-language explanation envelope on that error is described in
the error-handling guide.
What gets cached
Only responses worth replaying are stored:
- Successes only.
2xxresponses are cached; transient non-2xxcodes are not frozen behind the key, so a later retry re-executes cleanly. 5xxis never cached. A server error usually means the side effect did not commit (or is indeterminate). Freezing it for 24h would make every retry hit the same stale failure — instead, a retry with the same key re-runs the write.- Stateful headers are stripped.
Set-CookieandAuthorizationare never replayed; a stale session cookie is always wrong.
When the guard itself can't run
If Apier cannot reach the reservation store to check whether your key was already
used, it fails closed: the write is rejected with 503 UPSTREAM_UNAVAILABLE
rather than risk executing twice. A double-submitted filing cannot be un-sent; a
503 can simply be retried. Re-send with the same key once the service
recovers.
Agent checklist
- One key per logical operation. Generate it before the first attempt and reuse the same key on every retry of that operation. A fresh key per retry defeats the entire mechanism.
- Never mutate the body between retries of the same operation — that earns a
422 IDEMPOTENCY_KEY_MISMATCH. If the request genuinely changed, it deserves a new key. - Honour
Retry-Afteron a409 IDEMPOTENCY_IN_PROGRESSinstead of hammering the endpoint. - Mind the 24-hour window. Beyond it the key is forgotten and a re-send executes as a brand-new request.
- Pair it with an approval token on the live filing path. Idempotency stops
accidental duplicates; the approval token enforces the human-in-the-loop gate.
Both headers are required on
POST /api/v1/actions/execute.
Read next: MVA filing · Error handling · Authentication.
Error handling and the Compliance Explainer
Structured error codes, Norwegian-language fix steps, and the human-agent handover boundary.
Inspecting actions with the audit trail
Agents have no dashboard. Use the signed receipt, the audit endpoint, correlation_id, and initiated_by to reconstruct exactly what an action did — on the sandbox today, on production later.