How to organize REST API versioning?
When you change an API (remove fields, change structure), old clients may stop working. Versioning allows supporting multiple versions simultaneously.
Junior Level
API versioning is a way to manage changes without breaking existing clients.
Why is versioning needed?
When you change an API (remove fields, change structure), old clients may stop working. Versioning allows supporting multiple versions simultaneously.
Main strategies:
1. Version in URL — the most common approach in the industry (GitHub, Stripe, AWS). Easiest to route and debug:
/api/v1/users
/api/v2/users
2. Version in header:
GET /users
Accept-Version: v2
3. Version in content type:
GET /users
Accept: application/vnd.myapi.v2+json
What counts as a “breaking” change (Breaking Change)?
- Removing or renaming fields
- Changing structure (object became an array)
- New required fields
- Changing data types (Integer → String)
Middle Level
Versioning strategies
1. URI Versioning (/v1/users)
- Pro: Easy to configure caching on CDN. Simple logging and routing
- Con: URL changes with each new version
2. Header Versioning (Accept-Version: v2)
- Pro: One endpoint serves different versions
- Con: Requires a
Vary: Accept-Versionheader, otherwise the cache will serve V1 to a V2 user
3. Media Type Versioning (Accept: application/vnd.myapi.v2+json)
- Pro: Closest to the spirit of REST per Roy Fielding (creator of REST), as it uses Content Negotiation. However, in practice it’s rarely used due to debugging complexity.
- Con: Harder to implement and debug
Contract Testing (Pact)
In microservices, versioning is often replaced by contract testing:
- The Consumer describes which fields it needs
- The Provider checks at each build whether it broke client expectations
- Allows “smooth” updates without changing the major version
API Gateway and Blue-Green Deployment
An API Gateway (Kong, Spring Cloud Gateway) can act as a Version Router:
- 10% of traffic with header
v2→ new version (Canary Release — the new version gets a small percentage of traffic, e.g., 10%. If there are no errors — traffic is gradually shifted completely). - 90% → old version
Performance
- Sunset Policy (RFC 8594): Always set a “death date” for the old version. Use the
Sunsetheader - Backward Compatibility Layer: Create an “adapter” at the API Gateway level that transforms old-format requests into the new format
Edge Cases
- Database Schema: The hardest part — when the new version requires a DB schema change
- Solution: “Expand and Contract” strategy — DB migration without downtime: 1) Expand — add a new column without removing the old one. 2) Migrate data. 3) Contract — remove the old column.
- Side Effects: If v1 writes to table A, and v2 — to table B, make sure analytics accounts for both sources
Senior Level
Contract management
Versioning is about managing the contract between provider and consumer. Any change that requires changes in the client’s code is “breaking”.
What breaks the contract?
- Removing or renaming fields in JSON
- Changing structure (object became an array)
- Changing validation logic (new required field)
- Changing data types (Integer → String)
Infrastructure and Testing
Contract Testing (Pact)
Allows “smooth” updates without changing the major version:
- Consumer describes expectations
- Provider checks compatibility at each build
API Gateway and Canary Release
API Gateway acts as a Version Router:
- Canary Release: 10% traffic to v2, 90% to v1
- Monitor errors on the new version before full switch
Performance and Highload
Sunset Policy (RFC 8594)
Use the Sunset header to warn clients about version deprecation:
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Backward Compatibility Layer
If supporting the old version is expensive — create a proxy adapter on the API Gateway. This allows removing old code from the main microservice.
Diagnostics
- Metric:
api_version_usage_total: Count requests per version. This is the only way to reasonably justify deprecating an old version - Deprecation Header: Use
Deprecation: truefor automated monitoring of deprecated endpoint usage - Database Strategy: Expand and Contract — first add new columns, then migrate, then remove old ones
Interview Cheat Sheet
Must know:
- 3 strategies: URL (
/v1/users), header (Accept-Version: v2), Media Type (application/vnd.myapi.v2+json) - URL Versioning — most common approach (GitHub, Stripe, AWS), easiest to route
- Breaking Changes: removing/renaming fields, changing structure, new required fields, changing data types
- Contract Testing (Pact) allows updating APIs without changing the major version
- Sunset Policy (RFC 8594):
Sunsetheader warns about version deprecation - Expand and Contract — DB migration without downtime: add column → migrate → remove old
- API Gateway can act as Version Router with Canary Release (10% to v2)
Common follow-up questions:
- What breaks an API contract? — Removing fields, changing structure, new required fields, changing data types
- Why is Media Type Versioning rarely used? — Harder to implement and debug, although closer to REST spirit
- What is Contract Testing? — Consumer describes expectations, Provider checks compatibility at each build
- How to migrate DB during versioning? — Expand and Contract: add new, migrate data, remove old
Red flags (DO NOT say):
- “URL versioning is an anti-pattern” — it’s the most common approach in the industry
- “Non-breaking changes don’t require versioning” — even adding a field can break clients
- “Contract Testing fully replaces versioning” — allows smooth updates but doesn’t eliminate it
- “Media Type Versioning is the best choice” — theoretically correct, but rarely used in practice
Related topics:
- [[What is REST]]
- [[What is RESTful API design]]
- [[What is Content-Type header]]
- [[What is Accept header]]
- [[How to properly name REST endpoints]]