API Versioning and Lifecycle

Lifecycle Intermediate 18 min Jan 12, 2026

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:#ffcdd2

Option 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:#c8e6c9

Versioning 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 TypeNew Version Needed?
Remove a fieldYes
Rename a fieldYes
Change a field’s typeYes
Change a required field to optionalMaybe (context-dependent)
Change an optional field to requiredYes
Change error response formatYes
Add a new fieldNo
Add a new endpointNo
Add a new optional parameterNo
Fix a bugNo (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:#fff9c4

Example: 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:#e8f5e9

Example: 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.

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:#c8e6c9

Pros:

  • 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

StrategyVisibilityTestabilityCachingPurity
URL PathHighEasySimpleLow
HeaderLowHardComplexHigh
Query ParamMediumEasyProblematicLow

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:#e8f5e9

5. 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 Gone

Deprecated = “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

PracticeMinimumRecommended
Notice period3 months6-12 months
Header warningsOn deprecated endpointsOn all responses
Email notificationOnceMonthly reminders
Migration guideBasicStep-by-step with examples
Sunset warningsAt deprecationProgressive (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, 0d

The 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 only

Stage 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

StageTypical DurationSupport Level
Preview1-6 monthsBest effort
Active1-3 yearsFull SLA
Deprecated6-12 monthsSecurity only
Sunset1-3 monthsNone

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:#e3f2fd

Best practice: Keep at most 2-3 versions active simultaneously.

Lifecycle Communication Plan

EventWho to NotifyHow
Preview releasedInterested developersBlog, changelog
GA releasedAll API usersEmail, blog, changelog
DeprecationAll v(N) usersEmail, headers, dashboard
Sunset warning (30 days)Active v(N) usersEmail, headers
Sunset warning (7 days)Active v(N) usersEmail, urgent header
Version removedn/a410 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.


Deepen your understanding: