Deprecation as a Process
Series
API Design Mastery← Part 9
SDK Ergonomics
Shipping a new API version is the easy part. Retiring the old one is where most teams fail. You announce the sunset date, a handful of teams migrate, and then nothing happens — until the day before cutover when you discover three critical internal services and two large enterprise customers are still on the old version. Deprecation is not a date on a calendar. It is a process: continuous signaling, per-client measurement, proactive outreach, and a dual-stack operation period that keeps the lights on while everyone migrates.
Why Deprecation Fails
The typical deprecation goes:
- Ship v2.
- Post a blog post.
- Set a sunset date 6 months out.
- Forget about it.
- Panic 2 weeks before the date.
The failure mode is that clients do not read blog posts. Automated systems never read blog posts. The signal must be in the API response itself, visible to every caller on every request.
The Sunset Header (RFC 8594)
RFC 8594 defines the Sunset header — a machine-readable signal that a resource or API version will become unavailable at a specific time:
HTTP/1.1 200 OK
Content-Type: application/json
Deprecation: true
Sunset: Wed, 01 Apr 2026 00:00:00 GMT
Link: <https://api.example.com/v2/orders/42>; rel="successor-version",
<https://api.example.com/docs/migration/v1-to-v2>; rel="deprecation"Fields:
Deprecation: true(or a date) — signals that this endpoint is deprecated.Sunset— the RFC 7231 date after which the endpoint will stop responding.Linkwithrel="successor-version"— the replacement URL.Linkwithrel="deprecation"— the migration guide URL.
Add these headers from the moment v2 is generally available — not after you set the sunset date. Every response from a deprecated endpoint is an opportunity to prompt migration.
Deprecation Timeline
A minimum dual-stack window of 6 months for external APIs; 3 months for internal services with known clients. Enterprise contracts may require 12 months — account for this in your SDK and API agreements.
Per-Client Usage Tracking
Deprecation outreach is impossible without data. Track v1 usage per API key:
SELECT
api_key,
tenant_id,
COUNT(*) AS request_count,
MAX(requested_at) AS last_seen_at
FROM api_access_logs
WHERE api_version = 'v1'
AND requested_at > NOW() - INTERVAL '30 days'
GROUP BY api_key, tenant_id
ORDER BY request_count DESC;Pipe this into a weekly report. Sort by request_count DESC to prioritize outreach. The long tail of low-volume callers is usually the hardest to find — they have automated scripts running on cron jobs that no one remembers.
Expose usage data to customers through your dashboard so they can self-serve the answer to "are we still on v1?"
Migration Guides
A migration guide that just lists breaking changes is not useful. A migration guide that gives a diff for every change, explains the reason, and links to working examples is.
Structure:
# Migrating from v1 to v2
## Overview
v2 changes X, Y, and Z. Most changes require minimal code modification.
Estimated migration time: 2–4 hours for a typical integration.
## Breaking Changes
### 1. Orders endpoint response shape
The `customer_name` field has been split into `customer.firstName` and `customer.lastName`.
**v1 response:**
{
"id": "ord_123",
"customer_name": "Jane Smith"
}
**v2 response:**
{
"id": "ord_123",
"customer": {
"firstName": "Jane",
"lastName": "Smith"
}
}
**Migration:** Update any code that reads `customer_name` to use
`customer.firstName + ' ' + customer.lastName`.For SDKs, provide a codemod:
npx @example/api-codemod v1-to-v2 ./srcCodemods handle the mechanical changes; migration guides explain the conceptual ones.
Dual-Stack Operation
During the deprecation window, both versions must work. Route at the gateway:
Do not run dual-stack in application code. Keep version routing at the gateway or load balancer. This lets you decommission v1 with a config change rather than a code deploy.
The Final Cutover
On sunset day, v1 endpoints should return 410 Gone (not 404, not 503):
HTTP/1.1 410 Gone
Content-Type: application/problem+json
{
"type": "https://api.example.com/problems/version-sunset",
"title": "API Version Retired",
"status": 410,
"detail": "API v1 was retired on 2026-04-01. Please migrate to v2.",
"migrationGuide": "https://api.example.com/docs/migration/v1-to-v2",
"v2BaseUrl": "https://api.example.com/v2"
}Keep the 410 response active for 3 months after sunset. Do not immediately remove the route — clients that missed the sunset will get a clear, actionable error rather than a cryptic DNS failure.
Automating Deprecation Signals
Build deprecation signaling into your API framework, not your handlers:
# Framework middleware — pseudocode
class DeprecationMiddleware:
def __init__(self, app, deprecated_versions: dict):
# {"v1": {"sunset": "2026-04-01", "successor": "/v2"}}
self.deprecated = deprecated_versions
def __call__(self, request, response):
version = extract_version(request)
if version in self.deprecated:
info = self.deprecated[version]
response.headers["Deprecation"] = "true"
response.headers["Sunset"] = info["sunset"]
response.headers["Link"] = (
f'<{info["successor"]}>; rel="successor-version"'
)
return responseRegister deprecated versions in configuration, not code. When you add a new version, the middleware automatically applies deprecation headers to all old versions.
Key Takeaways
- Send
DeprecationandSunsetheaders from the day v2 launches — not from the day you set the sunset date. Every response is a migration prompt. - Track v1 usage per API key and tenant in your access logs; generate weekly reports sorted by call volume; reach out proactively to the top consumers.
- Maintain a 6-month minimum dual-stack window for external APIs; enforce routing at the gateway layer so decommissioning is a config change, not a deploy.
- Write migration guides as diffs, not change lists — show exactly what code to change, with before and after examples for every breaking change.
- Return 410 Gone (not 404 or 503) after sunset, with a body pointing to the migration guide, and keep that 410 active for at least 3 months.
- Automate deprecation header injection in framework middleware keyed to a version registry so you never have to touch individual handlers to signal a sunset.
Series
API Design Mastery← Part 9
SDK Ergonomics