Capabilities/Reliability & Infra

API Versioning

Backward-compatible API evolution with URL, header, and content negotiation strategies

APIversioningRESTbackward-compatibilitydeprecation

Learning lens

Start with judgment, not a universal prescription.

This page is a learning model for understanding the capability. It does not claim exact production internals, exact costs, or an infinite-scale architecture.

Evidence

Conceptual pattern

0->1 default

Start with a durable queue, clear ownership, retries, and idempotency before building a larger event platform.

Cost drivers

retry amplificationqueue requestsworker runtimedelivery history and logs

Watch for breakpoints

  • -backlog grows during normal traffic
  • -manual recovery becomes frequent
  • -independent consumers need replay

Learn first

Concepts behind this architecture

The Problem

APIs are contracts with external consumers. Once published, changing a response shape, removing a field, or altering behavior can break thousands of integrations. Yet APIs must evolve — new features, performance improvements, and security fixes require changes. API versioning lets you introduce breaking changes without disrupting existing clients by maintaining multiple versions simultaneously. The challenge is choosing a versioning strategy, managing the lifecycle of each version, minimizing code duplication across versions, and eventually sunsetting old versions gracefully.

Architectural Approaches

URL Path Versioning

Include the version in the URL path: /api/v1/users, /api/v2/users. The most visible and widely used approach. Each major version is a distinct URL namespace.

Pros

  • +Most explicit and visible — version is obvious in every request
  • +Easy to route at the load balancer or gateway level
  • +Simple to cache (different URLs = different cache entries)
  • +Easy to document and test each version independently

Cons

  • −URL changes break bookmarks and hardcoded links
  • −Can lead to URL proliferation (/v1, /v2, /v3...)
  • −Philosophically impure — the version isn't part of the resource identity
  • −Harder to share common logic across versions without duplication

Header-Based Versioning

Version is specified in a custom HTTP header: X-API-Version: 2 or using the Accept header with content negotiation: Accept: application/vnd.myapi.v2+json. URLs remain stable across versions.

Pros

  • +Clean URLs — resource identity doesn't change across versions
  • +More RESTful — version is metadata, not part of the resource
  • +Can default to latest version if no header is provided
  • +Supports gradual migration — clients opt into new versions

Cons

  • −Version is hidden — harder to discover and debug
  • −Requires custom header handling in every client
  • −Load balancer routing based on headers is more complex
  • −Caching is trickier — must use Vary header

Query Parameter Versioning

Version as a query parameter: /api/users?version=2. Simple to implement and test. Less common in production APIs but useful for internal services.

Pros

  • +Easy to test — just change the query parameter
  • +No URL structure changes
  • +Simple to implement — just read a query param
  • +Works well for internal APIs and GraphQL

Cons

  • −Pollutes the query string with non-resource concerns
  • −Easy to forget — defaults can be surprising
  • −Caching must include the version parameter in the cache key
  • −Less conventional for public APIs

Architecture

Loading diagram...
API Versioning — High-level architecture

Key Components

Version Router

Extracts the requested API version from the URL path, header, or query parameter. Routes the request to the appropriate version handler. Returns an error if the requested version is unsupported or has been sunset. Adds version info to response headers.

Version Controllers

Thin adapter layers that translate between the versioned API contract and the shared business logic. v1 controller maps to/from the v1 request/response format, v2 controller maps to/from v2 format. Both call the same underlying service layer.

Response Transformer

Transforms the internal data representation to the version-specific response format. Can add/remove fields, rename keys, change data types, and restructure nested objects. Implemented as middleware or decorators on the response path.

Deprecation Manager

Tracks version lifecycle: active, deprecated, sunset. Adds Deprecation and Sunset headers to responses for deprecated versions. Sends notifications to API consumers using deprecated versions. Enforces sunset by returning 410 Gone after the sunset date.

API Documentation Generator

Generates version-specific API documentation (OpenAPI/Swagger). Each version has its own spec file. Shows deprecation notices and migration guides for deprecated versions. Auto-generated from code annotations or schema definitions.

Version Usage Metrics

Tracks API call volume per version, per client. Identifies which clients are still using deprecated versions. Provides data for sunset decisions — don’t sunset a version with significant traffic without reaching out to affected clients.

Data Model

Loading diagram...
API Versioning — Entity relationship diagram

Additive Changes vs Breaking Changes

Not every change requires a new API version. Understand what’s breaking and what’s not.

Non-breaking (additive) changes — safe to deploy without versioning:

  • Adding a new optional field to the response
  • Adding a new optional query parameter
  • Adding a new endpoint
  • Adding a new enum value (if clients handle unknown values)
  • Increasing rate limits
  • Adding new webhook event types

Breaking changes — require a new version:

  • Removing or renaming a field in the response
  • Changing a field’s data type (string → number)
  • Making a previously optional parameter required
  • Changing the URL structure
  • Removing an endpoint
  • Changing error response format
  • Changing authentication mechanism
  • Altering the semantics of an existing field

