API Versioning Cheat Sheet
Quick reference for API versioning: URI versioning, header versioning, content negotiation, deprecation strategies, and migration patterns.
API Versioning Strategies
| Strategy | Example | Pros | Cons | Used By |
|---|---|---|---|---|
| URI Path | /v1/users | Simple, visible, cacheable | Pollutes URLs, REST purists dislike | Stripe, GitHub, Twitter |
| Query Param | /users?version=1 | Simple, flexible | Less visible, caching issues | Some internal APIs |
| Custom Header | X-API-Version: 1 | Clean URLs, flexible | Less visible, requires docs | Many enterprise APIs |
| Accept Header | Accept: application/vnd.api.v1+json | RESTful, content negotiation | Complex, less intuitive | GitHub (alternative) |
| Media Type | Content-Type: application/vnd.company.v1+json | Standard content negotiation | Verbose, requires client config | Some REST APIs |
| Date-Based | Accept-Version: 2024-01-01 | Clear timeline, auto-expiry | Requires date tracking | Stripe (for features) |
Breaking vs Non-Breaking Changes
⚠ Breaking Changes (require new version)
- × Removing an endpoint
- × Removing a response field
- × Changing a field's type
- × Changing authentication method
- × Modifying error response format
- × Changing field behavior/semantics
- × Removing required parameters
- × Changing status codes
✓ Non-Breaking Changes (backward compatible)
- ✓ Adding new endpoints
- ✓ Adding optional response fields
- ✓ Adding optional request parameters
- ✓ Adding new enum values
- ✓ Extending field length limits
- ✓ Adding new HTTP methods to endpoints
- ✓ Performance improvements
- ✓ Bug fixes (that don't change behavior)
Deprecation Strategy
| Phase | Duration | Actions | Communication |
|---|---|---|---|
| Announce | Day 0 | Publish deprecation notice, migration guide | Blog, email, dashboard banner, docs |
| Warn | 6 months before EOL | Add Deprecation header to responses | Response headers, API logs, emails |
| Sunset | EOL date | Return 410 Gone or redirect | Final email, last-chance notice |
| Retire | After EOL | Remove old version code | Post-mortem blog (optional) |
# Deprecation Response Headers
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Link: <https://api.example.com/v2/users>; rel="successor-version"
X-API-Warn: This endpoint is deprecated. Please migrate to /v2/users by Dec 31, 2026.
# RFC 8631 Deprecation format
Deprecation: true
Sunset: Sat, 31 Dec 2026 23:59:59 GMT
Link: </docs/migration-v2>; rel="deprecation"; type="text/html"
Version-Related HTTP Headers
| Header | Direction | Example |
|---|---|---|
| X-API-Version | Request | 2 |
| Accept-Version | Request | 2024-01-01 |
| Accept | Request | application/vnd.api.v2+json |
| API-Version | Response | 2.1.0 |
| Deprecation | Response | true |
| Sunset | Response | Sat, 31 Dec 2026 23:59:59 GMT |
| X-API-Warn | Response | This endpoint is deprecated... |
Real-World API Versioning Examples
Stripe — URI Versioning
# URI versioning (default)
GET https://api.stripe.com/v1/charges
# Date-based versioning for features
curl https://api.stripe.com/v1/charges \
-H "Stripe-Version: 2024-01-01"
# Account-level default version
# Set in Dashboard → Settings → API Version
# Response includes version
{
"id": "ch_123",
"object": "charge",
"livemode": false
}
GitHub — URI + Accept Header
# URI versioning (primary)
GET https://api.github.com/v1/users/octocat
# Accept header for media type versions
GET https://api.github.com/users/octocat \
-H "Accept: application/vnd.github.v3+json"
# Beta features via accept header
GET https://api.github.com/installation/repositories \
-H "Accept: application/vnd.github.machine-man-preview+json"
# Versioned error responses
{
"message": "Validation Failed",
"errors": [...],
"documentation_url": "https://docs.github.com/rest"
}
Twilio — URI Versioning with Year
# Year-based URI versioning
GET https://api.twilio.com/2010-04-01/Accounts/{sid}/Calls.json
GET https://api.twilio.com/2015-04-01/Accounts/{sid}/Messages.json
# Version in response
{
"accounts": [...],
"next_page_uri": "/2010-04-01/Accounts/...",
"uri": "/2010-04-01/Accounts.json"
}
Migration Patterns
Pattern 1: Side-by-Side Deployment
# Both versions run simultaneously
/v1/users → routes to UserControllerV1
/v2/users → routes to UserControllerV2
# Router config (Express.js example)
app.use('/v1/users', require('./controllers/v1/users'));
app.use('/v2/users', require('./controllers/v2/users'));
# Shared models, different transformers
const User = require('./models/User');
// V1 controller
exports.list = async (req, res) => {
const users = await User.findAll();
res.json(users.map(u => transformV1(u))); // Old format
};
// V2 controller
exports.list = async (req, res) => {
const users = await User.findAll();
res.json(users.map(u => transformV2(u))); // New format
};
Pattern 2: Adapter/Facade
# Single implementation, version-specific adapters
class UserService {
async getUser(id) { /* core logic */ }
}
class UserAdapterV1 {
format(user) {
return { id: user.id, name: user.full_name }; // Snake case
}
}
class UserAdapterV2 {
format(user) {
return { id: user.id, name: user.fullName }; // Camel case
}
}
# Router selects adapter based on version
app.get('/users/:id', (req, res) => {
const version = req.headers['x-api-version'] || 'v1';
const adapter = version === 'v2' ? new UserAdapterV2() : new UserAdapterV1();
const user = await userService.getUser(req.params.id);
res.json(adapter.format(user));
});
Pattern 3: Gradual Rollout
# Feature flag controlled migration
const MIGRATION_PERCENTAGE = 10; // 10% of traffic to v2
app.get('/users/:id', (req, res) => {
// Check if user is in migration cohort
const userHash = hash(req.params.id) % 100;
if (userHash < MIGRATION_PERCENTAGE) {
// Route to v2 (new implementation)
return v2Controller.get(req, res);
}
// Default to v1
v1Controller.get(req, res);
});
# Gradually increase: 10% → 25% → 50% → 75% → 100%
Best Practices
✓
Use URI versioning for simplicity and visibility
✓
Maintain at least 6-12 months overlap between versions
✓
Communicate deprecation early and often
✓
Provide migration guides with code examples
✓
Use Deprecation and Sunset headers for machine-readable warnings
✓
Version SDKs alongside API versions
✗
Don't break existing clients without long notice
✗
Don't version everything—only when behavior changes