Audience
This guide is for developers and architects who need to evolve APIs without breaking existing clients:
- Backend developers maintaining APIs that need to change over time
- API architects designing versioning strategies for new APIs
- Tech leads establishing API governance policies for their teams
- DevOps engineers managing API deployments and migrations
- Product managers understanding the impact of API changes on integrations
You should already understand REST API basics. If not, start with How a REST API Works.
Goal
After reading this guide, you’ll understand:
- Why versioning is essential for any API that will evolve
- How to identify breaking vs. non-breaking changes
- The pros and cons of different versioning strategies
- How semantic versioning applies to APIs
- What deprecation means and how to announce it properly
- The complete lifecycle of an API version from release to sunset
You’ll have a framework to make versioning decisions and communicate changes to your API consumers.
1. Why Version APIs?
Every API evolves. Features get added, bugs get fixed, and sometimes the design needs to change fundamentally. Without versioning, you have two bad options:
Option A: Break everyone
graph LR
A[API Change Deployed] --> B[All Clients Break]
B --> C[Support Tickets Flood In]
B --> D[Users Lose Trust]
B --> E[Business Impact]
style A fill:#ffccbc
style B fill:#ffcdd2
style C fill:#ffcdd2
style D fill:#ffcdd2
style E fill:#ffcdd2Option B: Never change anything
Your API becomes frozen, accumulating technical debt and unable to fix design mistakes. New features get bolted on awkwardly, and the API becomes harder to use over time.
The solution: Versioning
graph LR
A[v2 Released] --> B[v1 Continues Working]
A --> C[New Clients Use v2]
B --> D[Existing Clients Unaffected]
C --> E[Gradual Migration]
D --> F[Happy Users]
E --> F
style A fill:#c8e6c9
style B fill:#c8e6c9
style F fill:#c8e6c9Versioning gives you a contract: “This version behaves this way, and I won’t break that behavior.”
What Triggers a New Version?
Not every change requires a new version. You need a new version when you make breaking changesβchanges that would cause existing clients to fail.
| Change Type | New Version Needed? |
|---|---|
| Remove a field | Yes |
| Rename a field | Yes |
| Change a field’s type | Yes |
| Change a required field to optional | Maybe (context-dependent) |
| Change an optional field to required | Yes |
| Change error response format | Yes |
| Add a new field | No |
| Add a new endpoint | No |
| Add a new optional parameter | No |
| Fix a bug | No (usually) |
The rule is simple: If existing client code would break, it’s a breaking change.
2. Breaking vs. Non-Breaking Changes
Understanding what constitutes a breaking change is crucial for API governance.
Breaking Changes (Require New Version)
graph TD
A[Breaking Changes] --> B[Field Changes]
A --> C[Behavioral Changes]
A --> D[Contract Changes]
B --> B1[Remove field]
B --> B2[Rename field]
B --> B3[Change type]
C --> C1[Change validation rules]
C --> C2[Change business logic]
C --> C3[Change error codes]
D --> D1[Change auth method]
D --> D2[Change required params]
D --> D3[Change response structure]
style A fill:#ffcdd2
style B fill:#fff9c4
style C fill:#fff9c4
style D fill:#fff9c4Example: Field Removal
// v1 Response
{
"id": 123,
"name": "Alice",
"email": "[email protected]",
"phone": "+1-555-1234" // Clients may depend on this
}
// v2 Response (breaking: removed phone)
{
"id": 123,
"name": "Alice",
"email": "[email protected]"
}
Any client code that accesses response.phone will now fail.
Example: Type Change
// v1 Response
{
"price": "29.99" // String
}
// v2 Response (breaking: changed to number)
{
"price": 29.99 // Number
}
Clients parsing the price as a string will break.
Example: Required Field Added
// v1 Request (works)
{
"name": "Product",
"price": 29.99
}
// v2 Request (breaking: category now required)
{
"name": "Product",
"price": 29.99,
"category": "electronics" // Required in v2
}
Existing client code won’t send category, causing validation failures.
Non-Breaking Changes (No New Version)
graph TD
A[Non-Breaking Changes] --> B[Additive Changes]
A --> C[Relaxing Constraints]
A --> D[Internal Changes]
B --> B1[Add new field]
B --> B2[Add new endpoint]
B --> B3[Add optional param]
C --> C1[Make required optional]
C --> C2[Expand valid values]
C --> C3[Increase limits]
D --> D1[Performance improvements]
D --> D2[Bug fixes]
D --> D3[Internal refactoring]
style A fill:#c8e6c9
style B fill:#e8f5e9
style C fill:#e8f5e9
style D fill:#e8f5e9Example: Adding Fields
// v1 Response
{
"id": 123,
"name": "Alice"
}
// Still v1 (non-breaking: added field)
{
"id": 123,
"name": "Alice",
"avatar_url": "https://..." // New field, clients can ignore
}
Well-designed clients ignore unknown fields, so this is safe.
Example: New Endpoint
# Existing endpoints unchanged
GET /users
GET /users/:id
# New endpoint added (non-breaking)
GET /users/:id/preferences
Existing clients don’t call the new endpoint, so they’re unaffected.
The Gray Areas
Some changes fall into gray areas:
Changing default values:
- If clients rely on the old default, it’s breaking
- If it’s truly just an internal optimization, it might be safe
Changing error messages (not codes):
- Generally safe if clients use error codes, not message strings
- Breaking if clients parse error messages
Adding values to an enum:
- Safe if clients handle unknown values gracefully
- Breaking if clients have exhaustive switch statements
Best practice: When in doubt, treat it as breaking.
3. Versioning Strategies
There are several ways to indicate which API version a client wants.
URL Path Versioning (Recommended)
GET /v1/users
GET /v2/users
# Full URL
https://api.example.com/v1/users
https://api.example.com/v2/users
graph LR
A[Client Request] --> B{URL Path}
B -->|/v1/users| C[v1 Handler]
B -->|/v2/users| D[v2 Handler]
C --> E[v1 Response]
D --> F[v2 Response]
style A fill:#e3f2fd
style C fill:#fff9c4
style D fill:#c8e6c9Pros:
- Highly visible: Version is right in the URL
- Easy to test: Just change the URL in your browser
- Simple routing: Route different versions to different handlers
- Cache-friendly: Different URLs = different cache keys
- Clear documentation: Each version has distinct endpoints
Cons:
- URL pollution: All URLs change with each version
- Potential code duplication: May need separate handlers per version
- Versioning the resource, not the representation: Philosophically impure
When to use: Most APIs. This is the default choice for simplicity and visibility.
Header-Based Versioning
GET /users
Accept: application/vnd.example.v1+json
GET /users
Accept: application/vnd.example.v2+json
Or with a custom header:
GET /users
X-API-Version: 1
GET /users
X-API-Version: 2
Pros:
- Clean URLs: Same URL for all versions
- HTTP-compliant: Uses content negotiation as designed
- Resource identity preserved: URL identifies the resource, not the version
Cons:
- Less visible: Version hidden in headers
- Harder to test: Can’t just paste URL in browser
- Easy to forget: Clients may omit the header
- Caching complexity: Need to vary cache on headers
When to use: When you prioritize clean URLs and have sophisticated clients.
Query Parameter Versioning
GET /users?version=1
GET /users?version=2
Pros:
- Version is visible in URL
- Easy to switch versions
Cons:
- Mixes concerns: Query params are for filtering, not versioning
- Caching issues: Query params can affect caching unpredictably
- Optional by nature: Easy to forget
When to use: Rarely. Generally not recommended.
Strategy Comparison
| Strategy | Visibility | Testability | Caching | Purity |
|---|---|---|---|---|
| URL Path | High | Easy | Simple | Low |
| Header | Low | Hard | Complex | High |
| Query Param | Medium | Easy | Problematic | Low |
Recommendation: Start with URL path versioning. It’s the most practical choice for most teams.
4. Semantic Versioning for APIs
Semantic Versioning (SemVer) provides a structured way to communicate the impact of changes.
The MAJOR.MINOR.PATCH Format
v2.3.1
β β β
β β βββ PATCH: Bug fixes (backward compatible)
β βββββ MINOR: New features (backward compatible)
βββββββ MAJOR: Breaking changes
Applying SemVer to APIs
MAJOR version (breaking changes):
- Increment when you make incompatible API changes
- Clients must update their code to use the new version
# Major version bump: v1 β v2
GET /v1/users β GET /v2/users
# Breaking change: removed field
# v1: {"id": 1, "name": "Alice", "phone": "..."}
# v2: {"id": 1, "name": "Alice"} β phone removed
MINOR version (new features):
- Increment when you add functionality in a backward-compatible manner
- Clients can continue using existing features without changes
# Minor version bump: v2.1 β v2.2
# New endpoint added
POST /v2/users/:id/preferences # New in v2.2
# New optional field added
# v2.1: {"id": 1, "name": "Alice"}
# v2.2: {"id": 1, "name": "Alice", "avatar": "..."} β optional, additive
PATCH version (bug fixes):
- Increment when you make backward-compatible bug fixes
- No API contract changes, only internal fixes
# Patch bump: v2.2.0 β v2.2.1
# Fixed: POST /users now returns 201 instead of 200 (was a bug)
# Fixed: Rate limit header was missing in some responses
SemVer in URL vs. Headers
Most APIs only expose the major version in the URL:
# URL shows major version
GET /v2/users
# Full version in response header
X-API-Version: 2.3.1
This keeps URLs simple while still communicating the full version.
Versioning Decision Tree
flowchart TD
A[Made a change to API] --> B{Does it remove or modify
existing behavior?}
B -->|Yes| C{Will existing clients break?}
B -->|No| D{Did you add new features?}
C -->|Yes| E[MAJOR version bump]
C -->|No| F[Review carefully
probably MINOR]
D -->|Yes| G[MINOR version bump]
D -->|No| H{Bug fix?}
H -->|Yes| I[PATCH version bump]
H -->|No| J[No version change
internal only]
style E fill:#ffcdd2
style G fill:#fff9c4
style I fill:#c8e6c9
style J fill:#e8f5e95. Deprecation: Announcing the End
Deprecation is a warning that a feature or version will be removed in the future. It’s a social contract with your API consumers.
What Deprecation Means
timeline
title API Version Lifecycle
section Active
v1 Released : Fully supported
v1 Active : Bug fixes, security patches
section Deprecated
v1 Deprecated : Warning issued
v1 Deprecated : Migration period (6-12 months)
section Sunset
v1 Sunset : Version removed
v1 Gone : Returns 410 GoneDeprecated = “This still works, but stop using it.” Sunset = “This will stop working on date X.”
How to Announce Deprecation
1. Response Headers
Include deprecation information in every response:
HTTP/1.1 200 OK
Deprecation: true
Sunset: Sat, 01 Jul 2027 00:00:00 GMT
Link: <https://api.example.com/v2/users>; rel="successor-version"
X-Deprecation-Notice: This version is deprecated. Migrate to v2 by July 1, 2027.
2. Documentation
Update your API documentation prominently:
## Version 1 (Deprecated)
> **Warning:** API v1 is deprecated and will be removed on July 1, 2027.
> Please migrate to [v2](/docs/v2) as soon as possible.
### Migration Guide
See our [v1 to v2 migration guide](/docs/migration/v1-to-v2).
3. Changelog Announcements
## 2026-01-15: v1 Deprecation Notice
API v1 is now deprecated. Key dates:
- **Today**: v1 marked deprecated
- **April 1, 2027**: v1 enters sunset period (reduced SLA)
- **July 1, 2027**: v1 removed
### What You Need to Do
1. Review breaking changes in the [migration guide](/docs/migration)
2. Update your integration to use v2 endpoints
3. Test in staging before July 1, 2027
### Breaking Changes in v2
- `phone` field removed from User response
- `created` renamed to `created_at`
- Error response format changed
Deprecation Best Practices
| Practice | Minimum | Recommended |
|---|---|---|
| Notice period | 3 months | 6-12 months |
| Header warnings | On deprecated endpoints | On all responses |
| Email notification | Once | Monthly reminders |
| Migration guide | Basic | Step-by-step with examples |
| Sunset warnings | At deprecation | Progressive (30, 14, 7 days) |
Deprecating Individual Fields
You don’t always deprecate entire versions. Sometimes just a field:
{
"id": 123,
"name": "Alice",
"phone": "+1-555-1234",
"phone_deprecated": "Use 'phones' array instead. Removed in v2.5",
"phones": [
{"type": "mobile", "number": "+1-555-1234"}
]
}
Or in OpenAPI specification:
properties:
phone:
type: string
deprecated: true
description: "Deprecated: Use 'phones' array instead"
6. Sunset and Removal
Sunset is the final phaseβthe version will stop working.
Sunset Timeline
gantt
title API v1 Lifecycle
dateFormat YYYY-MM-DD
section Development
v1 Development :2024-01-01, 2024-06-01
section Active
v1 Active :2024-06-01, 2025-06-01
section Deprecated
v1 Deprecated :2025-06-01, 2026-06-01
section Sunset
v1 Sunset Period :2026-06-01, 2026-09-01
v1 Removed :milestone, 2026-09-01, 0dThe Sunset HTTP Header
RFC 8594 defines the Sunset header:
HTTP/1.1 200 OK
Sunset: Sat, 01 Jul 2027 00:00:00 GMT
This tells clients exactly when the endpoint will stop working.
What Happens at Sunset
Option 1: Return 410 Gone
GET /v1/users
HTTP/1.1 410 Gone
Content-Type: application/json
{
"error": {
"code": "API_VERSION_SUNSET",
"message": "API v1 has been removed. Please use v2.",
"documentation_url": "https://api.example.com/docs/migration",
"sunset_date": "2027-07-01"
}
}
Option 2: Redirect to new version
GET /v1/users
HTTP/1.1 301 Moved Permanently
Location: https://api.example.com/v2/users
Warning: Redirects can mask breaking changes. Use carefully.
Option 3: Proxy with warnings
Temporarily proxy old requests to new version while adding warning headers. This is a grace period approach.
Sunset Checklist
Before removing a version:
- Deprecation notice sent 6+ months ago
- Migration guide published
- Multiple reminder emails sent
- Usage metrics show minimal traffic
- Support team prepared for questions
- 410 Gone response configured
- Monitoring for 410 responses active
7. API Lifecycle Stages
Every API version goes through distinct stages.
The Complete Lifecycle
stateDiagram-v2
[*] --> Preview
Preview --> Active: GA Release
Active --> Deprecated: New version released
Deprecated --> Sunset: Migration period ends
Sunset --> [*]: Version removed
Active --> Active: Bug fixes, minor updates
Deprecated --> Deprecated: Critical fixes onlyStage Definitions
Preview/Beta
- Not for production use
- May change without notice
- Limited SLA or no SLA
- Gathering feedback
X-API-Status: preview
X-API-Warning: This API is in preview and may change without notice
Active (GA)
- Production-ready
- Full SLA applies
- Breaking changes require new major version
- Bug fixes and minor features added
X-API-Status: active
X-API-Version: 2.3.1
Deprecated
- Still functional
- No new features
- Security fixes only
- Migration warnings active
X-API-Status: deprecated
Deprecation: true
Sunset: Sat, 01 Jul 2027 00:00:00 GMT
Sunset
- Reduced or no SLA
- May be intermittently unavailable
- Aggressive warnings
- Counting down to removal
X-API-Status: sunset
Sunset: Sat, 01 Jul 2027 00:00:00 GMT
X-Days-Until-Removal: 14
Lifecycle Duration Guidelines
| Stage | Typical Duration | Support Level |
|---|---|---|
| Preview | 1-6 months | Best effort |
| Active | 1-3 years | Full SLA |
| Deprecated | 6-12 months | Security only |
| Sunset | 1-3 months | None |
Managing Multiple Active Versions
graph TD
A[API Gateway] --> B{Version?}
B -->|v1| C[v1 Service
Deprecated]
B -->|v2| D[v2 Service
Active]
B -->|v3-preview| E[v3 Service
Preview]
C --> F[(v1 Database)]
D --> G[(v2 Database)]
E --> G
style C fill:#fff9c4
style D fill:#c8e6c9
style E fill:#e3f2fdBest practice: Keep at most 2-3 versions active simultaneously.
Lifecycle Communication Plan
| Event | Who to Notify | How |
|---|---|---|
| Preview released | Interested developers | Blog, changelog |
| GA released | All API users | Email, blog, changelog |
| Deprecation | All v(N) users | Email, headers, dashboard |
| Sunset warning (30 days) | Active v(N) users | Email, headers |
| Sunset warning (7 days) | Active v(N) users | Email, urgent header |
| Version removed | n/a | 410 response |
What’s Next
This guide covered the fundamentals of API versioning and lifecycle managementβenough to make informed decisions about evolving your APIs.
For deeper topics like:
- Managing complex migrations across many clients
- Communication strategies for breaking changes
- Measuring the impact of API changes
- Organizational governance for API lifecycle
- Tooling for automated version management
See the upcoming course: Governance of the API Lifecycle.
Related Vocabulary Terms
Deepen your understanding:
- API Versioning - Overview of versioning strategies
- Breaking Change - What makes a change breaking
- Deprecation - Marking features for removal
- Deprecation Policy - Formal deprecation rules
- Semantic Versioning - The MAJOR.MINOR.PATCH system
- API Lifecycle - Stages from design to sunset
- Sunset - End-of-life for API versions
- URI Path Versioning - Version in the URL
- Header-Based Versioning - Version in headers