Deprecate an API Without Angering Users
31 Aug 2026Deprecating an API badly means angry issues, silent breakage, and support tickets from people you didn’t know were even calling the endpoint. Deprecating it well means most callers migrate before you ever have to flip the switch.
An endpoint only ever moves through a few states on its way out, whether or not the caller pays attention:
stateDiagram-v2
[*] --> Active
Active --> Deprecated : timeline announced
Deprecated --> Migrated : caller switches to v2
Deprecated --> StillOnOldPath : no action taken
StillOnOldPath --> Sunset : sunset date reached
Sunset --> Gone : 410 returned with replacement link
Migrated --> [*]
Gone --> [*]
1. Announce with a timeline, not a date
“This endpoint is deprecated” with no date is worse than nothing — nobody knows if they have a week or a year, so nobody moves. Give a real window: 90 days minimum for anything with external callers, 30 days is reasonable for an internal-only API where you control every caller.
Deprecated: 2026-09-16
Sunset: 2026-12-15
Replacement: POST /v2/orders
Put this in the changelog, the API docs, and a response header on every call to the old endpoint. Three channels, because you don’t know which one the caller’s team actually reads.
2. Tell callers in the response itself
A deprecation notice nobody sees might as well not exist. Add a header to every response from the deprecated endpoint:
Deprecation: true
Sunset: Tue, 15 Dec 2026 00:00:00 GMT
Link: <https://api.example.com/v2/orders>; rel="successor-version"
Deprecation and Sunset are real, semi-standardized HTTP headers — tooling and API monitors already look for them. If you log requests, grep for Sunset response headers in your own client code and check whether it fires. If you can, log every caller still hitting the old route by API key, so you can email the five accounts generating 90% of the remaining traffic directly instead of waiting for a mass email to work.
3. Ship the migration path before you deprecate
Never deprecate an endpoint before its replacement exists, is documented, and has at least one real caller using it in production. “We’re deprecating X, Y is coming soon” tells callers to freeze on the old version, which is the opposite of what you want.
Write a migration guide that’s a diff, not a redesign explainer:
- POST /v1/orders {"customer": "123", "amount": 500}
+ POST /v2/orders {"customer_id": "123", "amount_cents": 50000}
Show the exact field renames and type changes. Don’t make people reverse-engineer the new schema from a reference doc.
4. Keep both versions alive, actually alive
A deprecated endpoint that silently degrades — slower, buggier, less monitored — punishes people for not migrating yet on your timeline instead of theirs. Keep it fully functional until the sunset date. If you can’t afford to maintain both, that’s a sign your sunset window is too long for your team’s capacity, not a reason to let the old one rot.
5. Sunset in stages, not a hard cutoff
On the sunset date, don’t jump straight to a 404. Return 410 Gone with a body pointing at the replacement, for at least a few weeks after cutoff:
{
"error": "endpoint_removed",
"message": "This endpoint was removed on 2026-12-15. Use POST /v2/orders.",
"docs": "https://api.example.com/docs/v2/orders"
}
A caller who gets a clear error with the fix in it will patch and move on. A caller who gets a bare 404 or a connection failure will file a ticket, or worse, assume your service is down.
The gotcha
The callers who break loudest are never your active integrations — they’re the ones who integrated once, it worked, and nobody has touched that code in two years. Those are exactly the ones least likely to read your changelog. If a deprecated endpoint still has meaningful traffic a week before sunset, push the date. A missed deadline costs you nothing. A broken integration at a customer who didn’t see it coming costs you the account.