CodeVix Labs
Engineering Team
TL;DR: Idempotency in API design means an operation can be sent more than once and still produce the same result, without duplicate side effects like double charges or duplicate orders. You achieve it by making read/delete operations naturally repeatable and by attaching an idempotency key to write operations so the server recognizes and de-duplicates retries. It is one of the cheapest reliability investments a product can make.
What is idempotency in API design?
An operation is idempotent if performing it once and performing it many times leave the system in the same state. Reading a customer record is idempotent: ask ten times, get the same answer, nothing changes. Charging a credit card is not naturally idempotent: send the request three times and, without protection, you charge the customer three times.
Idempotency in API design is the discipline of making sure that when a client retries a request, the server does the real work exactly once. This matters because networks are unreliable. A mobile app on a train, a webhook from a payment provider, a background job behind a flaky load balancer, all of these can send the same request twice, either because the first response was lost or because a timeout triggered an automatic retry. The request arrived; the reply didn't. The client has no way to know, so it retries. A well-designed API absorbs that retry safely.
Why does idempotency matter for real products?
The failure it prevents is expensive and visible to customers. Duplicate charges trigger chargebacks and support tickets. Duplicate orders ship real inventory and cost real money to unwind. Double-sent notifications erode trust. In fintech, marketplaces, and healthtech, these are not edge cases, they are the cases regulators, banks, and users notice first.
The uncomfortable truth is that the client will retry whether or not the server is ready for it. If you don't design for safe retries, you are effectively betting that the network never drops a response, which it will. Payment processors like Stripe learned this early, which is why idempotency keys are a first-class part of their API rather than an afterthought. If you are building anything that moves money, read our companion piece on how to build a fintech app alongside this one.
Idempotency is not a feature users ask for. It is the reason they never have to.
Which HTTP methods are idempotent by definition?
The HTTP spec already classifies methods, and it pays to respect that contract because clients, proxies, and browsers rely on it when deciding whether an automatic retry is safe.
| Method | Idempotent? | Typical use | Notes |
|---|---|---|---|
GET | Yes | Read a resource | Should never change state |
PUT | Yes | Replace a resource | Same body, same final state |
DELETE | Yes | Remove a resource | Deleting twice still leaves it deleted |
HEAD | Yes | Read metadata | Like GET without a body |
POST | No | Create / trigger action | Needs an idempotency key to be safe |
PATCH | Depends | Partial update | Idempotent only if the change is absolute, not relative |
The nuance most teams miss is PATCH. Setting status = "paid" is idempotent because applying it twice lands in the same place. Sending balance = balance - 10 is not, because each retry subtracts again. When a partial update expresses a relative change, treat it like a POST and protect it.
How do idempotency keys actually work?
For operations that are not naturally idempotent, the standard solution is an idempotency key: a unique token the client generates (usually a UUID) and sends with the request, commonly in a header such as Idempotency-Key. The server uses it to recognize retries.
The flow looks like this:
- The client generates a unique key for the logical operation, for example creating order #4821, and stores it before sending.
- The server receives the request and checks whether it has seen that key.
- If the key is new, the server performs the work, saves the result against the key, and returns the response.
- If the key already exists, the server skips the work and returns the stored response from the first attempt.
A minimal server-side sketch, using the kind of Node.js and PostgreSQL stack we build on, makes the idea concrete:
// Pseudocode, not production-hardened
async function createCharge(req) {
const key = req.headers['idempotency-key'];
const existing = await db.idempotency.find(key);
if (existing) return existing.response; // replay the first result
return db.transaction(async (tx) => {
const charge = await paymentProvider.charge(req.body);
await tx.idempotency.save(key, charge); // store result atomically
return charge;
});
}
Two details separate a toy from a reliable implementation. First, the check-and-save must be atomic, ideally protected by a unique constraint on the key column or a transaction, so that two simultaneous retries cannot both slip through. Second, stored keys need an expiry (24 hours is a common window) so the table doesn't grow forever and stale keys don't collide with genuinely new operations.
What should you store, and for how long?
Store the key, the resulting response body and status code, and a timestamp. Many teams also store a hash of the request body so that if the same key arrives with a different payload, the server can reject it with a clear error instead of silently returning the wrong result. That guard catches client bugs early. Because this de-duplication table is a hot path, indexing it correctly matters, our PostgreSQL indexing guide covers the patterns we use.
When should you add idempotency, and when can you skip it?
Idempotency is not free, so apply it where retries cause real harm rather than everywhere.
- Always protect: payments, order creation, money transfers, sending emails or SMS, provisioning accounts, anything that costs money or is externally visible.
- Usually fine without keys: pure reads, and writes that are already idempotent by nature such as "set this flag to true".
- Design carefully: anything that fans out to third-party systems, because you are now coordinating retries across services you don't control. This overlaps heavily with building webhooks reliably, where the same request genuinely will arrive more than once.
A practical rule: if a duplicate would generate a support ticket, protect it. If a duplicate is harmless, don't add the machinery.
How does idempotency fit alongside other reliability patterns?
Idempotency rarely travels alone. It is the safety net that makes several other patterns usable. Automatic client retries are only safe because the server is idempotent. Exactly-once message processing in a queue depends on de-duplicating by key. Optimistic concurrency control (versioned updates) and idempotency solve different problems, one prevents lost updates, the other prevents duplicate operations, and mature systems use both.
None of this is exotic, but it is easy to get subtly wrong, and the bugs only surface under real network stress, which is exactly when they hurt most. This is where experienced engineering earns its keep. At CodeVix Labs, a QA-first software company, we treat safe retries and idempotency as part of the definition of done for any endpoint that touches money or inventory, not a patch bolted on after the first duplicate-charge incident. If you want that reviewed or built into your product, our engineering services and team are a good place to start.
Frequently asked questions
Is idempotency the same as caching?
No. Caching serves a stored response to improve speed and reduce load, and it applies mainly to reads. Idempotency guarantees a write operation runs its side effects only once even if the request is repeated. They can look similar, both may return a saved response, but idempotency is about correctness of state, not performance.
Who generates the idempotency key, the client or the server?
The client generates it, because only the client knows that two requests represent the same logical operation, for example "the single checkout the user clicked once". A common mistake is generating the key on the server, which defeats the purpose, since a retried request would arrive with a new key and be treated as new work.
Does using PUT instead of POST make my API idempotent automatically?
Only if the operation is truly a full replacement to a known resource ID. PUT /orders/4821 with the complete order body is idempotent. But if you don't know the ID in advance and are effectively creating a resource, you still need an idempotency key. The method name is a contract, not a guarantee your handler honors it.
How long should idempotency keys be stored?
Long enough to cover realistic retry windows but short enough to control storage. Twenty-four hours is a widely used default; some teams keep keys for a few days for high-value operations. Store an expiry with each key and clean up expired ones so the de-duplication table stays fast.
Idempotency is a small idea with an outsized payoff: it turns "the network might duplicate this" from a production incident into a non-event. If you are weighing who should build this into your stack, our guides on choosing an engagement model and our recent work can help you decide.
Ready to discuss your project?
Book a free 15-minute technical audit with our engineering team.