The moment a second client depends on your API, every breaking change becomes a coordination problem — and "just update all clients" stops being a real answer
The previous articles on this site covered HTTP status codes, API authentication, and rate limiting. This article addresses API versioning — the strategies for making breaking changes to an API without breaking existing clients — and why the choice of versioning strategy matters less than having a strategy (and, crucially, a deprecation plan) before you need one.
What counts as a "breaking change"
Not all API changes are breaking. Adding a new field to a response, adding a new optional request parameter, adding a new endpoint — these are generally backwards-compatible (existing clients that don't know about the new field/parameter/endpoint simply ignore it, or don't use it, and continue working as before).
Breaking changes are those that cause existing clients, without modification, to fail or produce incorrect results:
- Removing or renaming a field in a response (clients that read that field now get
null/undefined/an error) - Changing a field's type (a field that was a string becomes an integer — clients expecting a string may break)
- Changing the meaning of a field's value (a status field that had values
"active"/"inactive"now has"enabled"/"disabled"— the values the client checks for no longer match) - Removing or renaming an endpoint (clients calling it get 404s)
- Changing required vs optional parameters (a field that was optional becomes required — clients that don't send it now get errors)
Strategy 1: URL path versioning — /v1/, /v2/
The most common, most visible versioning approach: the version number is part of the URL path itself — https://api.example.com/v1/users, https://api.example.com/v2/users.
Advantages:
- Extremely visible and unambiguous — anyone reading a URL or a codebase immediately knows which version is being called
- Easy to test/explore in a browser or any HTTP tool — no special headers needed
- Simple caching — the version is part of the URL, so CDNs and caches can treat
/v1/and/v2/as entirely separate resources with no ambiguity - Easy to run both versions simultaneously — route
/v1/to old code,/v2/to new code, independently
Disadvantages:
- "REST purist" objection: the resource (
/users) hasn't changed — it's the representation of that resource that changed — purists argue the URL should identify the resource, not the representation version, and that versioning belongs in headers - URLs proliferate in documentation, bookmarks, client code — all need updating when clients upgrade
Strategy 2: Accept header versioning — Accept: application/vnd.api+json;version=2
Version information is conveyed via HTTP request headers — typically using Accept (the client specifies what response format/version it's willing to accept) or a custom header like API-Version: 2.
Advantages:
- "Cleaner" URLs — the same URL (
/api/users) serves all versions; the request specifies which version it wants - More aligned with HTTP semantics (content negotiation via
Acceptis a built-in HTTP mechanism)
Disadvantages:
- Much harder to test/share — you can't simply paste a URL to demonstrate "this is v2 of the users endpoint"; the header is invisible in a URL
- Caching complications — caches that don't correctly consider the
Acceptheader as part of the cache key may serve the wrong version to clients that sent different headers — theVary: Acceptresponse header is required to handle this correctly (covered in previous HTTP-headers articles), but isn't universally respected by all caching layers - Discoverability — clients need documentation to even know what header to send and what values are valid
Strategy 3: Query string versioning — ?version=2 or ?v=2
The version is a query parameter: https://api.example.com/users?v=2.
Advantages:
- Visible (like URL path versioning — it's in the URL)
- Easy to test — just add
?v=2to any URL in a browser/tool - No URL structure changes — no
/v1///v2/prefix to route at the server
Disadvantages:
- Cache behavior is version-dependent — caches should include query strings as part of cache keys, but some caching configurations ignore query strings entirely (or strip them) — risking serving the wrong cached version
- Less "official"-looking than URL path versioning — often feels more ad-hoc to API consumers
- Query string pollution — if an endpoint already uses query parameters (pagination, filtering, etc.), adding a
?v=parameter alongside them is functional but messy
Which strategy "wins"? The practical answer
There is no universal consensus. URL path versioning is arguably the most widely adopted in practice (by major public APIs), largely due to its simplicity and visibility — the "REST purist" objection to it is theoretically valid but practically less relevant for most teams. Header-based versioning is theoretically cleaner but operationally more complex to test and cache correctly. Query-string versioning occupies a middle ground that works fine but feels less intentional to many API consumers.
The more important question: regardless of which strategy you choose, do you have:
- A clear definition of what constitutes a "breaking change" (so your team agrees on when versioning is required vs when a change can be made without a version bump)?
- A deprecation policy (how long will
v1continue to work, oncev2exists)? - Communication to clients about where
v1is, what changed inv2, and whenv1will be removed?
These operational questions matter more than the versioning scheme itself.
Deprecation: the part most API designers underplan
Releasing v2 creates two APIs to maintain — v1 and v2 — both receiving (potentially different) bug fixes, both needing security patches, both consuming infrastructure. Without a deprecation timeline, v1 can persist indefinitely, accumulating maintenance burden.
A typical deprecation lifecycle:
- Announce deprecation of
v1— with a specific, committed sunset date - Communicate to clients (API keys that are calling
v1should ideally get direct notification — many APIs add aDeprecationorSunsetresponse header to deprecated-version responses, providing the sunset date in machine-readable form) - At (or after) the sunset date: return
410 Gone(or404) for deprecated-version requests — not a silent redirect tov2, since clients expectingv1semantics may mishandlev2responses even if they're redirected to them
How long before sunset to announce — varies widely by API and client ecosystem — public APIs with many clients (especially third-party clients the API publisher doesn't control) typically need longer deprecation windows than internal APIs where you do control all clients.
How to use the REST API Checker on sadiqbd.com
- Testing versioned endpoints: explicitly set the version-indicating element (URL path, header, or query parameter) in each request — to ensure you're testing the intended version and not relying on a default that may differ in production
- Comparing v1 vs v2 responses: run identical requests against both versions, comparing responses — this is exactly the scenario this tool enables, and is the most direct way to understand what actually changed between versions (beyond what documentation says)
- Checking
Deprecation/Sunsetheaders: if an endpoint you're testing returns these headers — note the sunset date; this is a signal your client integration will need updating before that date
Frequently Asked Questions
Should I version my API from the very beginning, or wait until I actually need a v2?
Starting with /v1/ from day one is generally recommended — even if you hope never to need v2 — because retrofitting versioning into an existing, unversioned API is harder than starting versioned (clients would have to update URLs to add /v1/, which is itself a breaking change if they were calling unversioned endpoints). The cost of starting with /v1/ is nearly zero; the cost of not having done so, when v2 becomes necessary, can be significant. Starting versioned is a cheap, one-time decision that buys future flexibility.
Is the REST API Checker free? Yes — completely free, no sign-up required.
Try the REST API Checker free at sadiqbd.com — test, compare, and debug any REST API endpoint directly in your browser.