BugMojoBugMojoBugMojo
FeaturesPricingBlogHelpAbout
Add to ChromeLog inGet started
BugMojoBugMojo

Bug reports that actually help fix bugs — capture, replay, share.

A product of Softech Infra.

Product

  • Features
  • Pricing
  • Browser extension
  • Get started
  • Log in

Resources

  • Help & guides
  • Blog
  • Compare
  • Glossary

Company

  • About
  • Contact
  • Security
  • Privacy
  • Terms
  • Sitemap
© 2026 BugMojo. All rights reserved.
AllGuidesEngineeringPlaybooksCompareGlossaryAlternativesBy roleBug tracking by framework
  1. Home
  2. Blog
  3. Glossary
  4. What Is Idempotency? (In APIs and HTTP)
Glossary

What Is Idempotency? (In APIs and HTTP)

Idempotency means doing an operation many times has the same effect as doing it once. Here is why retries make it essential in APIs, which HTTP methods are idempotent, and how idempotency keys stop a retried request from double-charging a card.

Hrishikesh BaidyaHrishikesh Baidya·Jul 16, 2026·6 min read
Glossary
Isometric line-art timeline of the same request sent three times yet producing one identical result, lime on a dark canvas
TL;DR
  • Idempotency means performing an operation many times has the same effect as performing it once.
  • It matters because networks are unreliable and clients retry — without it, a retried charge or order can double-fire.
  • Per RFC 9110, GET, HEAD, PUT, DELETE are idempotent; POST generally is not; PATCH may or may not be.
  • Idempotent is not the same as safe — DELETE is idempotent but destroys data.
  • Make POST safe to retry with an idempotency key — the Stripe/PayPal pattern.

Definition

An operation is idempotent if performing it multiple times has the same effect as performing it once. Sending the same request twice leaves the system in the same state as sending it once. It is a promise about the effect on server state, not about identical response bodies.

The idea predates the web. In mathematics a function f is idempotent when f(f(x)) = f(x) — applying it again changes nothing. Absolute value is the classic example: abs(abs(-3)) is just abs(-3). MDN carries that straight into HTTP: an operation is idempotent if it 'can be applied multiple times without changing the result beyond the initial application.'

The concrete test is state, not the number of calls. Setting a user's plan to pro is idempotent — run it once or a hundred times and the plan is pro. Appending an item to a cart is not idempotent, because each call adds another item and the end state keeps changing. That single distinction is what makes an API endpoint safe to retry or dangerous to retry, and it is the reason the concept earns its own word.

Why it matters: networks are unreliable

Idempotency matters because of one unavoidable fact: the network can fail after the server did the work but before the response gets back. The client sees a timeout and cannot tell whether the request succeeded or vanished. Its only reasonable move is to retry — and retries are built into virtually every HTTP client, load balancer, and job queue. If the operation is idempotent, the retry is harmless. If it is not, the retry is a second charge, a duplicate order, a double-sent email.

The double-charge scenario

A user taps Pay. The server charges the card successfully, then the response is lost to a flaky connection. The client retries the exact same POST /charge. A naive server charges the card again. The user is out twice the money, and the only record that anything went wrong is a support ticket. This is not a rare edge case — at scale, timed-out-but-succeeded requests happen constantly, which is why payment APIs treat idempotency as mandatory, not optional.

HTTP methods and idempotency

RFC 9110, the HTTP semantics spec, is explicit about which methods carry the idempotency guarantee. GET, HEAD, PUT, DELETE, OPTIONS, and TRACE are idempotent by definition. POST is generally not — two identical POSTs to a collection usually create two resources. PATCH is left to the implementation: replacing a field with a fixed value is idempotent, but a patch that means 'increment this counter' is not.

FeatureMethodIdempotent?Safe (read-only)?
GET / HEAD—✓✓
PUT—✓—
DELETE—✓—
POST———
PATCH—Depends—
Per RFC 9110 and MDN — 'safe' means read-only, 'idempotent' means repeat-harmless. They are different axes.

Notice the second column. Idempotent is not the same as safe. A safe method never changes server state at all — GET and HEAD only read. An idempotent method may change state, but repeating it lands on the same end state. DELETE proves the two are different: it is idempotent (deleting twice leaves the thing deleted) yet emphatically not safe (the first call destroys data). Keep the axes separate — 'safe' asks does it modify anything, 'idempotent' asks is repeating it harmless.

One honest caveat the spec is careful about: idempotency is about the effect on state, not about getting an identical response. A first DELETE may return 200 and a second may return 404 — different status codes (see HTTP status codes explained), same end state. The resource is gone either way, so the method is still idempotent.

