Evolutiondeprecationsunsetcommunicationmigration guide

Deprecation as a Process, Not a Label

Marking something deprecated changes nothing; deprecation is a campaign with artifacts — announcement, migration guide, machine-readable signals, telemetry, a deadline someone will enforce — and a finish line. A deprecation nobody plans to complete is just an apology in advance.

Follow the failure

Frame the contract

API design starts with a consumer, a design question and a guarantee — never with a URL.

Design question
When this contract element must go away, how does every affected consumer find out, migrate, and confirm — before the deadline does it for them?
Consumers
The consumers being asked to do work on your schedule: the attentive ones who read changelogs, the busy ones who see only IDE strikethroughs and response headers, and the absent ones — unstaffed integrations that will not notice anything short of an error.
The promise
A run deprecation guarantees that removal day is an anticlimax: everyone affected knew, had a documented path and enough time, and the stragglers were found by telemetry and contacted — not discovered by the outage.
RequirementConsumersResource ModelStyleContractValidationAuthorizationErrorsIdempotencyPaginationVersioningObservabilityEvolutionTrade-offs

The artifacts of a real deprecation

Deprecation fails as a label because a label is passive: it waits to be noticed, and the consumers most at risk — the unstaffed integration, the annual batch job — are precisely the ones not looking. A real deprecation is an active campaign, and it is made of concrete artifacts. If any one below is missing, you do not have a deprecation; you have a wish.

The deadline deserves special honesty. A deprecation without a date is a permanent apology — consumers correctly learn that "deprecated" means "still works, ignore it", and that lesson poisons every *future* deprecation you announce. Conversely, a date nobody intends to enforce is worse than none: the first quietly extended sunset teaches the same lesson with extra steps. Announce dates you will keep, and keep them (with the escape hatch used consciously, not habitually).

  • The announcement — what is deprecated, why (consumers migrate faster when the reason is real), what replaces it, and the sunset date. Delivered where consumers actually look: changelog, email to registered key owners, dashboard banner.
  • The migration guide — old call → new call, field-by-field mapping, the semantic differences that a mechanical translation would miss, and worked before/after examples. The guide's quality is the single biggest lever on migration speed.
  • Machine-readable signals — spec annotations (deprecated: true in OpenAPI: Describing the Contract, Not Designing It), SDK @deprecated markers that produce IDE strikethroughs and build warnings, and response headers for consumers who read neither.
  • Telemetry — per-consumer usage of the deprecated element, so the campaign has a burn-down and the final chase has a call list (see Consumer-Driven Evolution: Telemetry Before Breakage).
  • The deadline and its owner — a date, a named owner who will enforce it, and an escalation path for consumers who cannot make it.

Signals in the wire: headers old clients cannot help receiving

Documentation reaches people who read documentation. The wire reaches everyone still calling — which is why HTTP grew standard fields for exactly this: the Deprecation header (RFC 9745) marks an endpoint deprecated and since when; the Sunset header (RFC 8594) names the date it stops working; a Link with rel="deprecation" points at the migration guide. Well-built clients and gateways can log, alert or dashboard on these automatically — an SDK that surfaces a one-line warning when it first sees a Deprecation header converts wire metadata into a developer-visible nudge for free.

Signals escalate as the deadline approaches, and the last escalation before the end is the brownout: deliberately failing the deprecated surface for short scheduled windows — 5 minutes of 410 Gone with a pointer to the guide, announced in advance — weeks before the permanent sunset. It sounds hostile and is the opposite: a brownout converts a future permanent outage into a present recoverable blip, at a moment when the consumer's engineer is awake and the rollback is "wait five minutes". GitHub used exactly this for its authentication deprecations because years of polite headers had not moved the long tail.

A deprecated endpoint that tells every caller, every call
Request
GET /v1/reports/weekly HTTP/1.1
Authorization: Bearer <token>
Accept: application/json
Response
HTTP/1.1 200 OK
Deprecation: @1767139200
Sunset: Tue, 30 Jun 2026 00:00:00 GMT
Link: <https://api.example.com/docs/migrate-reports>; rel="deprecation"
Content-Type: application/json

{ "report": { … } }

· still works — the response is complete and correct
· every intermediary, SDK and log line can see the clock ticking

The long tail, and finishing

Every deprecation curve has the same shape: attentive consumers migrate in the first weeks, the majority follow under reminder pressure, and then the curve flattens into the long tail — the unstaffed, the unaware, the deliberately waiting. The tail is where deprecations go to die, and it is handled with telemetry plus escalating personal contact: the burn-down names each remaining consumer (see API Ownership and the Catalog for why you can), and the campaign shifts from broadcast to individual outreach — email the key owner, file the ticket against the internal team, call the account manager for the big partner.

Then someone has to decide the endgame, and the options are finite: enforce the date (break the remaining tail, with brownouts as the final warning); extend for everyone (spends your deadline credibility — do it at most once, loudly); extend for named consumers via temporary per-key exemptions (preserves the public deadline, adds an exemption list someone must own and shrink); or accept permanence — conclude the removal is not worth the last consumers' breakage and cancel the deprecation honestly. The only unacceptable option is the default one: silence, where the date passes, nothing happens, and every consumer quietly recalibrates what your deadlines mean.

