What API Versioning and Lifecycle Management Mean at the Gateway
API versioning is the practice of evolving an API while keeping existing clients working. Lifecycle management is the set of rules and processes that control how versions are introduced, supported, deprecated, and retired. The gateway is a natural control point for this because it can: route requests to different upstream implementations based on version, enforce version-specific rules, and communicate lifecycle signals (warnings, deprecation, sunset) consistently to every client.
The goal is to make change predictable: clients know what to expect, and operators can measure adoption and safely remove old behavior.
Versioning Approaches: What Changes for Clients and the Gateway
1) URI/Path Versioning (e.g., /v1, /v2)
Example: GET /v1/orders/123 vs GET /v2/orders/123
- Pros: Very explicit; easy to route and observe; easy to document and test; works well with caches and tooling.
- Cons: Version becomes part of the resource URL; can encourage “big bang” versioning if teams treat versions as separate products.
- Gateway implications: Simple routing rules based on path prefix; easy to run multiple versions side-by-side.
2) Header-Based Versioning (Custom Header)
Example: X-API-Version: 2 while keeping the same path /orders/123
- Pros: Keeps URLs stable; can be introduced without changing links.
- Cons: Less visible; some clients/proxies make header management harder; caching must vary by header to avoid mixing versions.
- Gateway implications: Routing rules inspect headers; you must ensure caches and observability include the version header as a dimension.
3) Content Negotiation (Accept Header / Media Types)
Example: Accept: application/vnd.acme.orders+json;version=2
Continue in our app.
You can listen to the audiobook with the screen off, receive a free certificate for this course, and also have access to 5,000 other free online courses.
Or continue reading below...Download the app
- Pros: Aligns with HTTP semantics; can support multiple representations; good for evolving response formats.
- Cons: More complex; clients must set correct
Accept; debugging can be harder; caching must vary byAccept. - Gateway implications: Routing rules parse
Accept(or map it to an internal version); you must ensure correctVary: Acceptbehavior if caching is involved.
Compatibility Expectations: What “Safe Evolution” Looks Like
Before you choose a versioning strategy, define compatibility rules. A practical set of expectations:
- Non-breaking changes (should not require a new major version): adding optional fields, adding new endpoints, adding new enum values (if clients are tolerant), adding optional query parameters, expanding rate limits or performance improvements.
- Breaking changes (typically require a new major version): removing or renaming fields, changing field types, changing required fields, changing error formats, changing semantics (same request produces meaningfully different result), removing endpoints, changing id formats, tightening validation in a way that rejects previously valid requests.
- Client tolerance policy: decide whether clients must ignore unknown fields and unknown enum values. If you cannot guarantee that, treat those changes as potentially breaking and plan accordingly.
The gateway helps enforce these expectations by keeping old and new behaviors isolated and by making version selection explicit and testable.
Routing Two Versions to Different Upstreams
A common pattern is to keep v1 implemented by one upstream service (or one deployment) and v2 by another. The gateway becomes the switchboard.
Step-by-step: Path-based routing (/v1 and /v2)
- Step 1: Define two upstream targets (for example,
orders-v1andorders-v2). - Step 2: Add routes that match the version prefix and forward to the corresponding upstream.
- Step 3: Optionally rewrite the path so upstreams don’t need to know about
/v1or/v2(depends on your service design). - Step 4: Add version-specific observability tags/labels so you can measure usage per version.
# Pseudocode (gateway-agnostic) illustrating intent
routes:
- name: orders-v1
match:
path_prefix: /v1/orders
upstream: http://orders-v1.internal
rewrite:
strip_prefix: /v1
tags:
api_version: v1
- name: orders-v2
match:
path_prefix: /v2/orders
upstream: http://orders-v2.internal
rewrite:
strip_prefix: /v2
tags:
api_version: v2Step-by-step: Header-based routing (X-API-Version)
- Step 1: Keep a stable public path (e.g.,
/orders). - Step 2: Route based on
X-API-Version. - Step 3: Define a default version when the header is missing (be explicit; don’t guess silently).
- Step 4: Ensure metrics/logs include the resolved version.
# Pseudocode
routes:
- name: orders-default
match:
path_prefix: /orders
header_absent: X-API-Version
upstream: http://orders-v1.internal
tags:
api_version: v1
- name: orders-v2
match:
path_prefix: /orders
headers:
X-API-Version: "2"
upstream: http://orders-v2.internal
tags:
api_version: v2Step-by-step: Content negotiation routing (Accept)
- Step 1: Decide the media types you will support (e.g.,
application/vnd.acme.orders+json;version=1and...;version=2). - Step 2: Route based on pattern matching in
Accept. - Step 3: If the client sends an unsupported version, return a clear 406 response with supported options.
# Pseudocode
routes:
- name: orders-v2
match:
path_prefix: /orders
header_contains:
Accept: "version=2"
upstream: http://orders-v2.internal
tags:
api_version: v2
- name: orders-v1
match:
path_prefix: /orders
header_contains:
Accept: "version=1"
upstream: http://orders-v1.internal
tags:
api_version: v1
- name: orders-unsupported
match:
path_prefix: /orders
respond:
status: 406
body: "Unsupported API version. Use Accept: application/vnd.acme.orders+json;version=1 or version=2"Deprecating Routes: Communicating Change Through Gateway Headers
Deprecation should be visible to clients before anything breaks. The gateway can add standardized headers to responses for a deprecated version or endpoint.
Common lifecycle headers and signals
- Deprecation:
Deprecation: true(signals the resource is deprecated). - Sunset:
Sunset: Wed, 01 Oct 2026 00:00:00 GMT(the date after which the version may be unavailable). - Link to policy/migration guide:
Link: <https://api.example.com/docs/migrate-to-v2>; rel="deprecation" - Warning:
Warning: 299 - "v1 is deprecated; migrate to v2 before 2026-10-01"
Step-by-step: Add deprecation headers only for v1
- Step 1: Identify which routes are deprecated (entire version vs specific endpoints).
- Step 2: Configure a response policy on those routes to inject headers.
- Step 3: Ensure headers are present on both success and error responses so clients see them consistently.
# Pseudocode
routes:
- name: orders-v1
match:
path_prefix: /v1/orders
upstream: http://orders-v1.internal
response_headers:
add:
Deprecation: "true"
Sunset: "Wed, 01 Oct 2026 00:00:00 GMT"
Link: "<https://api.example.com/docs/migrate-to-v2>; rel=\"deprecation\""
Warning: "299 - \"API v1 is deprecated; migrate to v2 before 2026-10-01\""Managing Breaking Changes Without Surprises
Use “expand then contract” where possible
When you can, avoid breaking changes by first expanding the API (add new fields/endpoints) and only later contracting (remove old fields). The gateway helps by keeping old and new routes available during the transition window.
When a breaking change is unavoidable
- Introduce a new major version: keep v1 behavior frozen; implement v2 with the breaking change.
- Prevent accidental drift: treat v1 as a compatibility contract. If v1 must receive bug fixes, ensure they don’t change response shape or semantics.
- Fail clearly on invalid version selection: if a client requests v3 that doesn’t exist, return a deterministic error (not a default that hides the mistake).
- Consider “compatibility shims” carefully: if you must map v1 requests to v2 upstreams, do it explicitly and test it. Shims can reduce cost but can also hide semantic mismatches.
Keeping Documentation Aligned With Gateway Behavior
Versioning fails when docs say one thing and the gateway routes another. Treat the gateway configuration as a source of truth for what is actually reachable.
Practical alignment techniques
- Versioned API specs: maintain separate OpenAPI documents per version (e.g.,
openapi-v1.yaml,openapi-v2.yaml) and publish them under versioned URLs. - Document the version selection mechanism: if you use headers or content negotiation, show exact request examples and defaults.
- Document lifecycle dates: include deprecation and sunset dates in docs and ensure they match the headers the gateway emits.
- Change log per version: list breaking changes and migration steps; keep it short and actionable.
Testing Version-Specific Routing Rules
Because the gateway decides where traffic goes, you should test routing rules as part of your release process. Focus on: correct version detection, correct upstream selection, correct lifecycle headers, and correct error behavior for unsupported versions.
Step-by-step: Build a minimal routing test suite
- Step 1: Define test cases for each version selection method you support (path/header/Accept).
- Step 2: Verify upstream selection by calling endpoints that return a version marker (e.g., response header
X-Upstream-Versionfrom each upstream in non-production) or by checking gateway logs/metrics tags. - Step 3: Verify lifecycle headers appear only where intended (e.g., v1 responses include
Sunset, v2 does not). - Step 4: Verify negative cases: missing/invalid version should return the expected status and message.
- Step 5: Run tests in CI against a deployed gateway config (or a containerized gateway) before promoting config to production.
# Example HTTP-level tests (conceptual)
# Path versioning routes to v1
curl -i https://api.example.com/v1/orders/123
# Expect: 200, Deprecation/Sunset headers present, and upstream marker indicates v1
# Path versioning routes to v2
curl -i https://api.example.com/v2/orders/123
# Expect: 200, no Deprecation/Sunset headers, upstream marker indicates v2
# Header versioning selects v2
curl -i https://api.example.com/orders/123 -H "X-API-Version: 2"
# Expect: 200 from v2
# Unsupported version
curl -i https://api.example.com/orders/123 -H "X-API-Version: 99"
# Expect: 4xx with clear messageConcrete Deprecation Workflow (Announce, Dual-Run, Monitor, Retire)
1) Announce
- Set dates: choose a deprecation date (when warnings start) and a sunset date (when access ends).
- Update gateway responses: add
Deprecation,Sunset,Warning, and aLinkto migration docs for the deprecated version/routes. - Publish migration guidance: document request/response differences and provide side-by-side examples for v1 vs v2.
2) Dual-run (support v1 and v2 simultaneously)
- Keep both routes active: route v1 to the v1 upstream and v2 to the v2 upstream (or use a carefully tested shim).
- Freeze v1 contract: avoid adding new features to v1; only critical fixes that preserve behavior.
- Optionally add “soft nudges”: keep warnings in headers; avoid changing payloads solely to advertise deprecation.
3) Monitor usage
- Track per-version traffic: requests, unique API keys/clients, and top endpoints still used on v1.
- Identify lagging clients: use gateway analytics to find who is still calling v1 close to sunset.
- Watch error rates: spikes on v2 may indicate migration issues; fix v2 or improve docs before retirement.
4) Retire
- Progressive enforcement: optionally move from warnings to stricter responses near sunset (for example, returning 410 for specific endpoints after the sunset date).
- Disable v1 routes: remove or deactivate v1 routing rules at the gateway on the retirement date.
- Return a clear retirement response: if you keep a stub route, respond with
410 Goneand a link to migration docs rather than a generic 404.
# Pseudocode: after sunset, return 410 for v1
routes:
- name: orders-v1-retired
match:
path_prefix: /v1/orders
respond:
status: 410
headers:
Link: "<https://api.example.com/docs/migrate-to-v2>; rel=\"alternate\""
body: "API v1 has been retired. Please migrate to v2."