Versioning Strategies: URL, Header, Content Negotiation
What This Concept Is
Once you admit breaking changes are sometimes necessary, you need a way for old and new clients to coexist. The four common versioning strategies for REST:
- URL path versioning:
/v1/orders,/v2/orders. The version is a segment of the URL. - Header versioning:
API-Version: 2026-04-10orX-Api-Version: 2. Version travels as an HTTP header. - Media-type (content negotiation) versioning:
Accept: application/vnd.example.order.v2+json. Version is part of the MIME type. - Query-parameter versioning:
?api_version=2. Rarely chosen deliberately, but easy to patch in.
Geewax frames the larger decision as perpetual stability (rarely version; most changes are backward-compatible) vs agile instability (version freely with clear rules) vs semantic versioning (version like software releases). Your versioning strategy is the machine-readable expression of which stance your API takes.
Why It Matters Here
Versioning strategy decides:
- how clients select a version
- how infrastructure (caches, gateways, logs) routes requests
- how you ship and eventually remove old versions
- how a casual consumer finds and remembers "which API they are calling"
Pick once, live with it. Changing versioning strategy mid-life is itself a breaking change.
Concrete Example
URL path versioning
GET /v1/orders/ord_1
GET /v2/orders/ord_1
Pros:
- visible in logs, URLs, bookmarks, support tickets
- trivial routing at the gateway (
/v1/*-> old service pool,/v2/*-> new) - cache keys work correctly without extra vary rules
- humans can test with
curland know what they are calling
Cons:
- REST purists complain that URLs should be stable (but in practice,
/v1/resources/123is stable within v1) - granularity is coarse: usually one version per service, not per resource
Dominant choice in industry. Use this unless you have a specific reason not to.
Header versioning
GET /orders/ord_1 HTTP/1.1
API-Version: 2026-04-10
Pros:
- "clean" URLs;
/orders/ord_1works regardless of version - date-based versioning (Stripe, Twilio) fits naturally - server picks a "pinned" version per account or per request
- resource identity not tied to version
Cons:
- invisible in logs and support tickets unless explicitly captured
- requires care with caches (
Vary: API-Version) - harder to test manually (
curlneeds-H) - per-request debugging is harder when nobody can see the version
Prefer this when:
- you want date-based rollouts ("pin me to 2026-04-10")
- your consumers are primarily programmatic and will not hand-type URLs
Media-type versioning
GET /orders/ord_1 HTTP/1.1
Accept: application/vnd.example.order.v2+json
Pros:
- closest to "pure REST" philosophy
- version is tied to the representation, not the resource
- different resources can version independently
Cons:
- rarely implemented correctly end-to-end
- every client has to negotiate Accept headers
- debugging and tooling experience is worst of the three
- enterprise reality: most engineers have never seen it in production
Use only if you have a strong reason and a team that will maintain the discipline.
Query-parameter versioning
GET /orders/ord_1?api_version=2
Pros: trivial to add; visible.
Cons: not standard; conflicts with actual query filters; cache configs get weird.
Treat as an anti-pattern unless it is the only option available in your gateway.
Mixed example in one API
POST /v1/orders # URL version = major
API-Version: 2026-04-10 # header version = date-based within v1
This is real: Stripe uses URL /v1/ that is effectively permanent, plus a date-based header for per-account pinning. It combines the strengths of both.
Version semantics
Whatever strategy you pick, you also need to decide what a version means:
- Major version (v1, v2): breaking changes. Minor additions live within a major.
- Date version (2026-04-10): snapshot of the API at a point in time. New features arrive in later dates; old clients stay on the old date.
- Semver (1.4.2): less common for APIs than for libraries, but applies to SDKs.
Pick one meaning and document it. "v2" that sometimes includes non-breaking improvements and sometimes breaking changes is not helpful.
Common Confusion / Misconception
"We should version every field." No. Version the whole surface (at least per service) at once. Per-field versioning is operationally impossible.
"We'll bump the version on every release." Versions that change weekly are noise. Bump the major version only when you actually ship breaking changes. Everything else is in-place.
"Internal APIs don't need versioning." They do if you cannot deploy producer and all consumers simultaneously. Decoupled deploys require versioning discipline.
"URL versioning means every resource is duplicated." Only while the versions coexist. And it is usually fine to share implementation under the hood - the contract differs, the backend may not.
"Date-based versioning is clearly superior." It shifts complexity to the server (which must support N pinned versions). It is worth it for some audiences (payment APIs, long-lived integrations) and overkill for most internal services.
How To Use It
Pick a strategy when the API is born:
- Default to URL-path versioning unless you have a specific reason.
- Decide the meaning of a version bump (major only, date-based, semver) and write it in the style guide.
- Document the supported-versions list (which are live, which are deprecated, which are removed).
- Provide a way for clients to declare their version - header, URL, or both.
- Log the effective version on every request for debugging.
- Build the deprecation runway (Cluster 3 concept 15) before you ship v2 - because you will need it.
Within a single version, compatibility rules (concept 13) are absolute: no breaking changes.
Check Yourself
- Why is URL path versioning often preferred despite being criticized as "not RESTful"?
- When would you prefer date-based header versioning over URL versioning? Name a realistic consumer profile.
- Your API is at v1. You want to change one endpoint's response shape. Do you bump to v2 for the whole API or version one endpoint? Defend.
Mini Drill or Application
Pick one of the APIs you have designed in this module and commit to a versioning strategy:
- Write the choice (URL, header, content-negotiation, mixed) and a three-sentence justification.
- Define what a version bump means for this API (major-only, date-based, semver).
- Write the URL pattern and header contract (if applicable).
- Write the "supported versions" paragraph you would put in the docs.
- Describe what happens to a request that omits the version - default to latest? reject? default to oldest still-supported?