Alloovium

Core Concepts

Idempotency

Attach an Idempotency-Key header to any mutating request and Alloovium will make sure it never executes twice — even if your client retries on a network error or crashes mid-flight.

Why you want it

Networks lie. A client sends path, the request reaches the server and creates a project, but the 201 response is lost in transit. The client times out and retries. Without an idempotency key, you now have two identical projects.

With an idempotency key, Alloovium caches the original response for 24 hours keyed on key. The second request replays the first response verbatim — status, headers, and body — and does not re-execute the handler.

This is the Stripe pattern

If you've used Stripe's idempotency keys, Alloovium's behaves identically: same 24-hour window, same "different body ⇒ conflict" semantics, same in-progress locking.

The header

http
POST /api/v2/vault/projects HTTP/1.1 Host: api.alloovium.com Authorization: Bearer ak_live_... Idempotency-Key: create-tower-2026-04-08 Content-Type: application/json {"name": "Downtown Tower", "project_type": "commercial"}

The key must match the pattern pattern — 1 to 255 characters from chars. Anything else returns error.

Pick keys that are unique to the intent

A UUID per logical operation is ideal. "create-tower-2026-04-08" is fine for a nightly cron. Do NOT reuse a single static key across unrelated calls — you will hit cached responses for the wrong operation.

Which methods are covered

MethodIdempotency middleware runs?
GETno — safe by design
HEADno — safe by design
OPTIONSno — safe by design
POSTyes
PATCHyes
PUTyes
DELETEyes

GETs ignore the header entirely. If you attach header to a GET the API will neither cache nor reject it.

Replay behaviour

When a key matches a cached 2xx response, the server returns the full original response and adds:

http
HTTP/1.1 201 Created Content-Type: application/json Idempotent-Replayed: true {"id": "8f2e3c1a-...", "name": "Downtown Tower", ...}

The header header is the signal that no work was performed. Use it for observability — log it, expose it in traces — but do not branch on it. The response body is exactly what the first call returned.

Only 2xx responses are cached

4xx and 5xx responses bypass the cache entirely. That means if your first call returns code, fixing the body and retrying with the same key will execute a fresh request. Only successful outcomes are frozen.

Mismatched body

If you reuse an idempotency key but send a different body, the server does not overwrite the original. Instead you get:

json
{ "type": "https://api.alloovium.com/errors/idempotency_key_reused", "title": "Idempotency key reused", "status": 422, "detail": "The Idempotency-Key was previously used with a different request body.", "code": "idempotency_key_reused" }

This is a hard contract: once a key is bound to a body, that binding is immutable for the 24-hour window. The server hashes a canonical form of the body at first write and compares on every replay.

In-progress collisions

If the original request is still running when a retry arrives with the same key, the server returns:

json
{ "type": "https://api.alloovium.com/errors/idempotency_in_progress", "title": "Idempotency key in progress", "status": 409, "detail": "A request with this Idempotency-Key is still in progress. Retry shortly.", "code": "idempotency_in_progress" }
http
HTTP/1.1 409 Conflict Retry-After: 5

The in-progress lock has a 60-second TTL — if the original handler dies without caching a response, the lock expires and the next retry executes for real. Respect the header header; a 5-second backoff is usually enough to catch the cached result.

Size limits

Idempotency requires buffering the request body so the server can hash it. Bodies above limit bypass idempotency entirely — they execute normally and never write to the cache, regardless of whether you attached a key.

File uploads do not get idempotency

upload and fill send multipart bodies that are typically much larger than 64 KiB. Retrying these is your responsibility — see field on the ingestion job for a natural deduplication key.

Storage and scope

DimensionBehavior
BackendRedis, with SETNX + TTL for the in-progress lock
TTL24 hours from first write
Scope key(api_key_fingerprint, method, path, idempotency_key)
VisibilityEach API key has its own namespace — no cross-credential bleed
CleanupAutomatic via Redis expiry — no manual eviction needed

A correct retry loop

python
import time, uuid, httpx def create_project(client, name): idem_key = f"create-project-{uuid.uuid4()}" body = {"name": name, "project_type": "commercial"} for attempt in range(5): resp = client.post( "https://api.alloovium.com/api/v2/vault/projects", json=body, headers={ "Authorization": f"Bearer {API_KEY}", "Idempotency-Key": idem_key, }, ) # Success (fresh or replayed) — both are fine if resp.is_success: if resp.headers.get("Idempotent-Replayed") == "true": log.info("recovered from earlier success") return resp.json() # Original still in progress — wait and retry with the SAME key if resp.status_code == 409 and resp.json().get("code") == "idempotency_in_progress": time.sleep(int(resp.headers.get("Retry-After", "5"))) continue # Rate limited — honour Retry-After if resp.status_code == 429: time.sleep(int(resp.headers.get("Retry-After", "5"))) continue # 5xx transient — exponential backoff, same key if 500 <= resp.status_code < 600: time.sleep(min(30, 2 ** attempt)) continue # 4xx other than 429/409 — do not retry resp.raise_for_status() raise RuntimeError("max retries exhausted")

Same key across every retry

The most common mistake: generating a new UUID on each retry. That defeats the whole mechanism. Generate the key once, outside the retry loop, and keep it stable across every attempt for the same logical operation.

Observability

Every request gets an header header in the response — see Errors. When an idempotent replay happens, the replayed response carries the original request ID, not the current one. If you are debugging a double-submit scenario, log both the incoming request ID (from your own client) and the xheader returned by the server — they will match on replay and diverge on a fresh call.

See also

  • Rate Limits — retry strategy for 429 and 5xx.
  • Errors — full error code reference including codes variants.