Chapter 02 of 12
API Versioning Strategies Compared
URL path, header, query parameter, media type and date-based versioning, with the routing, caching, tooling and visibility trade-offs of each.
The five strategies below all solve the same problem, which is telling the server which contract the caller expects. They differ in how the version travels, and that one difference decides how the request routes, how it caches, how visible the version is, and how much work a migration turns out to be.
URL path versioning
The version sits in the address, as in /v2/orders/42. It is the most common choice on public APIs and the easiest to explain.
Routing is trivial, because a proxy or a router matches a prefix and needs to know nothing about the request. Caching works without configuration, since the version is already part of the URL and therefore part of the cache key. Every log line, browser address bar, bug report and support ticket carries the version, which shortens a surprising number of conversations.
The cost is conceptual and it is real. One resource now has two addresses, which conflicts with the idea that a URL identifies a thing and not a representation of it. Clients that build links by string concatenation have to be updated in every place they do it, and a migration therefore touches more code than a header change would.
Header versioning
The version travels in a request header, such as Api-Version: 2 or a vendor-specific name. The resource keeps one address for its whole life.
This is the version of the argument that HTTP purists win. It also has a practical trap, because a shared cache that does not know about your header will happily serve one version's response to a caller who asked for the other. The fix is a Vary header naming your version header on every response, and it has to be there from the first day.
Header versioning is quieter than the path in every log you own, so plan for that. A caller cannot try version 2 by editing a URL, which makes debugging over the phone harder, and it makes accidental version drift in a client less likely.
Query parameter versioning
The version is a query parameter, as in /orders/42?api-version=2026-01-01. It caches like the path, because the query string is part of the cache key, and it is the easiest of all to try by hand.
The drawback is tidiness. Version parameters mix with filtering and pagination parameters in the same namespace, and it becomes easy for a caller to omit yours by accident. Any API using this strategy therefore needs an explicit answer for a request that carries no version at all.
Media type versioning
The caller negotiates the version through content negotiation, sending something like Accept: application/vnd.example.v2+json, and the server answers with a matching Content-Type.
This is the most faithful to HTTP, since it treats a version as one representation of an unchanged resource. It is also the least well served by tooling. Browsers cannot ask for it without help, command line examples get longer, and a caller who sends a plain Accept: application/json needs a documented default. Teams that adopt it usually do so because they genuinely version representations separately, and not because they wanted a tidier URL.
Date-based versioning
Date-based versioning changes what the version is called instead of where it travels. Rather than counting majors, each release is named by its date, and consumers pin to the date they integrated against.
Stripe uses date-based versions with release names such as 2026-08-26.dahlia, carried in the Stripe-Version header, with an account-level default and a per-request override. Its documentation describes monthly releases as backward compatible and major releases as the ones that are not, and webhook endpoints carry their own version so event payloads stay stable while the caller upgrades. GitHub uses plain dates in the X-GitHub-Api-Version header, defaults requests without that header to 2022-11-28, and answers a request naming a retired version with 410 Gone. Both pages were retrieved on 13 September 2026.
The appeal is that a date is honest about what a version is, which is the state of the contract on a given day. The cost is that consumers can end up pinned to dozens of distinct dates, so this strategy needs strong tooling and a deliberate retirement policy behind it.
The comparison
| URL path | Header | Query | Media type | Date-based | |
|---|---|---|---|---|---|
| Routing | Prefix match, trivial | Needs header inspection | Query parsing | Content negotiation | Follows its carrier |
| Caching | Free, part of the key | Needs Vary | Free, part of the key | Needs Vary: Accept | Follows its carrier |
| Visible in logs | Always | Only if logged | Always | Rarely | Follows its carrier |
| Try by hand | Edit the URL | Needs a client | Edit the URL | Needs a client | Depends |
| Tooling support | Universal | Good | Universal | Patchy | Good |
| Migration cost | Every URL a client builds | One client setting | Every URL a client builds | One client setting | One client setting |
| Fits | Public APIs, wide audiences | Internal and platform APIs | Platforms with URL sharing | Representation-heavy designs | Products with frequent contract changes |
Azure API Management offers the first three of these as configurable schemes, calling them path, header and query string, with a version identifier that is any string you choose. Its documentation is at learn.microsoft.com/azure/api-management/api-management-versions, retrieved on 13 September 2026. That a managed gateway exposes exactly those three is a useful signal about which of them the wider ecosystem actually supports.
What every strategy needs answered
Whichever carrier you pick, three questions decide whether it works in practice, and none of them is about the carrier.
What happens when no version is sent. Serving the latest is friendly and it silently upgrades every unversioned caller into the next breaking change. Serving a fixed default is predictable and it strands newcomers on old behaviour. Rejecting the request is strict and it is the only option that never surprises anybody. Public APIs usually pick a fixed default, and internal APIs can afford to require the version.
What happens when an unknown version is sent. A typo should produce a clear error and never a silent fallback. Returning the nearest version is the one behaviour that guarantees a confusing bug report later.
Where the version applies. A version per API is simple and forces the whole surface forward at once. A version per endpoint is flexible and multiplies the combinations you have to test. The middle path, which most teams end up at, is one version for the API with additive change inside it.
In Routebase
A specification carries its versioning strategy as configuration instead of convention. You choose URL path, header, query parameter or content negotiation, name the carrier, and set what happens when a consumer sends nothing or sends something unknown.

The setting is not decorative. A Routebase mock server routes requests by the strategy you configured, so your frontend team develops against the same version selection your production API will use. Strict mode rejects an unknown version identifier instead of guessing, and the documentation portal shows consumers which strategy the API uses next to its version switcher. The Spec Versioning guide walks through each field, and Mock Server covers how version routing behaves against a mock.
Frequently asked questions
What are the main REST API versioning strategies?
There are five in common use. URL path versioning puts the version in the address, and header versioning carries it in a request header. Query parameter versioning appends it to the URL, while media type versioning negotiates it through the Accept header. Date-based versioning replaces a counter with a release date. The first four decide where the version travels, and the fifth decides what the version is called.
Is URL path or header versioning better?
URL path versioning is visible in every log line, every browser address bar and every support ticket, and it caches without any extra configuration. Header versioning keeps one address for one resource, which is closer to how HTTP was designed. It needs a Vary header, because otherwise a cache can serve one version's response to a caller who asked for another. Most public APIs choose the path for its visibility.
How does Stripe version its API?
Stripe uses date-based versions with release names, such as 2026-08-26.dahlia, sent in the Stripe-Version header. An account has a default version set in the dashboard, requests may override it per call, and webhook endpoints carry their own version so event payloads stay stable. The documentation for this is at docs.stripe.com/api/versioning, retrieved on 13 September 2026.
Does API versioning affect caching?
Yes, and it is the difference that most often decides the choice. A version in the path or the query string is part of the cache key already, so two versions never collide. A version in a header is invisible to a cache unless the response carries a Vary header naming it. Without that header, a shared cache can serve a version 1 response to a caller who asked for version 2.
Last reviewed by The Routebase Team.