Phase 3 — Idempotency¶
Goal¶
A timed-out POST that the client retries must not double-post money.
Worked example¶
POST /api/v1/expenses
Idempotency-Key: 9f3c-ada-hotel
{"vendor":"Hotel Mitte","description":"...","amount":"120.00",...}
# Client times out, retries the exact same body + key:
POST /api/v1/expenses
Idempotency-Key: 9f3c-ada-hotel
...
→ same JSON body, header Idempotency-Replayed: true, handler ran once
Redis claim: SET key NX EX 86400 — write only if the key does not exist,
expire after 24h. See the glossary.
Failure case¶
While the first request is still running, a retry gets 409
IdempotencyConflict. If Redis is down, the middleware fails open
(request proceeds, retry protection is lost) — documented in
ADR 003. The response rebuild must keep FastAPI BackgroundTasks attached so
inline receipt extraction still runs when a key is present
(api/middleware/idempotency.py). ARQ jobs do not ride on that object
(ADR 007).