Idempotency
Safe retries via `Idempotency-Key`.
POST /audit accepts an Idempotency-Key header. CiteFlow caches the
response for 24 hours; sending the same key with the same body
returns the original response — same audit_id, same status, only
one credit deduction.
POST /api/v1/audit HTTP/1.1
Authorization: Bearer ckf_…
Idempotency-Key: ord_42-attempt-1
Content-Type: application/json
{ "url": "https://example.com", "type": "seo" }The replay response carries an extra header so you can tell:
HTTP/1.1 202 Accepted
X-Idempotent-Replay: trueKey requirements
- Max 255 characters, any printable ASCII.
- Unique per logical operation in your system. UUID v4, ULID, or
${order_id}-${attempt}all work. - Don't reuse across different request bodies — that returns
409 IDEMPOTENCY_CONFLICT.
When to use
Always, for POST /audit. Networks lose responses; clients lose
process. Retrying without an idempotency key risks duplicate charges
and duplicate audits.
The official Node and Python SDKs auto-generate keys per call if the caller doesn't supply one.
Window
24 hours. After the window expires the key is forgotten and a fresh call with the same body creates a new audit.
Conflict detection
If you send the same Idempotency-Key with a different body
within 24h:
{
"error": {
"code": "IDEMPOTENCY_CONFLICT",
"message": "Idempotency-Key reused with a different request body",
"doc_url": "https://docs.citeflow.io/errors/idempotency-conflict"
}
}Resolution: generate a fresh key for the new payload.
Concurrency
Two concurrent requests with the same key + same body — only one
completes; the second receives 409 IDEMPOTENCY_CONFLICT (raced)
or the cached replay (if the first finished first). Sleep ~100ms
and retry; you'll get the cached replay.
What's NOT idempotent
POST /audit/{id}:cancel— cancellations are naturally idempotent (terminal states return current state).POST /billing/topup— each call mints a fresh Stripe Checkout Session by design; the same partner can have multiple open sessions.POST /webhooks/deliveries/{id}/replay— replays are naturally idempotent (re-firing the same event_id UPSERTs the delivery row).
Storage
CiteFlow stores the SHA-256 hash of partner_id + idempotency_key
plus the response body in Upstash Redis with a 24h TTL. The cache
lives outside the primary database to keep /audit p99 latency low.