Idempotency keys
Make POST retries safe — same key returns the original response_id instead of starting a duplicate turn.
Network blips happen. If your POST to /v1/sessions/{id}/messages or /v1/responses (streaming) lands on the server but you never see the response headers, retrying without coordination would either start a duplicate turn (rare — session.busy 409s the dup on the session path) or leave you locked out with no way to discover the original response_id. The Idempotency-Key header closes that gap, the same way Stripe and OpenAI do.
How to use it
- Generate a stable key per logical operation — a UUID is conventional.
- Send the POST with
Idempotency-Key: <your-key>. - If anything goes wrong client-side (connection drop, timeout, app crash before reading the response), retry the same POST — same body, same key.
- The server recognizes the key and replays the original
response_id. Same SSE stream fromid: 0. No new turn started, no extra tokens billed.
curl -X POST "$POKEE_API/v1/sessions/$SID/messages" \
-H "Authorization: Bearer $POKEE_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: 550e8400-e29b-41d4-a716-446655440000" \
-d '{"message": "summarize Q3 earnings"}'Semantics
| Scenario | Result |
|---|---|
| Same key + same body | Returns the original response_id. If the original turn is still in flight or within the 120s resume grace, the SSE stream replays from the buffer. |
| Same key + same body, but original buffer evicted | 200 application/json with {"response_id", "status": "replayed", "note": "...evicted..."}. You have the id, but no events to replay. |
| Same key + different body | 422 Unprocessable Entity. The fingerprint over the body changed — use a fresh key for a new logical operation. |
| Same key on a different session | 422 (the fingerprint is over (session_id, body)). |
| Concurrent retries with same key | Serialize on a per-key lock; the second gets the replay path automatically — only one turn ever starts. |
Where it applies
| Endpoint | Supported? |
|---|---|
POST /v1/sessions/{id}/messages | Yes |
POST /v1/responses with stream=true | Yes |
POST /v1/responses with stream=false | No — returns 400. Non-streaming requests are short-lived; retry directly. |
Validation
- 1 to 200 characters
- ASCII-printable only (
0x21–0x7e); no spaces, newlines, or control chars - Bad value →
400 Bad Request
Lifetime
- Keys live for 24 hours (
IDEMPOTENCY_TTL_SECONDS). - After that, the same key acts as if it had never been seen — a retry would start a fresh turn. Don't rely on long-tail replays for audit; if you need persistent operation tracking, log the
response_idon your side.
Pairs well with
- Resume — if your POST landed and you have the
response_idfrom the response headers, resume directly. If you don't (header lost), retry the POST with the same idempotency key to get theresponse_idback. - Cancel — the cancel endpoint is keyed by
response_id, so once you've replayed via idempotency you have everything you need to abort.