Every API that lasts long enough eventually needs to make a breaking change: a field gets removed, a response shape changes, a business rule shifts in a way old clients cannot handle gracefully. Versioning is how you make that change without breaking every existing integration on the day you ship it. The strategy you pick affects how easy it is for clients to discover, adopt, and eventually migrate off old versions.
What changed in 2026
- Header-based versioning gained ground at larger API providers, favored for keeping URLs stable and treating the version as metadata about the request rather than part of the resource identity.
- Sunset and deprecation headers saw wider standardized adoption, following the IETF's
Sunset and Deprecation header specifications, giving clients a machine-readable way to detect an approaching cutoff instead of relying purely on changelog emails.
- GraphQL's "no versioning" convention faced more scrutiny, as large federated graphs found that field-level deprecation alone was not always enough to manage genuinely breaking schema changes.
- AI coding assistants made it easier for client teams to migrate to a new API version quickly, slightly lowering the cost of maintaining a firmer deprecation timeline than teams used to feel comfortable enforcing.
The three common versioning strategies
| Strategy |
Example |
Pros |
Cons |
| URI path versioning |
/v2/orders |
Highly visible, cacheable, easy to route |
Version becomes part of the resource identity, arguably impure REST |
| Header versioning |
Accept: application/vnd.api+json;version=2 |
Keeps URLs stable |
Less discoverable, harder to test casually in a browser |
| Query parameter versioning |
/orders?version=2 |
Simple to add, optional |
Easy to omit accidentally, less conventionally clean |
There is no universally correct choice; URI versioning is the most common in practice because of its simplicity and visibility, even though header versioning is arguably more aligned with REST's idea that a URI identifies a resource, not a resource-plus-version.
Deciding whether a change actually needs a new version
Not every change is breaking. Adding a new optional field to a response, adding a new endpoint, or adding a new optional request parameter are all additive changes that well-behaved clients tolerate without modification. Reserve version bumps for genuinely breaking changes:
- Removing or renaming a field clients depend on.
- Changing a field's type or meaning.
- Changing required parameters or authentication requirements.
- Changing status codes or error formats clients parse programmatically.
Versioning every minor addition fragments your client base across unnecessary versions and multiplies the surface area you have to maintain and test.
Deprecation: the part people underinvest in
The versioning scheme is the easy part. The hard part is retiring old versions without breaking integrations that never got the memo. A workable deprecation policy needs:
- A stated support window, communicated at the time a new version ships, not improvised when you decide to retire the old one.
- Machine-readable deprecation signals, like the
Deprecation and Sunset headers, so client tooling can flag the issue automatically rather than relying on a human reading a changelog.
- Usage monitoring per version, so you know which clients are still on the old version before you cut it off, and can reach out directly if it is a small, identifiable set.
- A real cutoff date, held to. An indefinitely supported "deprecated" version is not actually deprecated; it is just permanent additional maintenance burden with an outdated label.
Versioning GraphQL vs REST
GraphQL's convention leans toward not versioning the API at all, instead evolving the schema by adding new fields and marking old ones @deprecated, since clients only request the fields they use. This works well for additive change but strains under genuinely breaking changes — removing a type or changing a resolver's fundamental behavior still needs a coordinated migration, whether or not you call it a "version." Some large federated graphs end up versioning specific subgraphs anyway, in effect if not in name.
FAQ
Should I version my API from day one?
Build the versioning mechanism in from the start, even if you launch as v1. Retrofitting a versioning scheme onto an API that already has unversioned clients in production is far more painful than starting with one.
How many versions should I support at once?
As few as operationally possible, ideally the current version plus one prior. Supporting more than two active versions long-term usually signals a deprecation process that is not being enforced.
Is semantic versioning appropriate for APIs?
As a convention for signaling scope, sure — but a version number alone does not enforce compatibility guarantees the way it might for a library. Clients still need to read what changed, not just trust the number.
Does REST require URI versioning?
No. REST as originally described does not prescribe a versioning mechanism at all; URI versioning is a popular convention, not a requirement of the architectural style.
Where to go next