Neun PredictionsGetting startedIntegration guideErrorsReference

Idempotency

status: written from ADR-060 section 3, ADR-045 and apps/api/src/app/modules/player/s2s-idempotency.ts. [BT]

Send an Idempotency-Key header on every write (POST, PUT, PATCH, DELETE) to the Operator API. A key is a UUID or 16 to 64 characters of A-Z a-z 0-9 _ -. Use a new key for each new action. Reuse the same key only to retry the same call.

Required on money writes. Missing key gives HTTP 400. The same rule holds for every operator write: a missing or badly formed key gives HTTP 400 IDEMPOTENCY_KEY_REQUIRED. The OpenAPI file still shows the header as optional, because v1 is not released and the contract gate treats a newly required header as breaking. Owner row N162 decides when the schema changes.

CaseResult
New keyThe call runs. A success is stored.
Same key, same bodyThe stored answer is returned again. Nothing runs twice.
Same key, different bodyHTTP 422 IDEMPOTENCY_KEY_REUSED (owner decision). ADR-060 says 409. Open: N45.
Same key, first call still runningHTTP 409 IDEMPOTENCY_KEY_IN_PROGRESS, retryable. Wait and send again.
Call failedThe key is freed. You can retry with it. Only successes are stored.
Key missing or malformedHTTP 400 IDEMPOTENCY_KEY_REQUIRED

Replay storage is behind S2S_IDEMPOTENCY_ENABLED (default off). [FO] With it off, the key is still checked but answers are not replayed.

Key lifetime

ADR-060 says 7 days. The code keeps a key for the longer of 7 days and IDEMPOTENCY_KEY_RETENTION_DAYS (default 30, never under 30). Show 30 days to your team. Shown-once secrets are not stored in a replay.

Wallet API

The wallet side is different. Every call carries an operationId. The PAM must return the first outcome with alreadyProcessed: true for an identical request, and 409 IDEMPOTENCY_CONFLICT for the same operationId with a different body. Return no money movement with that 409. Neun treats it as unknown, stops retrying that operation and opens a recovery case (see the sandbox guide). See the signing guide.