Keyboard Shortcuts N Next post
P Previous post
S Save / unsave
R Read aloud
T Toggle theme
/ Focus search
Esc Close panels
🔥
Ready to read...
API design APIs APIs From Zero to Real-World API Testing & Automation Module 3 — REST APIs REST API

API Versioning and Idempotency — Keeping APIs Stable Over Time

Reviewed & accurate
AI Summary

What You'll Learn

  • Why APIs need versioning and the three common ways to do it
  • What idempotency means and which HTTP methods are idempotent
  • How idempotency keys make POST safe to retry
  • How to design APIs that survive changes and network failures

Why This Matters

APIs change. Fields get added, renamed, removed. Behavior shifts. Without versioning, every change breaks existing clients. Without idempotency, every network retry risks duplicating side effects (double charges, double orders, double emails). Together, these two concepts are what separate a production-grade API from a prototype.

Part 1 — API Versioning

Versioning lets you introduce breaking changes without breaking existing clients. Old clients keep using /v1/; new clients use /v2/.

The three common styles

1. Path versioning (most common)

GET /v1/users/123
GET /v2/users/123

Simple, explicit, visible in logs and analytics. Used by GitHub, Stripe, Twitter.

2. Header versioning

GET /users/123
Accept: application/vnd.example.v2+json

URL stays clean; version is in the Accept header. Used by GitHub (for some endpoints) and many enterprise APIs. Harder to test in a browser.

3. Query parameter versioning

GET /users/123?version=2

Easy to implement but easy to forget. Breaks caching because the URL is the same for different versions.

Which to choose?

StyleProsCons
PathSimple, explicit, cacheableURLs change between versions
HeaderClean URLsHard to test, easy to forget
QueryEasyBreaks caching, easy to forget

For most APIs, path versioning is the best default.

What counts as a breaking change?

  • Removing a field from the response
  • Renaming a field
  • Changing a field's type (string → number)
  • Changing a status code (200 → 201)
  • Making an optional parameter required
  • Changing default behavior (e.g., pagination size)

Non-breaking changes (adding a new optional field, adding a new endpoint) do not require a new version.

Part 2 — Idempotency

Idempotency means: calling the same operation multiple times has the same effect as calling it once.

Idempotent HTTP methods

MethodIdempotent?Safe (no side effects)?
GETYesYes
PUTYesNo
DELETEYesNo
POSTNoNo
PATCHNot guaranteedNo

Why does this matter?

Networks fail. Timeouts happen. Clients retry. If a client calls POST /orders to place an order, the network drops the response, and the client retries — did the first call succeed? Should the retry create a second order?

For idempotent methods, this is not a problem. PUT /users/123 with the same body always results in the same final state, whether called once or ten times. DELETE /users/123 is the same — after the first call, the user is gone; subsequent calls return 404 but do not change anything.

But POST /orders is not idempotent. Retrying creates multiple orders. That is bad for e-commerce, payments, and any operation with side effects.

The solution: idempotency keys

The client generates a unique ID (a UUID) and sends it with the request. The server stores the ID and the response. If the same ID comes in again, the server returns the stored response instead of re-executing.

POST /payments HTTP/1.1
Idempotency-Key: 7c8d3f1e-2a4b-4c5d-9e8f-1a2b3c4d5e6f
Content-Type: application/json

{"amount": 1000, "currency": "INR"}

First call: server processes the payment, returns 201 Created, stores the response keyed by the idempotency key.

Retry with the same key: server recognizes the key, returns the same 201 Created response — does not create a second payment.

Real-world example: Stripe uses idempotency keys extensively for exactly this purpose. See Stripe's idempotent requests documentation.

How to Implement Idempotency

  1. Client generates a UUID for each logical operation.
  2. Client sends it in a header (commonly Idempotency-Key).
  3. Server stores the key + the response for some time (24 hours is typical).
  4. On retry with the same key: server returns the stored response without re-executing.
  5. On retry with the same key but different body: server returns 409 Conflict — the key is in use with different parameters.

Common Mistakes

  1. Not versioning at all. "We'll add it when we need it" never works. Start with /v1/ from day one.
  2. Renaming fields in a non-breaking version. Adding fields is fine; renaming or removing requires a new version.
  3. Assuming POST is idempotent. It is not. Without an idempotency key, retries create duplicates.
  4. Using the same idempotency key for different operations. Each logical operation needs a fresh UUID.
  5. Forgetting that DELETE is idempotent. DELETE /users/123 called twice should not error the second time — it should return 404 (already gone) or 204 (idempotent success).

Practical Exercise (5 minutes)

Classify each operation as idempotent or not:

  1. GET /users/123
  2. POST /users
  3. PUT /users/123
  4. PATCH /users/123 with body {"email": "new@x.com"}
  5. DELETE /users/123
  6. POST /payments with an idempotency key

Answers: 1. Idempotent. 2. Not idempotent. 3. Idempotent. 4. Idempotent in this case (setting one field to a fixed value). 5. Idempotent. 6. Idempotent because of the key.

Mini Challenge

Design an idempotency strategy for a "send email" endpoint. The client should be able to retry safely without sending duplicate emails. What header does the client send? What does the server store? How does it handle a retry? What if the client retries with the same key but a different email body?

Key Takeaways

  • Versioning lets you make breaking changes safely. Path versioning (/v1/) is the most common style.
  • Breaking changes: removing/renaming fields, changing types or status codes, making optional params required.
  • Idempotency: same operation called multiple times = same final state.
  • GET, PUT, DELETE are idempotent. POST is not. PATCH is conditional.
  • For non-idempotent operations (POST /payments), use an idempotency key header.
Course continuity
Previously: Lesson 19 covered pagination, filtering, sorting.
Today: You learned how to keep APIs stable across versions and safe across retries.
Next: Lesson 21 closes Module 3 with REST error handling and status code conventions.

FAQ

How long should an idempotency key be stored?

Typically 24 hours. Long enough to cover network retries, short enough to keep storage bounded. Stripe stores them for 24 hours; other APIs use 24 hours to 7 days. Document the window for your API.

Do I need a new version for every change?

No. New endpoints, new optional fields, and new response fields are non-breaking — they go in the current version. Only breaking changes (removing or renaming) require a new version. Most APIs go years between major versions.

Test Your Knowledge
How did you find this?

Comments

Join the discussion! Sign in with your Google or Blogger account, or comment as Anonymous - no account needed. For quick questions, also reach me on Telegram @cytestch.

Comments