API Versioning Strategies
Choose and implement an API versioning strategy that lets you evolve your API without breaking existing clients. Covers URI versioning, header versioning, content negotiation, sunset policies, and migration tooling.
APIs are contracts. Changing the response shape, renaming fields, or removing endpoints breaks every client that depends on the current behavior. API versioning is how you evolve the contract without violating existing agreements.
Versioning Approaches
URI Path Versioning
GET /api/v1/orders/456
GET /api/v2/orders/456
Pros: Obvious, easy to route, easy to test, cacheable. Cons: URL proliferation, harder to deprecate gradually.
This is the most common approach and the right default for most teams.
Header Versioning
GET /api/orders/456
Accept: application/vnd.example.v2+json
Pros: Clean URLs, same resource at different versions. Cons: Harder to test (need custom headers), harder to cache, invisible in logs.
Query Parameter Versioning
GET /api/orders/456?version=2
Pros: Simple. Cons: Mixes versioning with business parameters, cache-key pollution.
What Triggers a New Version
Not every change needs a version bump:
| Change | Breaking? | Version Bump? |
|---|---|---|
| Add a new field to response | No | No |
| Add a new optional parameter | No | No |
| Remove a field from response | Yes | Yes |
| Rename a field | Yes | Yes |
| Change field type (string → int) | Yes | Yes |
| Change error format | Yes | Yes |
| Add a new endpoint | No | No |
| Remove an endpoint | Yes | Yes |
Rule: Additive changes are non-breaking. Subtractive or structural changes are breaking.
Running Multiple Versions
Routing Layer
# API router that dispatches by version
@app.route('/api/v1/orders/<order_id>')
def get_order_v1(order_id):
order = OrderService.get(order_id)
return OrderSerializerV1(order).to_json()
@app.route('/api/v2/orders/<order_id>')
def get_order_v2(order_id):
order = OrderService.get(order_id)
return OrderSerializerV2(order).to_json()
The business logic (OrderService) is shared. Only the serialization layer differs between versions.
Adapter Pattern
class OrderSerializerV1:
def serialize(self, order):
return {
"id": order.id,
"total": order.total, # float
"customer_name": order.customer.name
}
class OrderSerializerV2:
def serialize(self, order):
return {
"id": order.id,
"total": { # structured object
"amount": order.total,
"currency": order.currency
},
"customer": { # nested object
"id": order.customer.id,
"name": order.customer.name
}
}
Deprecation and Sunset
Sunset Header
HTTP/1.1 200 OK
Sunset: Sat, 01 Jun 2027 00:00:00 GMT
Deprecation: true
Link: <https://api.example.com/v3/docs>; rel="successor-version"
Deprecation Policy
Phase 1: Announce deprecation (6 months before sunset)
- Add Sunset header to all v1 responses
- Email API consumers
- Update documentation
Phase 2: Warning period (3 months before sunset)
- Log all v1 usage with consumer identification
- Send targeted migration reminders to active consumers
Phase 3: Sunset
- Return 410 Gone for v1 endpoints
- Include migration guide URL in error response
Migration Tooling
Help consumers migrate with codemods, compatibility layers, or dual-write periods:
# Provide a compatibility shim
curl https://api.example.com/v1/orders/456
# Returns v1 format with deprecation warning header
curl https://api.example.com/v2/orders/456
# Returns v2 format
Anti-Patterns
| Anti-Pattern | Consequence | Fix |
|---|---|---|
| No versioning at all | Every change risks breaking clients | Version from day one |
| Too many active versions | Maintenance burden, bug surface | Max 2-3 active versions |
| Breaking changes without version bump | Silent client failures | Automated contract testing |
| Infinite version support | Legacy code never dies | Enforce sunset policy |
| Versioning internal APIs | Unnecessary overhead | Only version external/public APIs |
API versioning is about respecting your consumers’ investment in your API while preserving your ability to evolve. Get the strategy right early — retrofitting versioning onto an established API is painful for everyone.