APIBreak

Standards

Deprecation and Sunset headers: what RFC 8594 and RFC 9745 say, and what GitHub actually sends

Two RFCs define response headers for an endpoint that is going away. What each one specifies, a live GitHub response that carries both, and what to watch when a vendor sends neither.

Published 22 October 2026 · 6 min read

HTTP has two response headers for telling a client that an endpoint is going away. Sunset is RFC 8594, published in May 2019. Deprecation is RFC 9745, published in March 2025. This article reads both documents, shows a production response that carries both headers, and says what to do about the vendors where we could not find them. The RFC text is quoted as published. The live responses were read on 2026-10-08.

Sunset: RFC 8594

RFC 8594 defines the Sunset response header, which says a resource is expected to become unresponsive at a specific point in time. Its value is one HTTP-date. The RFC's own example:

Sunset: Sat, 31 Dec 2018 23:59:59 GMT

What the document says beyond the syntax:

  • It is an Informational RFC, not a Standards Track one.
  • The date is a hint. Clients should not assume the resource stays available until then, or disappears right after.
  • It does not say what happens afterwards. The RFC lists client errors, redirects, or no response at all as likely outcomes.
  • It is not for the first stage of deprecation, when an API is merely no longer recommended. It is for the date the API stops answering.
  • It is separate from caching. The header is not concerned with resource state, and the RFC calls the two complementary.
  • It registers a sunset link relation, for a page that describes the retirement policy or the migration path.

Deprecation: RFC 9745

RFC 9745 is a Standards Track document. It defines the Deprecation response header as a structured field whose value must be a Date in the RFC 9651 form: an @ sign followed by a Unix timestamp. The date can be in the past (the resource was deprecated then) or in the future (it will be). The RFC's example, with the link relation it also registers:

Deprecation: @1688169599
Link: <https://developer.example.com/deprecation>;
      rel="deprecation"; type="text/html"

That timestamp is Friday 30 June 2023, 23:59:59 UTC. Four properties matter in practice:

  • Deprecation changes no behavior. The RFC says the act of deprecation does not change any behavior of the resource.
  • Scope is the resource that returned the header. A provider may document a wider scope, such as a whole API signaled on its home document, but the RFC notes that clients who have not read that documentation cannot see it.
  • It is a hint. The RFC says clients should be built to work without it.
  • It pairs with Sunset. When both are sent, the Sunset time must not be earlier than the Deprecation time.

A live response that carries both

GitHub's REST API has an endpoint that exports a software bill of materials for a repository, GET /repos/{owner}/{repo}/dependency-graph/sbom. On 2026-10-08 we requested it for a public repository, without authentication. The response was a 200, and it included these headers:

deprecation: Tue, 12 May 2026 00:00:00 GMT
sunset: Fri, 13 Nov 2026 00:00:00 GMT
link: <https://github.blog/changelog/
  2026-05-12-synchronous-sbom-api-deprecated>;
  rel="deprecation"; type="text/html",
  <https://api.github.com/repos/{owner}/{repo}/
  dependency-graph/sbom/generate-report>;
  rel="alternate"

The link header is wrapped here to fit, and the repository path is replaced with the template. Three things to read in it:

  • The endpoint still returned 200. The deprecation changed nothing about the response body, as RFC 9745 describes.
  • The Deprecation value is an HTTP-date, not the @ timestamp RFC 9745 requires. A strict structured-field parser will reject it. A monitor should accept both forms.
  • The Sunset date was 36 days away on the day we read it. The link with rel=deprecation points at the changelog entry, and a second link names a replacement.

This is one endpoint on one day. We did not survey how consistently GitHub sends these headers across its deprecated operations.

Where we could not find them

The same day, unauthenticated requests to api.stripe.com, api.openai.com and api.twilio.com returned 401 with neither header. That proves little. A 401 is not a response from a deprecated resource, and a server that follows the RFCs sends the header only on resources that are deprecated. We have no authenticated measurement for those three, so we make no claim about them.

What can be said is that a vendor's lifecycle notice often lives outside the response. OpenAI, for example, keeps a deprecations page. As read on 2026-10-08, it states minimum notice before a model is retired: at least 6 months for generally available models, at least 3 months for specialized variants, and as little as 2 weeks for preview models. None of that reaches your client unless something reads the page.

Log the headers you do receive

The check is small. This script prints a line for every URL whose response carries either header:

for url in "$@"; do
  headers=$(curl -sI "$url")
  dep=$(echo "$headers" | grep -i '^deprecation:' || true)
  sun=$(echo "$headers" | grep -i '^sunset:' || true)
  if [ -n "$dep" ] || [ -n "$sun" ]; then
    echo "LIFECYCLE $url -> $dep $sun"
  fi
done

We ran it on 2026-10-08 against the GitHub API root and the SBOM endpoint above. It printed one line, for the SBOM endpoint, with both dates. Its limits:

  • It sends HEAD requests. Some APIs answer HEAD differently from GET.
  • It sends no credentials. Most endpoints you depend on need them, so in practice the better place is your HTTP client: log both headers on every real response.
  • It only sees the URLs you give it. A header on an endpoint you forgot to list is invisible.

Two additions make the output useful. Parse the dates, so findings sort by urgency. And fail the job when a Sunset date falls inside your release horizon, so the finding reaches someone before the date does.

The same deprecation, seen in the spec

Headers arrive only on calls you make. A published OpenAPI description covers every operation, including ones your tests never exercise, and deprecation is a field in it: an operation gains deprecated: true between one commit and the next.

The SBOM endpoint shows both channels side by side. Its header gives 2026-05-12 as the deprecation date. In GitHub's public description repository, the commit that marks the operation deprecated is dated 2026-08-26, by our snapshot of that repository. We do not know when the header was first sent, so this is not a measurement of which channel was earlier. It does show that the two can carry different dates for the same event.

This spec channel is the one apibreak reads. In a check we ran on 2026-10-08 against three declared GitHub endpoints with a 2026-03-26 baseline, the SBOM operation came back as a deprecation finding, listed separately from breaking ones. The spec flag carries no sunset date. The header does. Each channel has something the other lacks.

A combined policy

  1. On your own APIs, send Deprecation from the day you deprecate something, in the RFC 9745 format, with a rel=deprecation link to the migration notes. Add Sunset once the shutdown date is set.
  2. In your HTTP client, log both headers on every third-party response, accept either date format, and alert the first time one appears.
  3. For vendors that publish an OpenAPI description, diff it against a pinned baseline and treat a newly deprecated operation as its own severity, below breaking and above additive.
  4. For vendors with neither, a person or a program has to read the deprecations page on a schedule.
  5. Treat a Sunset date inside your release horizon as a failing condition.

The headers are the only one of these channels that gives a shutdown date in the response itself. Where a vendor sends them, they are worth the few lines it takes to log them.

Check the endpoints you actually call

APIBreak reads the endpoints you declare, compares the vendor's own OpenAPI specification against the revision you pinned, and fails the build only when a change touches one of yours. It runs in your CI with no credentials and nothing uploaded, and the check is free.

Install the check · read a real report · what changed at each vendor

What changed at the vendors in this article

Keep reading