API Versioning Strategies: Keep Your APIs Stable Without Blocking Innovation
API Versioning Strategies#
Your API is a contract. Every consumer — mobile app, partner integration, internal service — depends on that contract staying stable. But APIs must evolve. Versioning is how you evolve without breaking things.
Why Version APIs?#
Without versioning:
Ship breaking change → Every consumer breaks simultaneously
No migration path → Angry developers, broken apps
Rollback API → Your own new features break
With versioning:
Ship v2 alongside v1 → Consumers migrate on their schedule
Deprecation window → 6-12 months to upgrade
Clean cutover → Retire v1 when traffic drops to zero
The cost of not versioning is invisible until it's catastrophic. One breaking change to an unversioned API can cascade across dozens of consumers.
Breaking vs Non-Breaking Changes#
Before choosing a strategy, understand what actually requires a new version.
Non-Breaking (Safe to Ship)#
- Adding new optional fields to responses
- Adding new endpoints
- Adding optional query parameters
- Expanding enum values (with care)
- Performance improvements
Breaking (Requires New Version)#
- Removing or renaming fields
- Changing field types (
stringtonumber) - Removing endpoints
- Changing authentication mechanisms
- Altering error response formats
- Making previously optional fields required
Rule of thumb: additions are safe, removals and modifications are breaking.
The Four Versioning Strategies#
1. URL Path Versioning#
GET /api/v1/users/42
GET /api/v2/users/42
Pros: Explicit, easy to understand, easy to route, cache-friendly. Cons: URL pollution, can lead to massive code duplication.
This is the most common strategy and the easiest to implement. API gateways and load balancers can route by path prefix trivially.
# Flask example
@app.route('/api/v1/users/<id>')
def get_user_v1(id):
return {"name": user.name}
@app.route('/api/v2/users/<id>')
def get_user_v2(id):
return {"name": user.name, "email": user.email, "created_at": user.created_at}
2. Query Parameter Versioning#
GET /api/users/42?version=1
GET /api/users/42?version=2
Pros: Clean URLs, easy to default to latest. Cons: Easy to forget, harder to cache, less visible.
Works well for internal APIs where you control all consumers.
3. Header Versioning#
GET /api/users/42
X-API-Version: 1
GET /api/users/42
X-API-Version: 2
Pros: Clean URLs, separates versioning from resource identity. Cons: Hidden from casual inspection, harder to test in browsers, not cache-friendly by default.
4. Content Negotiation (Accept Header)#
GET /api/users/42
Accept: application/vnd.myapi.v1+json
GET /api/users/42
Accept: application/vnd.myapi.v2+json
Pros: RESTful purist approach, clean URLs, supports format negotiation. Cons: Complex, hard to test, poor developer experience.
GitHub's API uses this approach. It works at scale but adds cognitive overhead for consumers.
Choosing the Right Strategy#
| Criteria | URL Path | Query Param | Header | Content Negotiation |
|---|---|---|---|---|
| Simplicity | High | Medium | Low | Low |
| Visibility | High | Medium | Low | Low |
| Cacheability | High | Medium | Low | Low |
| REST purity | Low | Low | Medium | High |
| API gateway support | High | Medium | Medium | Low |
For most teams: URL path versioning. It's explicit, debuggable, and supported everywhere.
Deprecation Policies#
Versioning without deprecation is version hoarding. You need a clear lifecycle.
The API Lifecycle#
Active → Deprecated → Sunset → Removed
v3 v2 v1 v0
Active: Fully supported, receives new features. Deprecated: Functional but no new features. Migration docs available. Sunset date published. Sunset: Returns warning headers. May have degraded rate limits. Removed: Returns 410 Gone with migration guidance.
Deprecation Headers#
HTTP/1.1 200 OK
Deprecation: Sun, 01 Sep 2027 00:00:00 GMT
Sunset: Sun, 01 Dec 2027 00:00:00 GMT
Link: <https://docs.api.com/migration/v1-to-v2>; rel="deprecation"
Deprecation Timeline Best Practices#
- Internal APIs: 3-6 months deprecation window
- Partner APIs: 6-12 months
- Public APIs: 12-24 months
- Never: Sunset without notice, remove without sunset period
API Lifecycle Management#
Monitor Version Usage#
Track which consumers use which versions:
{
"v1": { "requests_per_day": 12400, "unique_consumers": 3 },
"v2": { "requests_per_day": 890000, "unique_consumers": 47 },
"v3": { "requests_per_day": 2100000, "unique_consumers": 112 }
}
When v1 traffic is near zero, you can confidently sunset it.
Migration Guides#
Every new version needs a migration guide covering:
- What changed and why
- Request/response diff examples
- Code snippets for common languages
- Timeline for previous version sunset
Rate Limiting by Version#
Encourage migration by adjusting limits:
v3 (active): 10,000 requests/minute
v2 (deprecated): 5,000 requests/minute
v1 (sunset): 1,000 requests/minute
OpenAPI and Swagger Versioning#
OpenAPI specifications should version alongside your API.
openapi: 3.1.0
info:
title: My API
version: "3.0.0"
x-api-lifecycle: active
x-sunset-date: null
paths:
/users/{id}:
get:
summary: Get user by ID
operationId: getUser
deprecated: false
Versioning Your Specs#
- Maintain separate spec files per major version:
openapi-v1.yaml,openapi-v2.yaml - Use the
deprecated: trueflag on operations being phased out - Publish changelogs alongside spec updates
- Generate client SDKs per version from the corresponding spec
Automated Compatibility Checks#
Tools like oasdiff can detect breaking changes between spec versions:
oasdiff breaking openapi-v1.yaml openapi-v2.yaml
This catches accidental breaking changes in CI before they ship.
Common Mistakes#
- Versioning too often — not every change needs a new version. Use additive changes.
- Never deprecating — maintaining five versions is expensive. Set sunset dates.
- Breaking without versioning — the cardinal sin. Even "small" field renames break consumers.
- Ignoring backwards compatibility in schemas — database changes can force API breaks if not planned.
- No migration tooling — a deprecation without a migration guide is just a warning nobody reads.
Key Takeaways#
- Version from day one — retrofitting versioning is painful.
- URL path versioning works for most teams.
- Additive changes avoid the need for new versions entirely.
- Deprecation policies with clear timelines keep your API surface manageable.
- OpenAPI specs should version alongside the API and run breaking-change detection in CI.
Your API is your product's handshake with the world. Version it deliberately, deprecate it gracefully, and retire it only when the data says it's safe.
Build tools that help teams design and document APIs visually at codelit.io.
Article #153 in the Codelit engineering series.
Try it on Codelit
GitHub Integration
Paste any repo URL to generate an interactive architecture diagram from real code
Related articles
Try these templates
Build this architecture
Generate an interactive architecture for API Versioning Strategies in seconds.
Try it in Codelit →
Comments