Best practice: Design APIs to be as additive as possible. Use nullable fields, optional parameters, and extensible enums from day one. This minimizes the frequency of breaking changes and the need for new major versions. Many successful APIs (Stripe, Twilio) have maintained backward compatibility within a single version for years through careful additive evolution.

The Stripe Date-Based Versioning Model

Stripe uses a unique versioning approach that’s become an industry model.

How it works:

  • Each API key is pinned to the version that was current when the key was created (e.g., 2024-01-15)
  • Breaking changes are released as new dated versions (2024-06-01)
  • Clients can override their pinned version per-request with a header: Stripe-Version: 2024-06-01
  • All changes between versions are documented with exact diffs

Implementation: Internally, Stripe maintains a chain of version transformers. The latest code always runs, and a series of backward-compatibility layers transform the response to match the requested version. When a client requests version 2024-01-15, the response passes through transformers for every version between 2024-01-15 and the current version — each transformer reverting one breaking change.

Benefits:

  • No URL changes — ever
  • Clients upgrade on their own schedule
  • Each breaking change is individually documented and revertible
  • New integrations automatically get the latest version

Drawbacks:

  • Complex transformer chain for old versions (technical debt)
  • Testing matrix grows with each version
  • Must maintain backward compatibility layers indefinitely (or until sunset)

Version Sunset Strategy

Old versions can’t live forever — they accumulate technical debt and security risk.

Sunset timeline:

  1. Announce deprecation: 6-12 months before sunset. Add Deprecation header to all responses. Send email to all API consumers using the deprecated version.
  2. Migration period: Provide a detailed migration guide with before/after examples. Offer office hours or support for complex migrations.
  3. Warning escalation: 3 months before sunset, start returning Warning headers. Log deprecated version usage and reach out to top consumers.
  4. Sunset: Return 410 Gone for all requests to the sunset version. Include the migration guide URL in the error response body.

Sunset headers (RFC 8594):

  • Deprecation: Sat, 01 Jan 2025 00:00:00 GMT
  • Sunset: Sat, 01 Jul 2025 00:00:00 GMT
  • Link: </docs/migration-v1-v2>; rel="deprecation"

Decision criteria for sunset:

  • Less than 1% of total API traffic on the old version
  • All major consumers have migrated (verify via usage metrics)
  • Security vulnerabilities in the old version that can’t be patched
  • Maintaining the old version blocks a critical feature or infrastructure change

Never sunset without notice: Even if traffic is near-zero, always announce. Someone’s production integration might only run monthly.

Real-World Examples

Stripe

Date-based versioning (e.g., 2024-06-01) with per-API-key pinning. Version override via Stripe-Version header. Backward-compatibility transformer chain. Extensive changelog documenting every change between versions.

GitHub

Date-based versioning for the REST API (2022-11-28). URL path versioning for GraphQL (/graphql). Deprecation notices in response headers. Preview features available via Accept header media type.

Twilio

Date-based URL path versioning (/2010-04-01/Accounts/…). Extremely long support windows — the 2010 API is still functional. New dated versions introduce breaking changes while old versions remain stable.

Google Cloud

Major version in the URL path (/v1, /v2). Follows a formal version lifecycle: preview → GA → deprecated → shutdown. Minimum 1-year deprecation notice for GA APIs. Version policy documented in the API Improvement Proposals (AIPs).

Architectural Tradeoffs

URL path vs header-based versioning

Pros

  • +URL: explicit, cacheable, easy routing at the gateway
  • +Header: clean URLs, more RESTful, version is metadata
  • +URL: most widely understood, best developer experience

Cons

  • −URL: changes resource identity, URL proliferation
  • −Header: hidden, harder to debug, requires Vary for caching
  • −Header: clients must set custom headers, easy to forget

Date-based vs integer-based version numbers

Pros

  • +Date: clear temporal context, natural for changelogs
  • +Integer: simpler, easier to compare (v2 > v1)
  • +Date: can have many micro-versions without confusion

Cons

  • −Date: longer identifiers, harder to remember
  • −Integer: fewer versions discourage breaking changes (good or bad)
  • −Date: may imply time-based support windows that aren't intended

Version-specific code vs transformer chain

Pros

  • +Specific code: clear separation, each version is self-contained
  • +Transformer: one codebase, backward compat as layers
  • +Transformer: less code duplication, easier bug fixes

Cons

  • −Specific code: duplication across versions, bug fixes needed in all
  • −Transformer: growing chain of transforms adds complexity
  • −Transformer: harder to understand the effective behavior per version

Learn API Versioning

Copy a learning context prompt for an AI assistant. It keeps assumptions visible and asks for tradeoffs, cost drivers, failure modes, and what to measure before adding complexity.

Explore More Capabilities