Skip to main content

Deprecation Policy and Sunset Discipline

What This Concept Is

A deprecation policy is the written, published, repeatable process for taking something out of your API. "Something" can be a whole version, an endpoint, a field, a query parameter, an enum value, or a behavior. The policy answers:

  • how do consumers find out a thing is deprecated?
  • how long do they have to migrate?
  • what happens when the runway ends?
  • who decides exceptions?

Two relevant HTTP standards:

  • Deprecation header (RFC 9745): announces that a resource has been deprecated, with an optional date.
  • Sunset header (RFC 8594): declares the date after which the resource will no longer be available.

Example:

HTTP/1.1 200 OK
Deprecation: @1734652800
Sunset: Wed, 01 Oct 2026 23:59:59 GMT
Link: <https://docs.example.com/migration/v1-to-v2>; rel="sunset"

The headers are machine-readable so clients can log or alert on them; the Link gives humans the migration guide.

Why It Matters Here

Without a deprecation policy:

  • nobody removes anything, and the API surface grows forever
  • teams "deprecate" in private docs and surprise consumers with removal
  • old versions run in production for years, attracting bugs and bills
  • consumers have no scheduling signal, so they never migrate until forced

A published policy turns "breaking change" from an event into a process. It is the only mechanism that lets large APIs stay small over time.

Concrete Example

The playbook: announce -> dual-run -> sunset -> remove

A four-stage process with concrete timings:

Stage 1: Announce (day 0).

  • publish a migration guide with before/after examples
  • start emitting Deprecation and Sunset headers on all affected responses
  • email / in-app notify every known consumer and account owner
  • add the deprecated item to the changelog with the sunset date

Example announcement, machine-readable:

HTTP/1.1 200 OK
Deprecation: true
Sunset: Wed, 01 Oct 2026 23:59:59 GMT
Link: <https://api.example.com/library/raw/migration/v1-to-v2>; rel="sunset"; type="text/html"
Warning: 299 api.example.com "Endpoint /v1/orders is deprecated; migrate to /v2/orders by 2026-10-01"

Stage 2: Dual-run (days 1 -> sunset - 30).

  • both old and new continue to work
  • dashboards track per-consumer calls to the deprecated endpoint
  • consumers who keep calling get outreach
  • server-side logs include deprecated=true on every such response

Stage 3: Final warning (sunset - 30 to sunset).

  • all remaining callers receive a warning email naming the endpoint and date
  • optionally inject artificial latency (1-3 seconds) on responses as a nudge
  • if a large caller has not responded, escalate; consider extending or providing a migration engineer

Stage 4: Remove (sunset + grace period).

  • endpoint returns 410 Gone (not 404, which would be ambiguous)
  • response body includes the same migration link
HTTP/1.1 410 Gone
Content-Type: application/problem+json

{
"type": "https://api.example.com/errors/gone",
"title": "Endpoint removed",
"status": 410,
"code": "ENDPOINT_REMOVED",
"detail": "GET /v1/orders was removed on 2026-10-01. Use GET /v2/orders.",
"migration_guide": "https://docs.example.com/migration/v1-to-v2"
}

Typical runway lengths by audience

  • Paid enterprise customers: 12-24 months
  • Free / self-serve consumers: 6-12 months
  • Internal services in the same org: 1-3 months, coordinated with the owning team
  • Unannounced experimental endpoints: documented in the spec as "no compatibility guarantees"; can be removed without process

Pick your numbers and publish them. Ad-hoc timelines produce either surprise removals or indefinite zombies.

Deprecation at smaller granularity

You can deprecate just a field:

{
"id": "ord_1",
"total": 4599,
"total_cents": 4599,
"_meta": {
"deprecations": [
{
"field": "total_cents",
"message": "Use 'total' (minor units) instead",
"sunset": "2026-10-01"
}
]
}
}

A deprecated field returns with a machine-readable note; the response still works. At sunset, the field disappears (within the same major version only if the originally-published policy allows it).

Common Confusion / Misconception

"We announced it in the changelog." Nobody reads changelogs. Put the deprecation signal in the response itself (Deprecation / Sunset headers) so clients can log it. Email the account owner. If they still call the endpoint the week before removal, personally ping them.

"If all our customers have migrated, we can remove anything." Yes - but verify by traffic, not by survey. Instrument every deprecated endpoint with a per-consumer counter. Only remove when traffic is zero (or when policy time runs out, whichever).

"404 Not Found is fine for a removed endpoint." It is ambiguous - consumers cannot tell if they have a wrong URL or a removed one. Use 410 Gone with a descriptive body. Gives them the fix in the response.

"The Sunset date can slip." It can once. If you slip it twice, consumers stop trusting your dates and will not migrate. Treat the published date as binding.

"Deprecation only applies to endpoints." Fields, query parameters, enum values, and response shapes all get the same treatment. The smallest deprecatable unit is whatever consumers can observe on the wire.

How To Use It

For any removal you want to make:

  1. Decide the scope: version, endpoint, field, parameter, enum.
  2. Pick a sunset date consistent with your published runway for this audience.
  3. Write the migration guide before anything else. If the guide is hard to write, the deprecation is premature.
  4. Emit Deprecation + Sunset headers starting day 0.
  5. Instrument per-consumer usage; track the drop curve.
  6. Notify via every channel: email, in-app, changelog, docs, response header.
  7. At sunset, switch the endpoint to 410 Gone with a pointer to the guide.
  8. Keep the 410 Gone response for at least one more cycle; do not reclaim the URL for something else.

Check Yourself

  1. Why is 410 Gone preferred over 404 Not Found for a removed endpoint?
  2. Two days before sunset, your biggest customer has not migrated and says they cannot. What are your options, and which do you take?
  3. Explain the difference between the Deprecation header and the Sunset header. Why do you want both?

Mini Drill or Application

Write the deprecation plan for removing an endpoint POST /v1/widgets:rebuild from a public API:

  1. A 1-page announcement (what's changing, why, when, migration steps).
  2. Example response headers during the dual-run stage.
  3. Example 410 Gone response after sunset.
  4. An internal timeline with specific dates for each stage (announce, dual-run, final warning, removal).
  5. The metrics you will track to know whether you can remove on the declared date.

Read This Only If Stuck