API Versioning Strategies: Backward Compatibility, Evolution, and Migration
API versioning is a problem every backend team eventually faces — do not change it, clients complain about incompatibility; change it, maintaining multiple versions becomes a burden. This article covers URL path versioning, header versioning, compatibility strategies, and version lifecycle — for backend developers and architects designing or maintaining APIs.
The Bottom Line: The Goal of Versioning Is “Avoid Changing Versions”
The best API versioning strategy is not “how to label versions” — it is “how to make clients not need to upgrade when you update.”
1. Versioning Strategies
1.1 URL Path Versioning (Recommended)
GET /v1/users
GET /v2/users
Pros: transparent, easy debugging, cache per version Cons: URL is less clean, version numbers spread through code
1.2 Header Versioning
GET /users
Accept: application/vnd.aigcharness.v1+json
Pros: clean URL Cons: opaque, extra step to debug, easy to miss
1.3 Parameter Versioning
GET /users?version=1
Pros: simple to implement Cons: cache issues, parameters are easy to overlook
2. Compatibility Strategy
2.1 Backward-Compatible Changes
| Change Type | Example | Compatible? |
|---|---|---|
| Add field | New field in response | ✅ Compatible |
| Add optional param | New optional request param | ✅ Compatible |
| Extend enum | New enum value | ✅ Compatible |
| Add new endpoint | New API endpoint | ✅ Compatible |
| Change response format | Rename field | ❌ Incompatible |
| Remove field | Delete response field | ❌ Incompatible |
| Change required param | Optional to required | ❌ Incompatible |
| Change URL | Modify endpoint path | ❌ Incompatible |
2.2 Compatibility Safeguards
interface ApiResponse<T> {
version: string;
data: T;
meta?: Record<string, unknown>;
}
interface GetUsersParams {
page?: number;
limit?: number;
sort?: 'name' | 'created_at';
}
3. Version Lifecycle
v1 released → v2 released → v1 deprecated → v1 sunset → v1 removed
Stages
| Stage | Status | Note |
|---|---|---|
| Active | Fully supported | Normal use |
| Deprecated | No new features | Security fixes only |
| Sunset | Going away soon | Sunset Header in response |
| Removed | Gone | Returns 410 Gone |
Notifying Clients
HTTP/1.1 200 OK
Sunset: Sat, 23 Jan 2027 00:00:00 GMT
Deprecation: true
Link: </v2/users>; rel="successor-version"
4. Migration Practice
4.1 Dual Write/Read
Run old and new versions simultaneously, gradually migrate traffic:
app.use('/api', (req, res, next) => {
req.apiVersion = req.headers['accept-version'] === 'v2' ? 'v2' : 'v1';
next();
});
4.2 Adapter Pattern
interface UserV1 { name: string; email: string; }
interface UserV2 { fullName: string; emailAddress: string; }
function adaptToV1(user: UserV2): UserV1 {
return { name: user.fullName, email: user.emailAddress };
}
Summary
| Principle | Note |
|---|---|
| Be compatible | Adding fields and params does not need a new version |
| Transparent | URL path versioning recommended |
| Clear lifecycle | Deprecation period of at least 6 months |
| Notify clients | Sunset Header + documentation |
| Gradual migration | Dual write/read + adapter pattern |
Good API versioning is not measured by “how many versions you maintain” — it is measured by “whether clients need to care that you upgraded.” An API with good backward compatibility means clients may not even know when you released a new version.
Need API backend development or architecture design? Contact us — tell us about your interface scope and scale, feasibility within 24 hours.
FAQ
URL path versioning or header versioning — which is better?
URL path versioning (/v1/users, /v2/users) is more transparent — clients can see the version from the URL, debugging is easier, and caching can distinguish between versions. Header versioning keeps URLs cleaner, but requires additional client configuration and an extra step during debugging. Recommendation: use URL path versioning for external APIs, header versioning for internal APIs.
How should API version numbers be named?
Use simplified SemVer: major version numbers only (v1, v2, v3) — only bump for incompatible changes. Do not use dates (v20260723) — hard-coding dates in client code looks odd. Do not use minor versions (v1.1, v2.3) — API versions only mark "incompatible changes," compatible changes do not need a new version.
How long should old versions be maintained?
At least 6 months, at most 12 months. Too short and clients cannot migrate; too long and maintenance costs are high. Recommendation: ① announce the deprecation timeline when releasing a new version; ② deprecation period of at least 6 months; ③ include a Sunset Header in responses during deprecation; ④ return 410 Gone after the sunset period.
How do you avoid having too many API versions?
The key is "backward compatibility." Make new versions as compatible as possible with old versions — adding fields, adding optional parameters, extending enums — none of these require a new version. Only incompatible changes (removing fields, changing field types, changing required parameters) need a new version. A well-maintained API should release 1-2 new versions per year.
This article comes from AI Enable Harness front-line delivery practice. Need a similar system or optimization service?
Subscribe to Updates
Get notified when new articles are published. No spam, occasional updates only.
Subscribe →