API Versioning Strategies [2026] Breaking Changes Guide
Bottom Line
Use additive changes in place and reserve new versions for true contract breaks. The best strategy is the one clients can pin explicitly, test automatically, and migrate off before deprecation becomes removal.
Key Takeaways
- ›GitHub’s current REST API version line includes 2026-03-10; retired versions return 410 Gone.
- ›Google AIP-185 says stable APIs expose major versions like v1, not v1.1 or v1.4.2.
- ›Azure requires explicit api-version query params and keeps stable versions for at least 3 years after a breaking release.
- ›Stripe’s post-2024-09-30.acacia process separates monthly non-breaking releases from named breaking releases.
API versioning is no longer a stylistic choice. As of May 21, 2026, major platforms still split across paths, headers, and dated query parameters, but they converge on one rule: breaking changes need explicit contracts, long migration windows, and runtime signals clients can inspect. This reference guide compresses the current playbook into a searchable cheat sheet for planning a v2, a dated release line, or a beta-to-GA cutover.
| Strategy | How clients select it | Best fit | Main risk | Edge |
|---|---|---|---|---|
| URI path | /v1/orders | Public APIs and docs clarity | Resource identity gets tied to version | Best human readability |
| Header | X-API-Version or vendor media type | Stable resource URLs | Harder to debug in browsers and caches | Best contract separation |
| Query param | ?api-version=2026-01-01 | Cloud control-plane APIs | URL sprawl in links and logs | Best for explicit date-based policy |
| Date-based release lines | 2026-03-10 | Large platforms with long support windows | Teams can over-version tiny changes | Best for operational governance |
Versioning Models That Hold Up in Production
Bottom Line
Pick one explicit version selector, define what counts as a contract break, and publish machine-readable deprecation signals before removal. Most API pain in 2026 still comes from weak migration policy, not from the version token itself.
What actually counts as a breaking change
- Removing or renaming an operation, parameter, or response field.
- Adding a new required field or making an optional field required.
- Changing a type, enum set, validation rule, auth requirement, or response shape.
- Changing semantics in a way that makes old client assumptions false, even if the JSON still parses.
What the major platforms are doing right now
- GitHub REST API uses date-based header versioning with
X-GitHub-Api-Version. Its docs list 2026-03-10 as a released version, keep previous versions for at least 24 months, and return 410 Gone after retirement. - Google AIP-185 requires major versions for APIs, with REST paths like v1, v1beta, and v1alpha. It explicitly says not to expose minor or patch numbers like v1.1.
- Azure REST APIs require an explicit
api-versionquery parameter. Microsoft says stable versions remain available for at least 3 years after a breaking-change version releases. - Microsoft Graph keeps a production v1.0 and a mutable beta line. Beta can break; production is the safe target.
- Stripe moved in 2024-09-30.acacia to monthly non-breaking API releases and named breaking releases. Its API reference currently lists 2026-04-22.dahlia as the current API version.
Live Search JS Filter
For a reference-heavy versioning guide, searchable content beats long scroll. The pattern below filters any versioning card, command block, or table row tagged with data-filter-item.
<input id='ref-filter' type='search' placeholder='Filter: header, sunset, v1beta, webhook' />
<div data-filter-item data-filter-text='header versioning github x-github-api-version'>...</div>
<div data-filter-item data-filter-text='query versioning azure api-version'>...</div>
<div data-filter-item data-filter-text='deprecation sunset link headers'>...</div>const input = document.getElementById('ref-filter');
const items = [...document.querySelectorAll('[data-filter-item]')];
function runFilter(query) {
const q = query.trim().toLowerCase();
for (const item of items) {
const text = (item.dataset.filterText || item.textContent || '').toLowerCase();
item.hidden = q !== '' && !text.includes(q);
}
}
input?.addEventListener('input', (event) => {
runFilter(event.target.value);
});
document.addEventListener('keydown', (event) => {
if (event.key === '/' && document.activeElement !== input) {
event.preventDefault();
input?.focus();
}
if (event.key.toLowerCase() === 'escape' && document.activeElement === input) {
input.value = '';
runFilter('');
input.blur();
}
});Keyboard shortcuts
| Shortcut | Action | Why it matters |
|---|---|---|
/ | Focus filter | Jump straight to the term you need. |
Esc | Clear filter | Restore the full cheat sheet fast. |
g v | Jump to versioning models | Useful during design reviews. |
g c | Jump to commands | Useful while testing an upgrade. |
g f | Jump to configuration | Useful when editing contracts. |
? | Open shortcut help | Keeps the page self-discoverable. |
Commands Grouped by Purpose
Probe path-based routing
curl -i https://api.example.com/v1/orders/123
curl -i https://api.example.com/v2/orders/123Probe header-based routing
curl -i \
-H "X-API-Version: 2026-03-10" \
https://api.example.com/orders/123Probe GitHub-style dated headers
curl -i \
-H "X-GitHub-Api-Version: 2026-03-10" \
https://api.github.com/zenProbe query-parameter routing
curl -i "https://management.azure.com/subscriptions?api-version=2020-01-01"Inspect deprecation signals
curl -si https://api.example.com/v1/orders/123 | grep -iE 'deprecation|sunset|link'Capture contract deltas before release
git diff --word-diff openapi.yaml
git diff --word-diff docs/migrations/orders-v2.md- Group smoke tests by selection method: path, header, query, and media type negotiation.
- Keep one request that intentionally omits a version to verify the default path is still what you think it is.
- Save response headers during CI, not just response bodies.
- Format example JSON before review with the Code Formatter so breaking field moves are obvious in diffs.
Configuration Patterns
OpenAPI contract with deprecation metadata
openapi: 3.1.0
info:
title: Orders API
version: 2026-03-10
paths:
/v1/orders/{orderId}:
get:
deprecated: true
summary: Get an order from the legacy contract
parameters:
- in: path
name: orderId
required: true
schema:
type: string
responses:
'200':
description: Legacy order payload
headers:
Deprecation:
description: Epoch date when the resource became deprecated.
schema:
type: string
Sunset:
description: Date when the resource will be removed.
schema:
type: string
Link:
description: Migration or deprecation policy link.
schema:
type: stringMinimal header resolver in Node
const supported = new Set(['2025-01-01', '2026-03-10']);
export function resolveApiVersion(req, res, next) {
const version = req.get('X-API-Version') || '2025-01-01';
if (!supported.has(version)) {
return res.status(400).json({
error: 'unsupported_api_version',
requested: version,
supported: [...supported]
});
}
req.apiVersion = version;
next();
}Configuration rules worth writing down
- Define a single source of truth for supported versions: code, gateway config, and docs should all read from it.
- Document the default version behavior explicitly. Silent defaults are where surprise breakages hide.
- Emit identical auth, rate-limit, and tracing behavior across versions unless the version contract says otherwise.
- Keep migration guides next to the contract, not buried in release notes.
Advanced Usage and Rollout
When to choose which strategy
Choose URI path versioning when:
- You need maximum clarity in docs, examples, and browser debugging.
- You expect many third-party consumers and low tooling sophistication.
- You can tolerate duplicated route trees during migration.
Choose header or query versioning when:
- You want stable resource URLs across generations.
- You need date-based governance and explicit client pinning.
- You operate SDKs, gateways, and observability that can surface version metadata cleanly.
Webhooks, SDKs, and typed clients
- Version request handling separately from event payload handling when needed. Stripe is the useful model here: webhook endpoints can pin their own API version.
- Assume strongly typed SDKs will surface breaks earlier than raw HTTP clients. That is a feature, not a bug.
- Do not upgrade request versions and webhook versions in the same deploy unless you have replay coverage.
beta endpoint is not just a different label for vNext. Microsoft Graph’s /beta line can change without production guarantees, so never treat preview traffic as stable contract evidence.Machine-readable deprecation
RFC 9745 standardized the Deprecation HTTP response header in March 2025. Pair it with RFC 8594 Sunset and a migration link.
HTTP/1.1 200 OK
Deprecation: @1772496000
Sunset: Tue, 31 Mar 2026 00:00:00 UTC
Link: </docs/deprecations/orders-v1>; rel="deprecation"- Deprecation tells clients a resource is deprecated or when it will become deprecated.
- Sunset tells clients when the resource is expected to stop responding.
- Link rel="deprecation" points humans and tooling to the migration policy.
Breaking Change Playbook
- Classify the change: additive, behavior-only, or contract-breaking.
- Pick one selector for the new contract: path, header, or query parameter.
- Publish the new version before announcing retirement of the old one.
- Diff request bodies, response bodies, auth rules, enums, and error shapes.
- Add runtime headers and migration docs before the sunset date is near.
- Run both versions in CI and replay production traces where legally allowed.
- Retire only after error budgets, adoption metrics, and support windows are met.
- If you cannot explain the migration in one page, the change is probably too large for one version jump.
- If you need to keep the same URL and same SDK method but radically change semantics, prefer a new version over clever compatibility shims.
- If only one field is risky, deprecate the field first; do not force an API-wide version unless the contract truly changed.
Frequently Asked Questions
What counts as a breaking API change? +
Should I version my API in the URL or a header? +
How long should I support old API versions? +
Do webhooks need their own versioning policy? +
Get Engineering Deep-Dives in Your Inbox
Weekly breakdowns of architecture, security, and developer tooling — no fluff.