Idempotency
Make POSTs safe to retry with the Idempotency-Key header.
Network failures during a POST leave you in the worst-of-both-worlds zone:
the call may have succeeded server-side, may have been dropped, or may still
be in flight. The Public API solves this with idempotency keys: a
client-supplied identifier that lets you safely retry mutating calls without
risk of duplicate work.
Sending the header
Pass a unique key in Idempotency-Key on every POST. The header must be
between 1 and 255 printable ASCII characters with no whitespace
([\x21-\x7e]{1,255}); a UUID v4 (uuidgen / crypto.randomUUID()) or a
hash of the business event are both fine. A bad value is rejected with
validation_error (400).
curl -X POST https://api.rankprompt.com/v1/brands \
-H "Authorization: Bearer rp_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-H "Idempotency-Key: $(uuidgen)" \
-d '{"name": "Acme Co"}'import { randomUUID } from 'node:crypto';
await fetch('https://api.rankprompt.com/v1/brands', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.RANKPROMPT_API_KEY}`,
'Content-Type': 'application/json',
'Idempotency-Key': randomUUID(),
},
body: JSON.stringify({ name: 'Acme Co' }),
});import os, uuid, httpx
httpx.post(
"https://api.rankprompt.com/v1/brands",
headers={
"Authorization": f"Bearer {os.environ['RANKPROMPT_API_KEY']}",
"Idempotency-Key": str(uuid.uuid4()),
},
json={"name": "Acme Co"},
timeout=30,
)The header is required on every POST: a POST without
Idempotency-Key is rejected with idempotency_key_required (400).
For PATCH and DELETE the header is optional but honored: if you send
one we’ll deduplicate the call, otherwise the request runs normally. GET
ignores the header.
What happens on retry
When the server sees a key it has processed before, it short-circuits the
business logic and replays the original response with the same status,
the same JSON body, the original Content-Type and quota (X-RP-Quota-*)
headers, plus a marker:
HTTP/1.1 201 Created
Content-Type: application/json
X-RP-Quota-Used: 18
X-RP-Quota-Remaining: 982
Idempotent-Replayed: true
{ ...same JSON the first call returned... }
A replay does not debit your quota again. Sensitive headers
(Set-Cookie, Authorization, Date) are deliberately stripped from the
cached response.
So your “did it actually go through?” recovery flow is just: re-send the same request with the same key. The first call wins; every retry returns the same response shape until the key expires.
Server failures (>= 500) don’t become permanent: the in-flight row is
discarded so your next retry with the same key actually re-runs the handler
instead of replaying the failure.
Errors you might hit
idempotency_key_required(400):POSTwithout the header.validation_error(400): the header didn’t match[\x21-\x7e]{1,255}(no whitespace, 1-255 printable ASCII chars).idempotency_key_mismatch(409): same key reused with a different request body or against a different route. The fingerprint issha256(method + route_template + body). Pick a fresh key or send the original payload byte-for-byte to the same endpoint.idempotency_key_in_flight(409): you’re racing yourself. A previous request with this key is still being processed; wait a moment and retry with the same key.
TTL & best practices
- Keys are remembered for 24 hours. After that the slot is freed and the same key is treated as a brand-new request.
- Generate keys at the business-event level, not at the HTTP level. “create-brand-acme-2026-04-19” is better than a fresh UUID per HTTP retry, because it deduplicates app-level retries too (e.g. a backoff job that fires a second time when the first one’s success is in doubt).
- For background workers, persist the generated key alongside the job record before the first attempt. That way every retry uses the same key even if the worker crashes and restarts.