Most "deprecations" are either silent deletions that break an integration at 2 a.m., or permanent warnings that glow for years until everyone ignores them. A real deprecation is a timeline with a signal clients can automate against, a pointer to the replacement, and a cut-off that actually happens. HTTP and OpenAPI already define the machinery; the missing piece is usually the discipline to use all of it together.

The four signals

SignalWhereWhat it says
Deprecation header (RFC 8594)Every response"This endpoint is deprecated as of this HTTP-date."
Sunset header (RFC 8594)Every response"It will stop working after this HTTP-date."
Link: rel="successor-version"Every response"The replacement lives at this URL."
deprecated: true + extensionsOpenAPIMarks the operation in docs and generated SDKs, with the same dates

The headers are the runtime signal that reaches code that never reads your changelog; the OpenAPI annotation is the design-time signal that reaches developers before they ship new calls. Use both, with matching dates.

The headers in action

HTTP/1.1 200 OK
Deprecation: Wed, 01 Oct 2025 00:00:00 GMT
Sunset: Wed, 01 Apr 2026 00:00:00 GMT
Link: <https://api.example.com/v2/charges>; rel="successor-version"
Content-Type: application/json
  • Deprecation carries the date the deprecation was announced (or true when you do not want to state a date; prefer a date).
  • Sunset carries the cut-off. The gap between the two is the migration window.
  • The Link header points at the replacement resource or documentation; use rel="successor-version" for a versioned successor and rel="alternate" for a guide.

Clients can log these headers in middleware and open tickets automatically, which is far more reliable than hoping a human saw the announcement.

Document it in OpenAPI

Mark the operation deprecated and carry the timeline and migration note in a consistent extension so renderers and SDKs can surface it:

paths:
  /v1/charges:
    get:
      deprecated: true
      summary: List charges (deprecated; use v2)
      description: >-
        Deprecated 2025-10-01 and scheduled for removal 2026-04-01.
        Migrate to GET /v2/charges, which paginates with cursors and returns
        amounts as minor-unit integers.
      x-deprecation:
        deprecated_at: '2025-10-01T00:00:00Z'
        sunset_at: '2026-04-01T00:00:00Z'
        successor: /v2/charges
        guide: https://docs.example.com/migrations/v1-to-v2-charges
      responses:
        '200':
          description: Charges (legacy shape).
          headers:
            Deprecation:
              schema: { type: string, format: http-date }
            Sunset:
              schema: { type: string, format: http-date }
            Link:
              schema: { type: string }
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/LegacyChargesResponse'

Documenting the headers on the response makes them visible in generated docs and lets contract tests assert they are actually being sent. Many generators also surface deprecated: true as a strikethrough or compiler warning, which discourages new call sites.

Pick a realistic timeline and hold to it

A defensible window depends on who depends on the endpoint:

SurfaceTypical minimum window
Internal-only, few callers4 to 8 weeks
Public API, authenticated partners6 to 12 months
Public API, unauthenticated or widely embedded12 months or longer

Announce, emit headers, and leave the endpoint fully functional during the window. Deprecation is not a slowdown or a behavior change; if you make the legacy path flaky to push migration, you break clients before the sunset and lose the trust that makes future deprecations credible.

Drive migration with telemetry, not guesses

You cannot retire what you cannot see. Before announcing, instrument the endpoint to answer: who calls it, how often, and which user-agent or credential. Then:

  1. At announcement, notify every active caller you can identify, with the successor and a guide.
  2. During the window, watch traffic; contact teams whose calls never decline.
  3. Near sunset, send a final reminder to the remaining callers.
  4. Keep a list of known stragglers so the cut-off is a decision, not a surprise.

If significant traffic remains at sunset with no owner, that is evidence the window or the migration path was wrong; extend once with a clear final date rather than silently deleting or silently keeping it forever.

What happens after sunset

Decide and document the post-sunset behavior in advance. The clean options are:

  • Remove and return 404/410 Gone. 410 Gone is the precise status for a deliberately removed resource; pair it with a JSON body linking the successor.
  • Redirect to the successor only when the call is mechanically translatable (for example a path rename with the same request and response), using 301 for GET and documenting it; do not redirect when shapes differ, as a silent transform hides breakage.
  • Versioned error with a clear type and the migration guide, so an automated client can surface an actionable message.

Do not leave a half-working endpoint. Returning 200 with subtly wrong data after sunset is worse than a loud 410.

Deprecating fields and parameters

The same discipline applies below the endpoint level. A deprecated field or query parameter should:

  • be marked deprecated: true on the schema property or parameter;
  • keep working through the window;
  • be described with the replacement (for example amount float replaced by amount_minor integer);
  • be removed only with a version bump or a documented breaking window.

Removing a field is a breaking change even when the URL stays the same, so field-level deprecations belong on the same timeline and diff checks as endpoint removals.

What codegen, contract tests, and AI callers need

  • Generators mark deprecated operations and properties so new code does not adopt them and IDEs warn on existing use.
  • A contract test can assert that every operation with deprecated: true actually returns Deprecation and Sunset headers, closing the gap between the spec and the running API.
  • An OpenAPI diff in CI catches an unannounced removal before it ships, so deletions become scheduled sunsets rather than accidents.
  • An AI agent or generated client reading the spec sees the successor pointer and can choose the current operation instead of building on the deprecated one.

Checklist

  1. Emit Deprecation, Sunset, and Link rel="successor-version" on every deprecated response, with matching dates.
  2. Mark the operation (and any deprecated fields) deprecated: true in OpenAPI with a consistent extension carrying the timeline and migration guide.
  3. Choose a window appropriate to the audience and keep the endpoint fully functional throughout.
  4. Instrument callers up front and drive migration by telemetry, contacting teams that do not move.
  5. Define post-sunset behavior now: 410 Gone with a successor link, or a documented redirect only when shapes match.
  6. Treat field and parameter removals as breaking changes on the same timeline.
  7. Add a contract test asserting the headers are sent and a CI diff that blocks unannounced removals.

Get these right and deprecation becomes a boring, predictable migration instead of a 2 a.m. outage and a damaged relationship with the developers who depend on you.

You can mark operations deprecated, generate SDKs that warn on them, and write contract tests asserting the sunset headers in one local-first workspace, right in your browser. For the versioning strategy that decides when a sunset is even necessary, see REST API versioning in 2026.