Every API eventually needs to change in ways that break existing clients. Field removed, response format changed, authentication updated. The question isn’t whether you’ll have breaking changes — it’s how you’ll manage them.
Good versioning gives you freedom to evolve while giving clients stability and migration paths.
The Three Schools of Versioning
URL Path Versioning
Pros:
- Obvious and explicit
- Easy to route at load balancer level
- Simple to document
- Cache-friendly (different URLs = different cache keys)
Cons:
- Version is part of resource identity
- Hard to version individual endpoints differently
- URL clutter
This is the most common approach and usually the right default choice.
Header Versioning
Or custom header:
Pros:
- Clean URLs
- Can version at granular level
- RESTful purists prefer it
Cons:
- Not visible in browser/logs without inspection
- Harder to test manually
- Caching more complex
Query Parameter Versioning
Pros:
- Visible in URL
- Easy to switch versions for testing
Cons:
- Pollutes query string
- Optional parameter means default version logic needed
- Caching complications
Implementation: URL Path Versioning
| |
Semantic Versioning for APIs
Adapt semver to API context:
- Major (v1 → v2): Breaking changes. New URL path.
- Minor (v1.1 → v1.2): New endpoints, new optional fields. Backwards compatible.
- Patch (v1.1.1 → v1.1.2): Bug fixes, documentation. No API changes.
Only major versions need new URL paths. Minor and patch versions are invisible to clients.
What Counts as Breaking?
Breaking changes (require new major version):
- Removing a field from response
- Removing an endpoint
- Changing field type (string → integer)
- Changing authentication mechanism
- Changing error response format
- Making optional field required
Non-breaking changes (safe in current version):
- Adding new optional fields to response
- Adding new endpoints
- Adding new optional request parameters
- Deprecation warnings
- Performance improvements
| |
Deprecation: The Migration Bridge
Never remove without warning:
| |
Standard headers:
Deprecation: true— This endpoint is deprecatedSunset: <date>— When it will stop workingLink: <url>; rel="successor-version"— Where to migrate
Version Lifecycle
Typical timeline:
- v2 released: v1 still current, both supported
- v1 deprecated: v1 works but returns deprecation headers
- v1 sunset announced: Clear date communicated
- v1 removed: Returns 410 Gone
Minimum deprecation period depends on your clients. Enterprise APIs might need 12-24 months. Internal APIs might need 2 weeks.
Content Negotiation Alternative
For more granular versioning without URL changes:
| |
Version Discovery
Let clients know what’s available:
| |
Default Version Behavior
What happens when no version specified?
Option 1: Require version (strict)
| |
Option 2: Default to latest (convenient but risky)
| |
Option 3: Default to oldest stable (conservative)
| |
Option 1 is safest. Clients must explicitly choose a version, preventing surprise breakage when you release v3.
Testing Across Versions
| |
Documentation Per Version
Each version needs its own documentation:
Clearly mark deprecated endpoints. Show migration guides. Include examples for both versions during transition periods.
API versioning is about respect for your clients. They built systems depending on your API. Breaking them without warning, without migration paths, without deprecation periods — that’s a betrayal of trust.
Version in the URL path for clarity. Communicate breaking changes early. Provide generous deprecation periods. Document everything. Your clients will thank you, and you’ll have the freedom to keep evolving your API.