Making non-idempotent operations idempotent

Most business-critical writes are POSTs — charge a card, place an order, send an invoice — so 'just use PUT' is not an answer. The job is to add idempotency on top of a non-idempotent operation. Here is the naive charge endpoint that double-charges on retry, next to the idempotency-key version that does not:

charge.jsjavascript
// NAIVE — a retry after a lost response charges the card twice
app.post('/charge', async (req, res) => {
  const { amount, cardId } = req.body;
  const charge = await gateway.charge(cardId, amount); // runs every call
  await db.charges.insert(charge);
  res.json(charge);
});

// IDEMPOTENT — client sends a unique key; server dedups on it
app.post('/charge', async (req, res) => {
  const key = req.header('Idempotency-Key');   // e.g. a client UUID
  const seen = await db.idempotency.get(key);
  if (seen) return res.json(seen.response);    // replay: return stored result

  const charge = await gateway.charge(req.body.cardId, req.body.amount);
  await db.idempotency.put(key, { response: charge });
  res.json(charge);
});

The idempotency key is a unique token the client generates (usually a UUID) and sends with the request. The server stores the result under that key; when the same key arrives again — because the client retried — it returns the stored response instead of re-charging. This is exactly the pattern Stripe and PayPal expose via an Idempotency-Key header. It requires server-side state: without somewhere to remember keys, there is no dedup, and that store is the real cost of the pattern.

Idempotency keys are the general tool, but there are lighter options when the shape of the data allows it:

  • Natural unique constraints — dedup on a business id (a database UNIQUE on order_number makes a duplicate insert fail loudly instead of double-creating).
  • Upserts — INSERT ... ON CONFLICT DO UPDATE collapses 'create or update' into one repeatable operation.
  • Conditional requests — ETag plus If-Match lets a write apply only if the resource has not changed since you read it, so a stale retry is rejected.
Idempotency, race conditions, and retries

Two identical requests arriving nearly simultaneously is both an idempotency problem and a race condition — the dedup check and the write must be atomic, or two retries can both miss the stored key and both charge. And retries themselves are usually driven by backoff and rate-limiting / Retry-After responses, which is why idempotency and retry policy are always designed together, never separately.

How this shows up in a BugMojo bug report

Double-submit and retry bugs are miserable to reproduce from a description, because the trigger is a timeline, not a single action: a request that succeeded, a lost response, and a retry that fired the write again. 'The customer was charged twice' tells you the symptom but not the sequence. What you need is the actual network trace — and that is what BugMojo captures alongside the session.

When a report comes in, the extension has already recorded the network panel, so you can see the two POST /charge calls with the same body and — critically — whether they carried the same Idempotency-Key or none at all. Pair that with the console (an unhandled promise rejection on the first attempt is a common trigger; see fixing uncaught-in-promise errors) and the duplicate request stops being a mystery. The captured request timeline is the proof that a retry, not a user, sent the second charge.

Key takeaway

Idempotency means repeating an operation lands on the same end state as doing it once — a promise about effect, not identical responses. Networks fail mid-flight, so clients retry; idempotency is what makes that retry safe. GET/HEAD/PUT/DELETE are idempotent, POST is not, PATCH depends — and idempotent is not the same as safe. For the writes that matter, add an idempotency key and the server-side store to dedup on it.

See the duplicate request, not just the double charge

BugMojo captures the full network timeline with every bug report — so a retried, double-fired POST shows up as two real requests you can inspect, keys and all, instead of a symptom you have to guess at.

Install the extension

Frequently asked questions

Frequently asked questions

Sources

  1. Idempotent — Glossary definition of an operation with the same effect whether applied once or many times — MDN Web Docs (2026)
  2. RFC 9110: HTTP Semantics — §9.2.2 Idempotent Methods (GET, HEAD, PUT, DELETE, OPTIONS, TRACE) — IETF RFC 9110 (2022)
  3. HTTP request methods — per-method safe/idempotent/cacheable properties — MDN Web Docs (2026)
  4. Idempotence — the mathematical origin: f(f(x)) = f(x) — Wikipedia (2026)
Share:
Hrishikesh Baidya
Hrishikesh Baidya· Chief Technology Officer

Hrishikesh Baidya is the CTO at Softech Infra. He is drawn to architecture that is invisible — systems that simply work — and leads the engineering behind BugMojo.

On this page

  • Definition
  • Why it matters: networks are unreliable
  • HTTP methods and idempotency
  • Making non-idempotent operations idempotent
  • How this shows up in a BugMojo bug report

Get bug-tracking insights, weekly.

Engineering deep-dives, QA playbooks, and honest tool comparisons. No spam — unsubscribe in one click.