POST /action-items request twice — without idempotency that creates two duplicate items. SiteVisit mirrors Stripe’s pattern: pass an Idempotency-Key header on any write, and we’ll guarantee the request runs at most once, returning the original response for any retries within the next 24 hours.
How to use it
Pass a header that’s unique to the intent of the request:What the server does
When to use it
- Always on write retries. If your code has any retry / circuit-breaker logic around POST/PATCH/DELETE calls, generate a key once per logical attempt and pass it on every retry. Otherwise you’ll create dupes on flaky networks.
- On webhooks fanning into the API. When your webhook handler kicks off a SiteVisit write, use the webhook’s
delivery_id(or a deterministic hash of the payload) as the key — receivers retry, but you’ll only write once. - On batch jobs. When a nightly script reconciles state, key each write by
(job_run_id, record_id)so a partial run can resume safely.
When you can skip it
GETand other read-only requests — naturally idempotent.DELETE— naturally idempotent (deleting the same id twice returns the same observable result either way). The header is accepted but doesn’t do anything special.- One-off scripts you’ll never retry. Even then, it’s free insurance.
Detecting a replay
Successful replays come back with theIdempotent-Replay: true response header. Useful in logs to distinguish a network retry from a genuinely repeated user action.
Edge cases
Same key, different body → 422. Means your client cached a key longer than it should have and is now sending a different request under it. The server can’t honor “return the original response” because the original response doesn’t match the new request — that would be lying. Use a fresh key. Concurrent retries → 409. If two requests with the same key arrive at the same instant, one wins the reservation race and runs the handler; the other returns409 idempotency_in_progress. A short backoff + retry will succeed (it’ll then get the stored response from the winner).
Past TTL → fresh request. After 24 hours we purge the record. If you retry with the same key 25 hours later, it’ll run as a fresh request (which may create a duplicate). Either set your retry window shorter than 24h, or use longer-lived keys + a job-id scoping scheme.