Idempotency: How to Design APIs and Jobs That Survive

An operation is idempotent when running it multiple times with the same input has the same effect as running it once.

Idempotency: The One Property That Makes Distributed Systems Bearable

An operation is idempotent when performing it multiple times with the same input produces the same effect as performing it once. GET is naturally idempotent; DELETE and PUT are idempotent by convention; POST is not, which is why POST endpoints that create resources need special design. In distributed systems every network call can fail, retry, and duplicate — idempotency is what turns 'the client saw a timeout so it retried' from a double-charged customer into a no-op.

The idempotency key pattern

The standard technique for making non-idempotent operations idempotent: require the client to send a unique key (idempotency_key or Request-Id header) with each new logical operation. The server stores the key with the result of the first successful execution. On retry with the same key, the server returns the stored result without re-executing. Stripe's API is the reference implementation; keys are typically UUIDs generated by the client, stored server-side for 24 hours or longer. This makes 'retry on network error' universally safe for the client.

Database-level idempotency

Beyond API keys, the underlying writes need to be safe against duplicate execution. Techniques: unique constraints on natural keys (a payment row keyed by (customer_id, invoice_id, attempt)); INSERT ... ON CONFLICT DO NOTHING/UPDATE; conditional updates with version checks (WHERE version = expected_version). Blind INSERTs and unchecked UPDATEs are the primary source of duplicate rows and lost updates. Every write in a background job path should be reviewable against the question 'what if this runs twice?'

Side effects and external calls

The hardest idempotency problems are side effects the database can't see: sending emails, calling third-party APIs, publishing events. Techniques: (a) use the vendor's idempotency support (Stripe, Twilio, most modern APIs accept idempotency keys). (b) record the intent in your database before the call, mark completed after; on retry, check the record before calling. (c) accept some duplicate emails and design the message content accordingly ('this is a copy of a message we already sent if you already received it'). Perfect exactly-once delivery across systems is theoretically impossible; the goal is 'at-most-one meaningful effect from the user's perspective.'

Idempotency vs. commutativity

Related but distinct: commutativity means operations can happen in any order and produce the same result (append-only logs, set unions). Idempotency alone doesn't handle ordering — two idempotent operations applied out of order can still produce wrong state. For strong consistency, combine idempotency keys with ordering (sequence numbers) or with an event-sourcing model where the final state is derived from the ordered log. Most CRUD APIs get away with idempotency alone; systems with concurrent conflicting writes need more.

Testing for idempotency

Idempotency bugs are invisible under normal load and destructive under retry storms. Test explicitly: for every write endpoint and every job, add a test that calls it twice with the same input and asserts a single effect. Chaos-testing at the infrastructure level (deliberate worker kills, simulated network partitions) catches the failure modes that unit tests don't. Idempotency is one of those properties where 'we think it works' and 'we tested that it works' are worlds apart.

Frequently asked questions

Should every API endpoint accept an idempotency key?
Every write endpoint — yes. Read endpoints are naturally idempotent. Making idempotency universal across the API removes the client's need to reason about which endpoints are safe to retry.
How long should we store idempotency keys?
24 hours is Stripe's default and works for most cases. Longer (7-30 days) is safer for high-value operations. The tradeoff is storage cost and the small window where an old key might collide with a new one — namespace keys by resource type to reduce collision surface.
Does using UUIDs make writes idempotent?
No. UUIDs prevent primary key collisions but don't prevent duplicate logical operations — two attempts to create a payment with different UUIDs still result in two payments. Idempotency requires that the retry uses the same key as the original attempt, which the client must do deliberately.

Related fundraising guides (40)

Investor directory · Fundraising library · Articles A–Z · Company funding database