A good API versioning strategy answers three questions before anyone touches a URL: what counts as a breaking change, how long old versions stay alive, and how you tell consumers. For most REST APIs, the safest default is to keep one stable major version (for example /v1), evolve it with additive changes, and publish a new major version only when a break is unavoidable. This guide walks through how to classify changes, pick a versioning scheme, write a deprecation policy, and run the transition in production.
TL;DR
- Treat versioning as contract lifecycle management, not a URL convention. Classify every change as non-breaking, behavioral, or breaking first.
- Prefer additive evolution within a major version. Create
/v2only when you cannot preserve the old meaning of a field or endpoint. - Pick one versioning mechanism (URI path, query parameter, header, or media type) and use it across your whole API portfolio.
- Write a deprecation policy with dates, supported-version limits, and measurable sunset criteria, and back it with
DeprecationandSunsetheaders. - Every extra live version costs testing, documentation, monitoring, and support. Cap how many you run at once.
Table of Contents
- Why versioning matters more once clients ship on their own schedule
- Start with compatibility rules, not a version number
- Choosing a versioning mechanism
- Preview, beta, and stable channels
- Avoiding a new version: expand, migrate, contract
- Writing an API deprecation policy
- A production rollout, step by step
- Common mistakes
- Our perspective
- Where we can help
- FAQ
- Sources
Why versioning matters more once clients ship on their own schedule
A web client updates the moment you deploy. A mobile app does not. Users stay on old builds for months, app store review adds delay, and some people never update. Partner integrations and internal services add more consumers that you do not control and sometimes do not know about.
That is why a backend change that looks harmless can break production for a subset of users you cannot reach. The same problem hits fintech, healthcare, and insurance platforms hardest: regulated workflows, long partner release cycles, and consumers that were integrated years ago by someone who has since left.
Versioning is the tool for letting the provider and its consumers move at different speeds. It is not free, though. Microsoft's API design guidance frames the trade-off plainly: multiple versions keep clients stable but raise deployment, testing, documentation, and support effort. A strategy has to manage both sides of that.

