Skip to content
Apier.no
Guides

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 to handle.

[Cite this as: Apier.no Docs v0.1.0 — last updated 2026-10-21]

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 registers a webhook subscription or mints a key, 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.

EndpointIdempotency-Key
POST /api/v1/subscriptionsOptional, recommended
POST /api/v1/brreg/company-profileOptional, recommended (the one metered POST; a replay is debited once)

The filing action routes (POST /api/v1/actions/execute and the acting-capacity and delegation routes) are withdrawn since 2026-10-21 and answer HTTP 410 CAPABILITY_WITHDRAWN before any header is read; they are listed in the error reference for compatibility only.

The four outcomes

Every keyed request resolves to exactly one of these. Branch on the HTTP status, never on response text.

SituationStatusWhat it meansKey headers
First time this key is seenthe handler's own status (e.g. 200)The request executed; its response is now cached for 24h.Idempotent-Replay: false
Same key, same body, againthe original statusReplay — 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 flight409 IDEMPOTENCY_IN_PROGRESSA concurrent request with this key hasn't finished. Wait and retry.Retry-After: 2
Same key, different body422 IDEMPOTENCY_KEY_MISMATCHYou reused a key for a genuinely different request. Mint a new key.—
Malformed key400 VALIDATION_FAILEDThe 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_free_<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": {
    "total_revenue_nok": "500000.00",
    "total_outgoing_vat_nok": "125000.00",
    "total_incoming_vat_nok": "20000.00"
  }
}
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. 2xx responses are cached; transient non-2xx codes are not frozen behind the key, so a later retry re-executes cleanly.
  • 5xx is 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-Cookie and Authorization are 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-After on a 409 IDEMPOTENCY_IN_PROGRESS instead 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.
  • Keep the key per logical write. Idempotency stops accidental duplicates; it is not a substitute for checking the response you got back.

Read next: Webhooks · Error handling · Authentication.

Building with an LLM? Read llms.txt for agent-oriented integration guidance.