TL;DR
API versioning helps you release changes without breaking existing clients. Use clear REST API versioning strategies (most teams start with /v1
in the path), keep backward compatibility by making additive changes, and announce a deprecation and sunset policy before removing old versions.
What is API versioning (and why it matters)
API versioning means giving your API a label (like v1
, v2
, or a date) so you can change it safely over time.
It matters because:
- You can ship new features without breaking old apps.
- Teams can upgrade at their own pace.
- You can clean up old mistakes in a controlled way.
What counts as a breaking change?
A breaking change forces a client to update code. Common examples:
- Remove or rename a field/endpoint.
- Change data type or meaning of a field.
- Tighten validation (fewer enum values allowed).
- Change default sorting or status codes.
Non-breaking (backward compatible) changes include adding new optional fields, new endpoints, or new query params. Clients should ignore unknown fields.
REST API versioning strategies (with pros and cons)
1) Version in the URL path (most common)
Pattern: /v1/orders
Why teams choose it: Very clear, easy to route and cache, simple docs.
Trade-offs: Coarse-grained (all of v1 vs v2), version appears in links.
When to use: Public APIs, many consumers, need maximum clarity.
2) Version in a query parameter
Pattern: /orders?api-version=2025-09-01
Pros: Flexible, can support date-based snapshots.
Cons: Less canonical for REST; some caches treat query strings differently.
When to use: You want date versions or per-request overrides.
3) Version in headers / media types (content negotiation)
Patterns:X-API-Version: 2
Accept: application/vnd.acme.orders+json;v=2
Pros: Clean URLs, fine-grained control.
Cons: Less visible; requires good tooling and docs.
When to use: Partner APIs; consumers are comfortable with headers.
4) gRPC and protobufs
Put version in the package: package billing.v1;
Breaking changes → new package billing.v2
running side-by-side.
5) GraphQL (usually no hard versions)
Prefer additive evolution. Mark old fields @deprecated
, communicate removal timelines. Works best with disciplined schema management.
Designing for backward compatibility in APIs
- Be additive by default
- Add new fields as optional.
- Keep old fields working until clients migrate.
- Never change semantics
- Don’t reuse a field for a new meaning.
- Don’t flip a boolean’s behaviour.
- Tolerant readers, strict writers
- Clients: ignore unknown fields.
- Servers: validate requests strictly and return clear errors.
- Careful with enums, pagination, and sorting
- Adding enum values can break strict clients—document this risk.
- Changing default sort can break clients—make new sort opt-in.
- Keep pagination tokens stable across versions.
Deprecation and sunset policy (operational playbook)
A clear deprecation and sunset policy builds trust.
- Announce: Add response headers like
Deprecation: true
andSunset: Wed, 05 Mar 2026 00:00:00 GMT
Include aLink
header to your changelog and migration guide. - Timeline: Give a support window (e.g., 12–18 months).
- Monitor: Track usage per version; reach out to top consumers.
- Remove: After traffic is near zero and the window has passed, retire the old version.
Practical patterns (with examples)
Path versioning (simple REST)
GET /v1/orders/{id}
Accept: application/json
// v1 response (additive evolution)
{
"id": "ord_123",
"status": "PLACED",
"total": 1099,
"currency": "INR",
"estimatedDelivery": "2025-09-10T12:00:00Z" // newly added, optional
}
Media-type versioning (clean URLs)
GET /orders/{id}
Accept: application/vnd.acme.orders+json;v=2
gRPC package versioning
syntax = "proto3";
package acme.orders.v1;
service Orders {
rpc Get(GetOrderRequest) returns (Order);
}
GraphQL deprecation
type Order {
totalCents: Int @deprecated(reason: "Use total.amount")
total: Money
}
Migration strategy when you must release v2
- Decide scope: Only break what you must; everything else stays compatible.
- Run v1 and v2 in parallel: Route
/v1/*
and/v2/*
at the gateway. - Provide shims: If possible, transform v1 requests/responses from/to the new internal model.
- Publish guides: Clear upgrade notes, examples, and diffs.
- Stage rollout: Private preview → public beta → general availability.
- Communicate deprecation: Use headers, emails, and dashboards.
- Retire with care: Only after the sunset window and usage is minimal.
Testing and quality gates
- Contract as source of truth: OpenAPI/Swagger for REST; Protobuf for gRPC.
- Automated breaking-change checks:
- REST: openapi-diff in CI to fail PRs that break v1.
- gRPC:
buf breaking
to catch incompatible changes.
- Consumer-driven contracts: Use Pact for critical partners.
- Canary and shadow traffic: Mirror real requests to vNext and compare.
Monitoring and analytics
Track these per API version:
- Request volume, error rates, latency.
- Top endpoints and consumers.
- Deprecated field usage.
This helps you plan migrations and choose the right time for sunset.
Quick decision guide
- Public REST API? Start with URL path versioning (
/v1
)—simple and clear. - Enterprise/partner API? Consider header/media-type versioning.
- gRPC? Use package versioning (
.v1
,.v2
). - GraphQL? Avoid hard versions; deprecate and remove with policy.
- Unsure? Use path versioning now; you can still add header-based versions later if needed.
Common pitfalls (and easy fixes)
- Pitfall: Changing field meaning silently.
Fix: Introduce a new field; mark the old one deprecated. - Pitfall: Force everyone to upgrade immediately.
Fix: Announce a deprecation timeline; provide a migration guide. - Pitfall: Big-bang
v2
with no telemetry.
Fix: Run v1 and v2 together; monitor version usage.
FAQs
Q1. Do I always need v2
?
No. Many APIs live long on v1 by making additive, backward compatible changes.
Q2. Is query parameter versioning wrong?
Not wrong. It’s useful for date versions and config-like APIs.
Q3. How long should I support old versions?
Common ranges are 12–18 months. Choose a window that matches your customer contracts.
Q4. Can I mix strategies?
Yes. Example: Path versioning for big breaks + media-type for fine-grained changes.
Simple checklist you can copy
- Choose strategy: path / header / query / package (gRPC)
- Define backward compatibility rules
- Publish deprecation and sunset policy
- Add CI checks for breaking changes
- Log usage by API version
- Provide migration guides and examples
- Announce, monitor, and retire safely
Final takeaway
Start simple with /v1
path versioning, evolve backward compatible whenever possible, and use a clear deprecation and sunset policy when you truly need v2
. This approach keeps your developers fast and your users happy.
Comments