Start with compatibility rules, not a version number
Before choosing /v2 or a header, agree internally on what a breaking change is. Google's AIP-185 is a useful reference for how a large API owner thinks about this. A practical three-way classification works for most teams:
| Change type | Examples | Typical handling |
|---|---|---|
| Non-breaking | New optional request field, new endpoint, new response field | Ship in the current major version |
| Behavioral (semantic) | Same field, different meaning; changed default; new validation rule; different sort order | Treat as breaking unless you can prove no consumer relies on the old behavior |
| Breaking | Removed or renamed field, changed type, removed endpoint, changed auth model | New major version, or a parallel change (see below) |
The middle row is where most incidents come from. Nothing in the schema changed, yet a client that assumed the old behavior now fails. Put behavioral changes in your written rules so reviewers catch them.
Two patterns reduce how often you need a new version at all:
- Tolerant reader. Clients read only the fields they need and ignore anything unknown. Martin Fowler's Tolerant Reader note explains why this lets a service evolve without every consumer changing in lockstep. Document this expectation for your consumers, because additive evolution only works if clients ignore new fields.
- Consumer-driven contracts. When many clients depend on one provider, consumer-driven contracts record what each consumer actually uses and turn that into automated checks. You find out a change is breaking in CI, not from a support ticket.
Choosing a versioning mechanism
The Microsoft guidance lists the common selectors: URI path, query parameter, custom header, and media type (content negotiation). It also advises choosing one convention across the portfolio unless a domain requires otherwise. Here is how they compare in practice.
| Mechanism | Example | Strengths | Trade-offs |
|---|---|---|---|
| URI path | /v1/accounts | Visible, easy to route, easy to test in a browser, simple to cache | Version is part of the resource address; clients must change URLs to migrate |
| Query parameter | /accounts?api-version=2 | Simple to add, keeps one path | Easy to omit; can complicate caching and routing rules |
| Custom header | Api-Version: 2 | Clean URLs | Hidden from casual inspection; harder to test by hand; gateways must be configured to read it |
| Media type | Accept: application/vnd.example.v2+json | Fine-grained, can version a single representation | Most complex for client developers and tooling |
| Date-based pinning | Account pinned to a release date | Consumers upgrade on their own timeline | Requires a change-translation layer on the server; heavier to build |
For the last row, this write-up on Stripe's approach describes how URLs still say v1 while each account is pinned to the version active when it signed up. It works well for a public platform with thousands of integrators, and it is more machinery than most internal or partner APIs need.
Our default recommendation: URI path with major versions only, for APIs serving mobile apps and partners. It is boring, visible in logs and dashboards, and easy for every engineer to understand. Use headers or media types when you have a specific reason, such as versioning one representation independently.
Keep semantic versioning internal-facing in detail
Semantic versioning (major.minor.patch) is a good way to communicate compatibility intent, but API Patterns points out that stakeholders still need to decide how many versions run in parallel and whether support is time-limited, indefinite, or preview-only. A common operating model is to expose only the major version in the URL, handle minor and patch changes compatibly, and record them in the contract, changelog, and documentation. Exposing /v1.4.2 in a path mostly gives consumers more to track.
Preview, beta, and stable channels
Version numbers and stability are two separate questions. AIP-185 describes alpha, beta, and stable channels, with examples such as v1alpha5 and v1beta1. It also recommends a 180-day transition period for beta releases after functionality reaches stable.
This matters for AI-enabled products. A new model-backed capability, such as a summarization or recommendation endpoint whose output will keep shifting, can be released as a clearly labeled preview without implying that your stable resources have a new breaking version. Label preview contracts as subject to change, and do not let a mobile app or partner treat them like a long-term commitment.
Avoiding a new version: expand, migrate, contract
Many breaking changes can be delivered without a public /v2 through the parallel change pattern, also called expand and contract:
- Expand. Add the new field or endpoint alongside the old one.
- Migrate. Deploy code that reads both forms, then move clients and stored data to the new form.
- Contract. Observe traffic. Once the old form is unused, remove it.
This often avoids forcing every mobile release or partner integration to upgrade at the same moment. Reserve a new major version for changes where the old and new contracts genuinely cannot coexist, such as a redesigned resource model or a new authentication scheme.
Writing an API deprecation policy
Deprecation should be an explicit policy, not a message in a Slack channel. At minimum, your policy states:
- What counts as a breaking change (reuse your table above).
- How much notice consumers get before a version is removed.
- Which versions are supported at any time, and the maximum number running in parallel.
- The sunset criteria: for instance, usage below an agreed threshold, plus confirmation from named partners.
- Who owns the migration and how consumers reach them.
The notice period is a business decision. Mobile clients and regulated partners need longer windows than an internal service, so set different windows by consumer type instead of one number for everyone.
Make deprecation machine-readable
The IETF has standardized the Deprecation response header in RFC 9745. Its example uses a timestamp form such as Deprecation: @1688169599 alongside Sunset: Sun, 30 Jun 2024 23:59:59 UTC, and the RFC requires that the Sunset timestamp not precede the deprecation date. The Sunset header itself is defined separately in RFC 8594, as this implementation guide explains.
Headers let client libraries and monitoring tools flag deprecated calls automatically. They do not replace human documentation. Consumers still need the replacement, migration steps, an owner to contact, and a date-based support promise. Link to that migration guide from the response.
A production rollout, step by step
Here is the sequence we use as a checklist when a breaking change is unavoidable. It follows the same shape as the process in Google's and Microsoft's guidance.
- Define compatibility rules and classify the proposed change.
- Inventory consumers: mobile app versions in the field, partners, internal services, scheduled jobs. Pull this from gateway logs, not memory.
- Update the OpenAPI contract and regenerate SDKs and mocks.
- Implement and test the new version, including contract tests against real consumer expectations.
- Release it alongside the old version. Azure's documentation on API versions in API Management shows how a gateway can publish several versions together and distinguish them by path, query string, or header.
- Announce migration and deprecation dates in the changelog, by email to registered consumers, and in response headers.
- Monitor usage per version and per client. Track which consumers remain and which app builds still call the old contract.
- Retire the old version only after your measurable criteria are met, not simply because the date arrived.
If your documentation and changelog live on a public site, publishing them through an API is common. When you do, preserve canonical and JSON-LD data when publishing via API so old version pages do not compete with current ones in search.
Managing many OpenAPI variants
If you maintain several descriptions of the same API, for example one for partners and one for internal use, copying the base contract invites drift. The OpenAPI Overlay Specification v1.1.0, released January 14, 2026, defines a standard way to apply ordered changes to a target OpenAPI document, so audience-specific descriptions can derive from one base.
Common mistakes
- Versioning everything on day one. A new major version for every release creates a maintenance burden with no benefit. Add versions for breaks, not for features.
- Treating behavioral changes as safe. If the meaning changed, it is breaking.
- No consumer inventory. Removing a version you believe is unused is a gamble without per-client usage data.
- Deprecation without a replacement path. An announcement with no migration guide just creates support load.
- Unlimited parallel versions. Set a cap and a window, and enforce them.
- Different conventions per team. Consumers should not have to learn a new scheme for each endpoint family.
Our perspective
We build mobile apps and the backends behind them, and the pattern repeats: the API is rarely the problem at launch. It becomes one 12 to 18 months later, when the app has several live builds, a second client appears, and the original author has moved on.

