Idempotency Headers
Adjudon's mutating endpoints accept an Idempotency-Key header so a
client can retry a failed network call without producing duplicates.
The mechanism is per-organisation, persistent across restarts, and
fails open when the store is unavailable. Tested against API version
v1.
The header
| Header | Required | Format | Notes |
|---|---|---|---|
Idempotency-Key | optional | string, ≤256 characters, case-insensitive | If absent, Adjudon auto-generates a deterministic key from the request hash (see below) |
Idempotency-Key: 9f0e2c4b-1a3d-4f5e-8b0c-6e1d2a3b4c5d
A v4 UUID per request is the canonical client choice. Adjudon does not require the value to be a UUID; any opaque string up to 256 characters works.
TTL: 24 hours
Idempotency records are stored for 24 hours after the first request. A MongoDB TTL index purges them automatically when the window closes. After 24 hours, the same key on the same payload is treated as a fresh request, not a duplicate.
This matches the standard window for replay-safe client retries. Long-running batch jobs that retry after more than 24 hours should generate fresh keys per attempt.
Auto-generated keys
When the client does not send the header, Adjudon computes a key itself:
key = sha256(`${agentId}:${organizationId}:${JSON.stringify(body)}`)
Two identical payloads from the same agent in the same organisation
produce the same key — a second POST with byte-identical body
returns the cached response, not a new trace. This is by design
and is the same behaviour as a client-supplied duplicate key. If you
want explicit retry control, send your own Idempotency-Key.
The auto-generation is deterministic across server instances (the hash uses no clock or randomness), so a retry that lands on a different API node behaves identically to one that lands on the original.
Behaviour matrix
| Scenario | Server response |
|---|---|
| First request with new key | Process normally, store the response keyed by (orgId, key) |
| Second request with same key, original completed | Return the cached statusCode + responseBody; no new resource created |
| Second request with same key, original still processing | Treated as a concurrent duplicate; the in-flight request wins the slot |
| Second request with same key, payload differs | Same cached response is returned (the key, not the body, is the dedup unit) |
| Same key from a different organisation | Independent; chains never mix across tenants |
| Idempotency store unavailable | Fail open — the request proceeds without idempotency protection; never a blocker |
The fourth row is the gotcha: the key is the dedup unit, not the body. If you reuse a key with a different body, you receive the original response, not a new one. Generate a fresh key per logical operation.
Worked example
curl -X POST https://api.adjudon.com/api/v1/traces \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Idempotency-Key: 9f0e2c4b-1a3d-4f5e-8b0c-6e1d2a3b4c5d" \
-H "Content-Type: application/json" \
-d '{"agentId":"a1","inputContext":{"prompt":"x"},"outputDecision":{"action":"deny"}}'
# → 201 Created, traceId: trace_aBcD1234
# Network hiccup, client retries with the same key
curl -X POST https://api.adjudon.com/api/v1/traces \
-H "Authorization: Bearer $ADJUDON_API_KEY" \
-H "Idempotency-Key: 9f0e2c4b-1a3d-4f5e-8b0c-6e1d2a3b4c5d" \
-H "Content-Type: application/json" \
-d '{"agentId":"a1","inputContext":{"prompt":"x"},"outputDecision":{"action":"deny"}}'
# → 201 Created, traceId: trace_aBcD1234 (same trace, NOT a duplicate)
import os, uuid, requests
def post_trace(payload, key=None):
return requests.post(
"https://api.adjudon.com/api/v1/traces",
headers={
"Authorization": f"Bearer {os.environ['ADJUDON_API_KEY']}",
"Idempotency-Key": key or str(uuid.uuid4()),
},
json=payload,
)
Endpoints that accept the header
The header is honoured on every mutating endpoint. The
trace-ingestion endpoint (POST /api/v1/traces) is the canonical
case. Other mutating endpoints (e.g., POST /api/v1/incidents,
PATCH /api/v1/policies/:id) accept the same header with the same
24-hour TTL.
GET and HEAD requests are read-only and do not need
idempotency.
See also
- REST API Reference — the top-level index
- Idempotency Concept — the mental model in narrative form
- Error Codes — the failure envelope and the rate-limit / 429 response
- Quickstart — the canonical first
Idempotency-Keyuse