API Versioning Skill
API Versioning Skill
How to Use This Skill
- Review the patterns and examples below
- Apply the relevant patterns to your implementation
- Follow the best practices outlined in this skill
Production-ready API versioning skill covering all major versioning strategies, deprecation workflows, backward compatibility patterns, and client migration guidance.
When to Use This Skill
Use api-versioning when:
- Planning versioning strategy for new APIs
- Adding breaking changes to existing APIs
- Creating deprecation and sunset workflows
- Migrating clients between API versions
- Documenting version compatibility
- Designing client SDK versioning
Don't use api-versioning when:
- Designing REST API structure (use
restful-api-designskill) - Designing GraphQL schemas (use
graphql-designskill) - Only need security review (use
security-auditskill) - Documentation generation only
Versioning Strategies Comparison
| Strategy | Example | Pros | Cons |
|---|---|---|---|
| URL Path | /api/v1/users | Explicit, cacheable | URL pollution |
| Header | API-Version: 1 | Clean URLs | Hidden, harder to test |
| Query Param | /api/users?version=1 | Explicit, easy testing | Affects caching |
| Content Type | Accept: application/vnd.api.v1+json | HTTP standard | Complex, verbose |
Recommendation by Use Case
| Use Case | Recommended Strategy | Rationale |
|---|---|---|
| Public REST API | URL Path | Most discoverable, best tooling support |
| Internal Microservices | Header | Cleaner routing, flexible |
| GraphQL API | Schema evolution | No versions, additive changes |
| Mobile Apps | Header + Fallback | Graceful degradation |
| Browser Apps | URL Path | Easy debugging, bookmarkable |
Instructions
Phase 1: Strategy Selection
Objective: Choose appropriate versioning strategy.
-
Evaluate requirements:
Decision Matrix:
[ ] Public API with external developers → URL Path (most transparent)
[ ] Internal services with controlled clients → Header (cleaner)
[ ] Single Page Applications → URL Path or Query Param
[ ] Mobile apps with slow updates → Header with backwards compatibility
[ ] Enterprise integrations → Header with long deprecation periods
[ ] GraphQL → Schema evolution (no explicit versions) -
Define version format:
Version Formats:
Simple numeric: v1, v2, v3
Semantic: v1.0, v1.1, v2.0
Date-based: 2024-01-15, 2024-06
Recommendation: Simple numeric (v1, v2) for major versions
Use semantic versioning in changelog/documentation -
Document versioning policy:
# API Versioning Policy
## Version Scheme
- Major versions: `/api/v1/`, `/api/v2/`
- Breaking changes require new major version
- Minor/patch changes within same version
## Supported Versions
| Version | Status | Support Until |
|---------|--------|---------------|
| v3 | Current | Ongoing |
| v2 | Maintained | 2025-12-31 |
| v1 | Deprecated | 2024-06-30 |
## Breaking Changes Definition
- Removing endpoints or fields
- Changing field types or formats
- Adding required parameters
- Changing authentication methods
- Modifying error response structure
Phase 2: Implementation
Objective: Implement chosen versioning strategy.
-
URL Path Versioning:
# FastAPI with URL versioning
from fastapi import FastAPI, APIRouter
app = FastAPI()
# Version 1 router
v1_router = APIRouter(prefix="/api/v1")
@v1_router.get("/users")
async def get_users_v1():
return {"version": "v1", "users": [...]}
# Version 2 router
v2_router = APIRouter(prefix="/api/v2")
@v2_router.get("/users")
async def get_users_v2():
# New response format
return {
"version": "v2",
"data": [...],
"pagination": {...}
}
app.include_router(v1_router)
app.include_router(v2_router)// Express with URL versioning
import express from 'express';
const app = express();
// Version 1 routes
const v1Router = express.Router();
v1Router.get('/users', (req, res) => {
res.json({ version: 'v1', users: [] });
});
// Version 2 routes
const v2Router = express.Router();
v2Router.get('/users', (req, res) => {
res.json({ version: 'v2', data: [], pagination: {} });
});
app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router); -
Header Versioning:
# FastAPI with header versioning
from fastapi import FastAPI, Header, HTTPException
app = FastAPI()
@app.get("/api/users")
async def get_users(api_version: str = Header(default="v2", alias="API-Version")):
if api_version == "v1":
return {"version": "v1", "users": [...]}
elif api_version == "v2":
return {"version": "v2", "data": [...], "pagination": {...}}
else:
raise HTTPException(status_code=400, detail=f"Unsupported API version: {api_version}")// Express with header versioning
const versionMiddleware = (req, res, next) => {
req.apiVersion = req.headers['api-version'] || 'v2';
next();
};
app.use(versionMiddleware);
app.get('/api/users', (req, res) => {
switch (req.apiVersion) {
case 'v1':
return res.json({ version: 'v1', users: [] });
case 'v2':
return res.json({ version: 'v2', data: [], pagination: {} });
default:
return res.status(400).json({ error: 'Unsupported version' });
}
}); -
Content Negotiation:
from fastapi import FastAPI, Request, HTTPException
app = FastAPI()
@app.get("/api/users")
async def get_users(request: Request):
accept = request.headers.get("accept", "")
if "application/vnd.api.v1+json" in accept:
return {"version": "v1", "users": [...]}
elif "application/vnd.api.v2+json" in accept:
return {"version": "v2", "data": [...]}
else:
# Default to latest
return {"version": "v2", "data": [...]}
Phase 3: Deprecation Workflow
Objective: Gracefully deprecate old API versions.
-
Add Sunset headers:
from fastapi import FastAPI
from fastapi.responses import JSONResponse
from datetime import datetime
app = FastAPI()
@app.get("/api/v1/users")
async def get_users_v1():
response = JSONResponse(content={"users": [...]})
# RFC 8594 Sunset header
response.headers["Sunset"] = "Sat, 30 Jun 2024 23:59:59 GMT"
response.headers["Deprecation"] = "true"
response.headers["Link"] = '</api/v2/users>; rel="successor-version"'
return response -
Document deprecation:
# Deprecation Notice: API v1
## Timeline
- **Deprecated:** January 1, 2024
- **Read-only:** April 1, 2024
- **Sunset:** June 30, 2024
## Breaking Changes in v2
### Response Format
v1:
```json
{ "users": [...] }v2:
{ "data": [...], "pagination": {...} }Removed Endpoints
GET /api/v1/users/all→ Use pagination withGET /api/v2/users
Changed Fields
user.fullName→user.nameuser.registered→user.createdAt(ISO 8601 format)
Migration Guide
-
Create migration guide:
# Migration Guide: v1 to v2
## Quick Migration Checklist
- [ ] Update base URL from `/api/v1` to `/api/v2`
- [ ] Update response parsing for new format
- [ ] Handle pagination in list endpoints
- [ ] Update field names per mapping table
- [ ] Test all API calls
## Response Format Changes
### Before (v1)
```javascript
const response = await fetch('/api/v1/users');
const { users } = await response.json();After (v2)
const response = await fetch('/api/v2/users');
const { data, pagination } = await response.json();
const users = data;Field Mapping
v1 Field v2 Field Notes fullNamenameCombined first/last registeredcreatedAtISO 8601 format isActivestatusEnum: active/inactive Code Examples
Python SDK
# v1
from myapi import Client
client = Client(version='v1')
users = client.users.list()
# v2
client = Client(version='v2')
result = client.users.list()
users = result.data
has_more = result.pagination.has_next
Phase 4: Client SDK Versioning
Objective: Version client SDKs alongside API.
-
SDK versioning strategy:
SDK Version: 2.3.1
│ │ │
│ │ └── Patch: Bug fixes
│ └──── Minor: New features, backwards compatible
└────── Major: Breaking changes (often tied to API version)
Mapping:
- SDK 1.x.x → API v1
- SDK 2.x.x → API v2
- SDK 3.x.x → API v3 -
Multi-version SDK support:
// SDK supporting multiple API versions
class APIClient {
constructor(options: {
apiKey: string;
version?: 'v1' | 'v2' | 'v3';
baseUrl?: string;
}) {
this.version = options.version || 'v3';
this.baseUrl = options.baseUrl || 'https://api.example.com';
}
async getUsers(): Promise<UsersResponse> {
const url = `${this.baseUrl}/api/${this.version}/users`;
const response = await fetch(url, {
headers: { 'Authorization': `Bearer ${this.apiKey}` }
});
if (this.version === 'v1') {
const { users } = await response.json();
return { data: users, pagination: null };
}
return response.json();
}
} -
Deprecation warnings in SDK:
class APIClient {
constructor(options: ClientOptions) {
if (options.version === 'v1') {
console.warn(
'[DEPRECATION WARNING] API v1 is deprecated and will be removed on 2024-06-30. ' +
'Please upgrade to v2. See https://docs.example.com/migration'
);
}
}
}
Examples
Example 1: URL Path Versioning (Complete)
# OpenAPI specification with URL versioning
openapi: 3.1.0
info:
title: User API
version: 2.0.0
description: |
## API Versioning
This API uses URL path versioning:
- `/api/v1/*` - Legacy (deprecated, sunset 2024-06-30)
- `/api/v2/*` - Current stable version
- `/api/v3/*` - Beta (subject to change)
## Version Support Policy
- Current version: Full support
- Previous version: 12 months maintenance
- Older versions: Deprecated, no support
servers:
- url: https://api.example.com/api/v2
description: Production (v2)
- url: https://api.example.com/api/v1
description: Legacy (deprecated)
paths:
/users:
get:
summary: List users
responses:
'200':
description: Success
headers:
X-API-Version:
schema:
type: string
description: API version used
Example 2: GraphQL Schema Evolution
# Schema evolution without explicit versions
type User {
id: ID!
name: String!
# Deprecated field - use 'name' instead
fullName: String @deprecated(reason: "Use 'name' field instead. Will be removed 2024-06-30")
# New fields added (non-breaking)
avatar: String
preferences: UserPreferences
}
type Query {
user(id: ID!): User
# Deprecated query
getUser(id: ID!): User @deprecated(reason: "Use 'user' query instead")
# New query added (non-breaking)
users(
first: Int
after: String
filter: UserFilter
): UserConnection!
}
Example 3: Sunset Header Implementation
from fastapi import FastAPI, Response
from fastapi.middleware.base import BaseHTTPMiddleware
from datetime import datetime
import pytz
class DeprecationMiddleware(BaseHTTPMiddleware):
DEPRECATED_VERSIONS = {
'v1': {
'sunset': datetime(2024, 6, 30, 23, 59, 59, tzinfo=pytz.UTC),
'successor': '/api/v2'
}
}
async def dispatch(self, request, call_next):
response = await call_next(request)
# Check if deprecated version
path = request.url.path
for version, info in self.DEPRECATED_VERSIONS.items():
if f'/api/{version}/' in path:
sunset_date = info['sunset'].strftime('%a, %d %b %Y %H:%M:%S GMT')
response.headers['Deprecation'] = 'true'
response.headers['Sunset'] = sunset_date
response.headers['Link'] = f'<{info["successor"]}>; rel="successor-version"'
break
return response
app = FastAPI()
app.add_middleware(DeprecationMiddleware)
Integration
Related Components
- Skill:
restful-api-design- Complete REST API design - Skill:
graphql-design- GraphQL schema evolution - Agent:
senior-architect- Architecture decisions - Skill:
security-audit- Version security review
OpenAPI Extensions
x-api-lifecycle:
status: deprecated
sunset: 2024-06-30T23:59:59Z
successor: /api/v2
migration-guide: https://docs.example.com/migration
x-version-history:
- version: v3.0.0
date: 2024-01-15
changes: "Added batch operations"
- version: v2.0.0
date: 2023-06-01
changes: "New pagination format"
Troubleshooting
| Issue | Solution |
|---|---|
| Clients not migrating | Add sunset headers, send deprecation emails |
| Version conflicts | Use strict version matching |
| Cache issues | Include version in cache keys |
| SDK compatibility | Test SDKs against all supported versions |
Best Practices Checklist
- Document versioning policy publicly
- Use semantic versioning in changelog
- Implement Sunset headers for deprecated versions
- Provide minimum 6-month deprecation period
- Create comprehensive migration guides
- Version SDK alongside API
- Test all versions in CI/CD
- Monitor version usage analytics
- Send proactive deprecation notifications
- Maintain backwards compatibility when possible
References
- RFC 8594 - Sunset Header
- Semantic Versioning
- API Versioning (Stripe's Approach)
- Microsoft REST API Guidelines - Versioning
Success Output
When successful, this skill MUST output:
✅ SKILL COMPLETE: api-versioning
Completed:
- [x] Versioning strategy selected and documented
- [x] Version routing implemented (URL/header/content negotiation)
- [x] Deprecation workflow established with Sunset headers
- [x] Migration guide created for breaking changes
- [x] Client SDK versioning aligned with API versions
- [x] OpenAPI spec updated with version metadata
Versioning Details:
- Strategy: URL Path Versioning (/api/v1, /api/v2)
- Supported Versions: v2 (current), v1 (deprecated, sunset 2024-06-30)
- Deprecation Headers: Sunset, Deprecation, Link (successor-version)
Outputs:
- API versioning policy document
- Deprecation notice for v1
- Migration guide v1 → v2
- OpenAPI spec with x-api-lifecycle metadata
- SDK version mapping documentation
Completion Checklist
Before marking this skill as complete, verify:
- Versioning strategy documented in API policy
- Version routing implemented and tested
- Sunset headers added to deprecated versions
- Deprecation notice published with timeline
- Migration guide created with code examples
- Breaking changes documented with before/after
- Field mapping table created for changed schemas
- SDK version mapping documented (SDK 1.x → API v1)
- OpenAPI spec includes x-api-lifecycle metadata
- All versions tested in CI/CD pipeline
- Version analytics tracking implemented
- Deprecation warnings added to SDK
Failure Indicators
This skill has FAILED if:
- ❌ No versioning strategy documented
- ❌ Multiple versioning methods mixed inconsistently
- ❌ Deprecated versions have no sunset date
- ❌ Breaking changes not documented
- ❌ No migration guide for version changes
- ❌ Client SDKs not versioned with API
- ❌ Sunset headers missing or incorrect format
- ❌ Version routing breaks existing clients
- ❌ OpenAPI spec lacks version metadata
- ❌ No version usage analytics
When NOT to Use
Do NOT use this skill when:
- Designing new API from scratch (use
restful-api-designorgraphql-designfirst) - Only adding non-breaking changes (use additive evolution)
- Internal APIs with single controlled client (versioning overhead not needed)
- GraphQL APIs using schema evolution (use
graphql-designskill) - Need only security/auth changes (use
api-security-patterns) - Documenting existing versions (use
api-documentationskill) - Performance optimization only (no version changes needed)
Use alternative skills:
restful-api-design- Complete REST API designgraphql-design- GraphQL schema evolution patternsapi-documentation- OpenAPI/Swagger documentationapi-security-patterns- Authentication and authorization
Anti-Patterns (Avoid)
| Anti-Pattern | Problem | Solution |
|---|---|---|
| Mixing strategies | Confuses clients, hard to maintain | Choose ONE versioning strategy |
| No deprecation period | Breaks clients without warning | Minimum 6-month deprecation period |
| Removing old versions early | Client outages | Follow published sunset timeline |
| No migration guide | Clients can't upgrade | Create detailed migration docs with examples |
| Breaking changes in minor versions | Violates semver expectations | Only break in major versions |
| No version analytics | Can't measure adoption | Track version usage in metrics |
| Implicit version changes | Silent behavior changes | Require explicit version in request |
| No SDK versioning | SDK/API version mismatch | Version SDK major versions with API |
Principles
This skill embodies these CODITECT principles:
- #2 First Principles - Understand versioning strategies and tradeoffs
- #5 Eliminate Ambiguity - Explicit version in every request
- #6 Clear, Understandable, Explainable - Migration guides explain changes
- #7 Inform, Don't Ask - Deprecation headers proactively notify clients
- #8 No Assumptions - Document all breaking changes and migration paths
- #9 Search Before Create - Follow industry standards (RFC 8594, semver)
Full Principles: CODITECT-STANDARD-AUTOMATION.md
Status: Production-ready Strategies Covered: URL, header, query, content negotiation Standards: RFC 8594 Sunset Header, Semantic Versioning Integration: REST, GraphQL, gRPC, client SDKs