So we begin versioning decisions at design time. We agree on compatibility rules before the first endpoint ships, keep the contract in OpenAPI, and put contract tests in the pipeline so a breaking change fails a build instead of a release. We prefer simple, visible major versions in the path and additive evolution inside them, because the team that inherits the system can reason about it quickly.
We also push clients to decide the support window early. A deprecation policy is easy to write when nothing is being removed and hard to negotiate in the middle of an incident. For products where a founder prototyped quickly with an AI builder and now needs a production-grade backend, defining the contract and versioning rules is often the first step in making that code safe to scale.
— Brian
Where we can help
If you are designing a new API or untangling an existing one, our team handles custom API and backend development for web and mobile apps, and our DevOps consulting covers the CI/CD pipelines where contract tests and staged releases run. For ongoing support after launch, see our app and website maintenance services, or browse the full list of services to talk through your project.
FAQ
What is the best API versioning strategy?
For most REST APIs, expose major versions only, such as /v1, and evolve that version with additive, backward-compatible changes. Create a new major version only for changes that cannot coexist with the old contract. Choose one mechanism and apply it everywhere.
Should I put the version in the URL or a header?
URL path versioning is the most visible and easiest to route, test, and debug, which suits most mobile and partner APIs. Headers keep URLs clean but are harder to inspect and require gateway support. Whichever you choose, use it consistently across your APIs.
How do I keep an API backward compatible?
Only add optional fields and new endpoints, never change the meaning of existing ones, and ask consumers to ignore fields they do not recognize. Back it up with consumer-driven contract tests so a breaking change is caught before release. When you must break something, use the expand, migrate, contract sequence.
How long should an API deprecation period last?
There is no universal number. Set it by consumer type: internal services can move quickly, while mobile apps and regulated partners need longer windows. Google's guidance recommends 180 days for beta-to-stable transitions, which gives you one reference point for preview channels. State the window, the sunset date, and the removal criteria in writing.
Sources
- Web API Design Best Practices, Microsoft Learn
- AIP-185: API Versioning, Google
- Tolerant Reader, Martin Fowler
- Consumer-Driven Contracts, Martin Fowler
- Parallel Change, Martin Fowler
- Semantic Versioning pattern, API Patterns
- RFC 9745: The Deprecation HTTP Response Header Field, IETF
- Versions in Azure API Management, Microsoft Learn
- Overlay Specification v1.1.0, OpenAPI Initiative
- How to Create API Deprecation Headers, OneUptime
- Why Stripe's API Never Breaks: Date-Based Versioning, Medium
