APIs evolve. Features are added, behaviors change, and sometimes breaking changes are necessary. A good versioning strategy lets you evolve your API while maintaining backward compatibility for existing clients.
Why Version APIs?
Problems Without Versioning
- Breaking changes affect all clients immediately
- No way to deprecate features gradually
- Clients forced to update on your schedule
- Rollback is difficult
- Can't support multiple client versions
Versioning Goals
✓ Backward compatibility
✓ Clear deprecation path
✓ Parallel version support
✓ Minimal client disruption
✓ Clean evolution path
Versioning Approaches
1. URL Path Versioning
GET /v1/users
GET /v2/users
Implementation:
Pros:
- Simple and obvious
- Easy to test and debug
- Clear in documentation
- Works with caching
Cons:
- URL changes between versions
- Can lead to code duplication
- Harder to share logic
2. Query Parameter Versioning
GET /users?version=1
GET /users?version=2
Implementation:
Pros:
- URL structure stays consistent
- Optional (can default to latest)
- Easy to add to existing APIs
Cons:
- Less discoverable
- Can be forgotten in requests
- Caching more complex
3. Header Versioning
GET /users
Accept-Version: v1
GET /users
Accept-Version: v2
Implementation:
Pros:
- Clean URLs
- RESTful (URL represents resource)
- Flexible
Cons:
- Hidden from URL
- Harder to test in browser
- Documentation complexity
4. Content Negotiation
GET /users
Accept: application/vnd.myapi.v1+json
GET /users
Accept: application/vnd.myapi.v2+json
Implementation:
Pros:
- Most RESTful approach
- Clean URLs
- Standard HTTP mechanism
Cons:
- Complex Accept header parsing
- Less intuitive
- Harder to test
Version Management
Supporting Multiple Versions
Deprecation Process
Timeline:
1. Announce deprecation (6 months notice)
2. Add deprecation headers
3. Monitor v1 usage
4. Send reminders to active v1 users
5. Sunset v1
Version Lifecycle
Development → Current → Deprecated → Sunset
Typical timeline:
- Development: Pre-release, breaking changes allowed
- Current: Stable, primary version
- Deprecated: Maintained, migration encouraged (6-12 months)
- Sunset: No longer available
Breaking vs Non-Breaking Changes
Non-Breaking (Safe)
✓ Adding new endpoints
✓ Adding optional request fields
✓ Adding response fields
✓ Adding new enum values (with care)
✓ Relaxing validation
Breaking (Requires New Version)
✗ Removing endpoints
✗ Removing or renaming fields
✗ Changing field types
✗ Changing error formats
✗ Tightening validation
✗ Changing authentication
Additive Changes
Documentation
Version-Specific Docs
Migration Guides
After (v2):
Migration Steps
- Update response parsing to handle nested profile
- Rename
avatar_urltoprofile.avatar - Note:
namemoved toprofile.name
## Client Libraries
### Version-Aware SDKs
```typescript
// SDK supports multiple versions
import { createClient } from '@myapi/sdk';
const v1Client = createClient({ version: 'v1' });
const v2Client = createClient({ version: 'v2' });
// Or auto-detect
const client = createClient({ version: 'latest' });
Gradual Migration
Best Practices
1. Default to Latest Stable
2. Version Response Includes
3. Rate Limit by Version
4. Monitor Version Usage
Choosing a Strategy
URL versioning when:
- Public API with many clients
- Need clear documentation
- Caching is important
- Simple is better
Header versioning when:
- Internal APIs
- RESTful purity matters
- Clients are controlled
- URLs should represent resources
Query parameter when:
- Adding to existing API
- Optional versioning
- Simple implementation needed
Conclusion
API versioning is about managing change while respecting your clients. Choose a strategy that fits your use case, document it clearly, and stick with it.
URL versioning works for most cases—it's simple, obvious, and works well with documentation and caching. Whatever you choose, the key is consistency and clear communication about deprecation timelines.
Your API is a contract. Versioning lets you evolve that contract without breaking the trust of developers who depend on it.