Endgame options when the tail will not move
OptionWhat it costsWhen it is right
Enforce the date (brownouts, then off)Breaks the remaining tail — a chosen, communicated breakageTail is small, contacted, and the maintenance burden is real
Extend for everyoneDeadline credibility — every future date is now a suggestionA major consumer class genuinely cannot make it (e.g. app-store review delays) and you say so publicly
Per-consumer exemptionsAn exemption list to own, shrink, and eventually enforce anywayOne or two named consumers with contracts or timelines that warrant it
Cancel the deprecationThe maintenance you hoped to shed — kept honestly, foreverThe removal's benefit turns out smaller than the tail's breakage; saying so beats a zombie deprecation

Key points

  • Deprecation is a campaign with artifacts — announcement, migration guide, machine signals, telemetry, owned deadline — not a docs label.
  • A deprecation without a date is a permanent apology; a date never enforced teaches consumers to ignore all your dates.
  • Put the signal in the wire: Deprecation, Sunset and Link headers reach consumers who will never read a changelog.
  • Brownouts convert a future permanent outage into a present recoverable blip — the kindest hostile act in the toolbox.
  • The long tail is handled by name, not by broadcast: telemetry produces a call list, and the campaign ends with individual outreach.
  • The endgame is a decision — enforce, extend loudly, exempt narrowly, or cancel honestly. Silence is the only wrong answer.

Follow the failure

How the contract fails or gets misused, hop by hop — and what it costs when it completes.

  1. 1
    Team → docs: adds "(deprecated)" to a field description; no date, no guide, no telemetry. The campaign is considered launched.
  2. 2
    Consumers → API: attentive ones ask "until when? migrate to what?" and get "TBD"; everyone else never sees the label at all.
  3. 3
    Team → time: two years pass; the deprecated surface still carries traffic; new consumers integrate against it because it still works and still appears in examples.
  4. 4
    Team → removal: a platform migration finally forces the issue; the surface is removed with a changelog entry as the only notice.
  5. 5
    Consumers → outage: the long tail — which nobody ever measured or contacted — discovers the deprecation as a production incident, and the postmortem finds the 2024 docs label offered as due process.
What breaks
  • Consumers in the tail take outages that a run campaign would have prevented — and they are disproportionately the least-staffed, slowest-to-recover integrations.
  • Deadline credibility is a shared org asset: one silent extension or one surprise enforcement damages every team's future migrations, not just this one.
  • Un-finished deprecations accumulate as permanent surface: the API carries a graveyard of "deprecated" elements that still need tests, patches and support answers.

Design, observe, evolve

A contract decision is incomplete until you know how you would notice it failing and how it changes later.

Design the contract
  • • Do not announce a deprecation until the replacement is shipped, the guide is written, the telemetry exists and a date with a named owner is set — the label goes on last, not first.
  • • Emit machine-readable signals at every layer: spec annotation, SDK `@deprecated`, and `Deprecation`/`Sunset`/`Link` response headers, so the notice travels with the traffic.
  • • Plan the escalation ladder into the schedule up front: broadcast → targeted reminders → individual outreach → brownouts → sunset, each stage dated.
  • • Block new adoption of deprecated surfaces early — new API keys get denied access to sunsetting endpoints, so the tail only ever shrinks.
Observe in production
  • • The per-consumer burn-down is the campaign dashboard; a flat week means the current channel has saturated and the next escalation stage is due.
  • • Watch for *new* consumers appearing on a deprecated surface — the clearest sign that the deprecation is invisible where developers actually discover the API.
  • • During brownouts, measure which keys hit the failing surface: that is the exact, final list of consumers the sunset will break, delivered while it is still recoverable.
Evolve without breaking
  • • Codify the campaign into policy — notice periods per surface stability tier, standard headers, standard guide template — so each deprecation stops being a bespoke negotiation (see [[api-documentation]]).
  • • Deprecation infrastructure (telemetry, exemption machinery, brownout tooling) is reusable capital: the tenth deprecation should cost a tenth of the first.
  • • Feed the pain backward into design: surfaces that were hardest to deprecate teach you which guarantees to avoid making casually next time.
What it costs
  • • A real campaign is expensive — guide-writing, telemetry, outreach, brownout engineering — which is exactly why the honest alternative of keeping the surface forever is sometimes the right call.
  • • Brownouts and enforced deadlines spend goodwill with the consumers they hit, even when handled well; the alternative spends more, later, without consent.
  • • Long notice periods (kind to mobile fleets and annual jobs) mean the provider carries dual surfaces longer, and product work queued behind the removal waits.

Misconceptions

Claim
“We deprecated it — consumers were warned.”
Reality
A docs label warns the people who reread docs for things they already integrated, which is nearly nobody. Warning is a delivery problem: wire headers, SDK strikethroughs, and direct contact for measured users is what "warned" actually takes.
Claim
“Enforcing the sunset date is too aggressive; we should wait until usage hits zero.”
Reality
Usage never hits zero on its own — the tail is unstaffed and will not move without force. Waiting forever is choosing permanent maintenance while pretending otherwise; brownouts plus an enforced date is the version of "aggressive" that consumers can actually plan around.