Deprecation
Date: 2026-08-17
Removing something people depend on, on a published timetable, with somewhere for them to go. The engineering is trivial; the whole difficulty is that announcement doesn’t cause migration, so a deprecation without measurement of actual usage is a guess about when it’s safe to delete.
Deprecation is formally marking a feature, endpoint or API as scheduled for removal, with a replacement and a date, before removing it.
The sequence
1 DECIDE what replaces it, and confirm the replacement is genuinely
sufficient. deprecating with no migration path is just breaking it
2 ANNOUNCE changelog, email, docs marked, and a date. name the date
on day one — "sometime next year" is not a deprecation
3 SIGNAL in the product itself, where the developer already is:
· a Deprecation / Sunset HTTP header on every response
· a warning in the SDK, once per process, not per call
· a console warning naming the replacement
· the docs page marked, with the alternative linked
4 MEASURE usage per consumer. this is the step that's skipped and
the only one that tells you whether any of the above worked
5 CHASE contact the remaining callers directly. by now it's a
short list, and it's the only thing that reliably works
6 BROWNOUT short scheduled outages before the real one
7 REMOVE on the announced date
Steps 4 and 5 are where deprecations succeed or fail. Everything before them is broadcast, and broadcast does not move people who have working code and other priorities.
Measurement is the whole game
Without per-consumer usage you have two bad options: delete and find out, or never delete.
endpoint GET /v1/orders deprecated 2026-02-01, removal 2026-08-01
calls/day consumers note
2026-02 412,000 38
2026-04 180,000 31 announcement moved the easy ones
2026-06 94,000 22 ← plateau. broadcast has done its work
2026-07 91,000 21 chase these 21 by name
2026-07-20 2,100 3 ← after direct contact
2026-07-28 0 0
The plateau at step 4 is the normal shape, and it’s the signal to switch from announcing to phoning. Log the consumer identity — API key, client ID, user agent — on every call to a deprecated thing, or step 5 is impossible.
Brownouts
Short, scheduled, announced outages of the deprecated thing before it’s removed for good — say an hour, then a day, in the final weeks.
They work because a team that hasn’t migrated usually doesn’t know they haven’t. The dependency is in a service nobody’s touched for a year, and the first genuine signal is a failure in an environment they’re watching. A brownout produces that signal at a time you chose, rather than on removal day.
Announce each one specifically. An unannounced brownout is an outage.
How long
Proportional to who’s affected and how much work it is:
| Consumers | Reasonable notice |
|---|---|
| Your own team, internal call | Days |
| Another team in the same organisation | Weeks to a quarter |
| External developers, small change | One or two quarters |
| External developers, real migration work | A year, sometimes more |
| Anything customers integrated with a contract | Whatever the contract says — check before announcing |
Never shorten an announced date. Extending is a courtesy; shortening destroys the credibility of every future deprecation you announce, and credibility is the thing that makes the next one cheaper.
The exception is security. A vulnerable endpoint gets removed on a security timetable, and that is a different communication, sent differently.
What makes it possible at all
- Version and date things from the start. Retrofitting a deprecation process onto an API with no versioning means the first removal is a breaking change — API Design, Backwards Compatibility
- Know who your consumers are. Authenticated API keys make this trivial; anonymous public endpoints make it impossible, which is an argument for authenticating even free endpoints
- A single changelog people actually read, in one place, permanently
- Deprecate in the type system where you can. A
@deprecatedannotation surfaces in the editor of everyone using it, which reaches people no email does — Static Analysis
Where it interacts
- The Strangler Pattern — a strangler migration ends in a deprecation, and the decommission date is the thing that keeps it from stalling
- Technical Debt — removing a feature is often cheaper than maintaining it, and deprecation is the mechanism. Features nobody uses are still debt
- Composable Commerce and Integration Patterns — you’re on the receiving end far more often than the sending end, so track vendors’ deprecation notices as work rather than as newsletters
- Dependency Management — the same process arriving from upstream, on their timetable rather than yours