Two headers, two different statements
The Sunset header is specified in RFC 8594 and answers a single question: how long can this URI be relied on? Its value is a timestamp in the HTTP date format and should lie in the future (RFC 8594). The specification is deliberately careful in its wording. A client should treat the timestamp as a hint, because there is no assurance that the resource stays available until that time and disappears afterwards (RFC 8594). For operations that means the date works as the earliest trigger for action, not as a dependable end point for planning. Leaving things until the named day is the weaker reading.
The Deprecation header comes from RFC 9745, on the Standards Track since March 2025, and answers a different question: from when does this URI count as deprecated? The decisive addition comes from the same specification — the act of deprecation does not change the behaviour of the resource (RFC 9745). A deprecated endpoint keeps responding, returns the same data and the same status code. That is exactly why it stays inconspicuous for months in monitoring that checks availability: the notice sits in the header, the result stays green.
- Deprecation names the point from which a resource counts as deprecated; the behaviour of the resource does not change because of it (RFC 9745).
- Sunset names the point at which the resource is likely to stop responding (RFC 8594).
- A link with the relation deprecation points to a description of the deprecation intended for human consumption (IANA).
- A link with the relation sunset points to information about the retirement policy of the context (IANA).
- Both fields together form a window: the start of the deprecation and the likely end of availability.
RFC 8594 separates the two phases explicitly. In the first stage the API remains operational and can still be used, and for that stage the Sunset header is not appropriate under this reading. It is appropriate for the second stage, when the API or a specific version does become unresponsive (RFC 8594). Watching both stages separately yields two signals instead of one: the notice as an early warning and the shutdown date as a deadline.
Two headers, two date formats
The most common reason a check rule finds nothing is not a missing header but a misread date. The two fields use different formats, and that is no accident: the Sunset value is an HTTP timestamp, while the Deprecation value is a structured field of type Date (RFC 9745). RFC 9745 points out in the same place that the Sunset header uses a different data format for historical reasons, and states at the same time that the sunset timestamp must not be earlier than the deprecation timestamp (RFC 9745).
HTTP/2 200
deprecation: @1688169599
sunset: Sun, 30 Jun 2024 23:59:59 GMT
link: <https://api.example.invalid/deprecation-info>; rel="deprecation"; type="text/html"
link: <https://api.example.invalid/sunset-policy>; rel="sunset"
content-type: application/json
cache-control: no-store
For the Sunset value the grammar from RFC 9110 applies: a recipient that parses a timestamp in an HTTP field must accept all three HTTP date formats, while a sender may only generate the IMF-fixdate format (RFC 9110). The time is Coordinated Universal Time, marked in the first two formats by the abbreviation GMT (RFC 9110). The Deprecation value, by contrast, is a delta in seconds from 1 January 1970, distinguished from an ordinary integer on the wire by an @ prefix; a parser has to support all days in the years 1 to 9999 (RFC 9651). Reading both values with the same function reliably produces an invalid timestamp for one of the two fields.
A shutdown date in Coordinated Universal Time lands on a different local clock time depending on the season. Showing the remaining term in local time on a dashboard means converting twice: once when reading, once when displaying. Which errors follow from that, and why a daylight saving switch shifts whole reports, is covered in Daylight saving time in monitoring. For the alert rule itself it is typically calmer to keep the remaining term in whole days and to carry the clock time only in the alert text.
Why green monitoring misses the notice
A typical API check asks three questions: does the URI respond, does it respond fast enough, does the expected status code come back? A deprecated endpoint answers all three cleanly. It returns 200, it responds in the same time as the week before, and its body contains the same fields. The only change sits in a line that a plain availability check does not look at. What a check beyond the status code looks like is covered in API monitoring: watching endpoints.
There is a second blind spot: response headers are rarely kept. Anyone looking through the log after an outage finds status codes and timings but no header — the notice was there and was recorded nowhere. The same gap exists in the opposite direction, when a header reveals too much rather than too little; that case is covered in Version banners and server headers. The practical conclusion is the same in both directions: the response header belongs in the check and in the archive, not only in the post-mortem.
An endpoint that ships its own shutdown date is not a risk. It becomes one only when the check stops reading at the status code.
— XICTRAQ
The check rule: one line in the existing job
Technically the effort is small. An API check that understands assertions on response headers needs no additional check for this, only an additional assertion in the job that already runs. How such assertions and thresholds are built is shown in Assertions and thresholds in API monitoring. The first step is done by hand anyway and takes a minute.
The third command is the actual point. The Deprecation value is unreadable without conversion, and a rule that only checks the raw value for presence keeps reporting long after the date has passed. The sensible move is to convert the value into a date and derive the remaining term from it. The number that ends up in the alert text is the time left — not the timestamp.
| Signal in the response header | What it states | What follows from it |
|---|---|---|
| Deprecation set, Sunset missing | Deprecation without a shutdown date | plan the replacement, ask the provider for the date |
| Deprecation and Sunset set | Notice with a deadline | calculate the remaining term, schedule the migration |
| Sunset set, Deprecation missing | Shutdown date without a formal deprecation | treat it like a deprecation |
| Sunset date moves closer | The deadline shortens | escalate, reassess the plan |
| Sunset date lies in the past | Shutdown may happen at any moment | treat as an incident, not as a warning |
| Header was set and has disappeared | Withdrawn notice or a configuration error | ask the provider, record the state |
| Link with the relation deprecation | Human-readable description linked | add the URI to the replacement file |
Four signals a rule should fire on
An assertion in API monitoring tests whether the response header carries Deprecation or Sunset. As long as neither name appears the check stays silent; once one shows up, an event is recorded together with the complete header.
The time left is derived from the Sunset date and tested against staged thresholds. Four stages are typically enough to keep the first message out of the same week as the shutdown itself.
A date that moves closer is the most important signal of all, because it invalidates the planning already done. The rule compares the value just read with the one stored last and reports any deviation through alerting.
The reverse case deserves a report too: a header that was set and no longer arrives is either a withdrawn deprecation or a configuration error at the provider. Either way you want to know before relying on the earlier state.
These four rules can sit on the same check job that runs anyway. What matters is separating the alert paths: the notice is a planning message for development, the shortened deadline an operations message for the on-call rota. Deliver both through the same channel at the same urgency and the team gets used to the message — Configuring monitoring alerts properly describes how to avoid that. The same separation applies to related headers such as quota information, covered in Rate limits and status code 429.
Turning a date into lead time
A date on its own steers nothing. It becomes actionable once it turns into a number of days that can be tested against thresholds. The arithmetic is unspectacular but holds two traps. The Deprecation value is in seconds and has to be multiplied by a thousand before a time object comes out of it. And a Sunset date in the past is not a negative term but a state: RFC 8594 recommends the safest reading for that case, treating a timestamp in the past as the present time, because the resource may then become unavailable at any time (RFC 8594).
// Read the response header: two fields, two formats, one remaining term
function readDeadline(headers) {
const dep = headers.get('deprecation'); // structured field: @<seconds>
const sun = headers.get('sunset'); // HTTP date: IMF-fixdate
const depDate = dep && /^@-?\d+$/.test(dep.trim())
? new Date(Number(dep.trim().slice(1)) * 1000)
: null;
const sunDate = sun ? new Date(Date.parse(sun)) : null;
if (sunDate && Number.isNaN(sunDate.getTime())) {
return { measured: false, reason: 'sunset date not parsable' };
}
if (!depDate && !sunDate) {
return { measured: false, reason: 'no shutdown signal in the header' };
}
const daysLeft = sunDate
? Math.floor((sunDate.getTime() - Date.now()) / 86400000)
: null;
return {
measured: true,
deprecatedFrom: depDate,
shutdown: sunDate,
daysLeft, // negative: date has passed
handleNow: daysLeft !== null && daysLeft <= 0
};
}
The return value deliberately separates measured from not measured. A missing header is not a value of zero and not a term of a thousand days, but a measurement that did not happen. Substituting a placeholder here builds a metric that looks permanently green because it knows no measurement at that point.
- 180 days before the date: message to development, entry in the replacement plan, check whether a successor version has been named.
- 90 days before the date: migration scheduled, staging tested against the successor version, rollback path written down.
- 30 days before the date: migration completed, or a named reason in the ticket why it is not.
- 7 days before the date: escalation to the on-call rota and a described fallback in case the date holds.
- Date has passed: handle as an incident, regardless of whether the URI still responds.
The most awkward change is not the appearance of a Sunset header but its shift. A date that jumps from December to September invalidates every plan built on the old value — and without a stored previous value nobody notices. That is why the last value read belongs in the check itself, not only in the alert text. How such a message runs from detection to handover is described in Incident management and escalation.
When the provider sets nothing at all
The two headers are recommendations, not obligations. RFC 8594 carries the status Informational, RFC 9745 the status Standards Track; both describe what a notice looks like, not that one has to be given. Dependable figures on how widespread the two fields are in the wild are not available here in a citable primary source — accordingly this article deliberately states no adoption rate. In practice that means the header rule is a gain where it applies, and does not replace watching the other signals.
- The provider's changelog and version index as a check of its own, not as a browser bookmark.
- A status code that changes: 301 to a new version, 410 for a removed resource.
- A response body that suddenly carries a notice field alongside the data without the status code changing.
- Certificate and domain expiry dates of the API, because an expiring contract often shows there first.
- Your own contract with the provider, which states the notice period, provided one was agreed.
The last point is the most effective and the least often checked. Where the notice period is written into the contract it can be compared with the date from the header: a shutdown two weeks after the notice, when three months were agreed, is a contract matter and not a technical one. What else such an agreement carries is described in Data processing agreements under Article 28. Deadlines that have nothing to do with APIs follow the same pattern — Preventing domain expiration shows the case on a domain.
The day after: what a client sees then
RFC 8594 describes what to expect after the date: interactions with the resource are then likely to result in client-side errors in the 4xx range, in redirect responses in the 3xx range, or the client may not be able to interact with the resource at all (RFC 8594). For monitoring that is a usable list of expectations, because each of the three cases looks different. A 404 is caught by a status code check immediately. A 301 to a new version is not, if the check follows redirects and only judges the final code.
The most awkward outcome is not on that list: the retired URI keeps answering with 200 and returns an empty list, a notice text or an error object in the body. Status code and response time stay inconspicuous, yet the data flow has stopped. That is why every API check needs an assertion on the content: an expected field, a minimum size, a schema. What a silent content fault looks like when it is not the API but the character encoding that breaks is shown in Broken umlauts after a migration.
The Sunset information applies to exactly the resource that returns it and announces the upcoming sunset of that specific resource (RFC 8594). A notice on one endpoint therefore says nothing about the neighbouring endpoints of the same API. Running an API with thirty URIs and checking only the entry point leaves the notice for the other twenty-nine unseen. In practice the check rule belongs on every URI the own code calls — and compiling that list is the real effort, not the rule.
Your own API: setting the header yourself
The perspective flips as soon as you offer an API yourself. Then the header is not a signal you read but one you send — on every response of the affected resource, not only on an announcement page. Both fields are listed in the IANA field name registry with the status permanent: Deprecation with the structured type Item, Sunset without a type (IANA). A custom header name with an X prefix is therefore neither necessary nor helpful.
location /v1/ {
# Deprecating an endpoint version: both headers on every response
add_header Deprecation "@1688169599" always;
add_header Sunset "Sun, 30 Jun 2024 23:59:59 GMT" always;
add_header Link "<https://api.example.invalid/deprecation-info>; rel=\"deprecation\"; type=\"text/html\"" always;
proxy_pass http://127.0.0.1:8080;
}
Two rules come with it. First the order: the timestamp in the Sunset header must not be earlier than the one in the Deprecation header, and where that happens anyway the client application developer should consult the resource developer (RFC 9745). Second the link: a link with the relation deprecation points to a description of the deprecation intended for human consumption, a link with the relation sunset to information about the retirement policy of the context (IANA). The Link header itself is specified in RFC 8288 and provides a means for serialising one or more links into HTTP headers (RFC 8288). Setting both gives someone else's monitoring exactly what you would like to find yourself.
An entry in the changelog reaches the people who read it. A header reaches every system that calls the API — including the one whose operator has not opened the changelog yet. Together they cost one more configuration line and typically save the phone call on shutdown day. The result can be verified in the same minute with a header request against your own URI.
What of this belongs in the operations calendar
A check rule without an owner produces messages that age in an inbox. For the signal to turn into action, three things have to be settled: who acknowledges the message, where the ticket is kept and when it escalates without progress. The technical part is already done at this point — what is missing is the assignment.
- Take inventory: list every external URI the own code calls, with version and calling service.
- Set up the check: one assertion on Deprecation and Sunset per URI, alongside the existing assertion on content and response time.
- Set thresholds: staged remaining terms with different recipients instead of one single threshold for every message.
- Store the previous value: keep the last date read per URI so that a shift can be noticed at all.
- Assign ownership: a named role per URI that acknowledges a notice and owns the ticket.
- Refresh quarterly: add new URIs from the code to the list, remove the ones that are gone.
Before a rule is set up, a single header request across the whole URI list is the faster answer. Within minutes it shows whether there is any notice in the estate at all and how many URIs are affected. Only then can it be decided whether a daily or an hourly check is appropriate. For URIs that are only called on a schedule, a heartbeat is a useful addition, because there a missing run is the first symptom.
Running deprecations as a standing check rule
The Sunset header and the Deprecation header solve no problem that would be unsolvable without them. They move the moment you learn about a shutdown: from a user's trouble report to a date that sits in every response months in advance. The effort is one assertion in the check job that already runs and a maintained list of your own dependencies. Setting that up once trades a surprise for a deadline — and deadlines can be planned. How the two fit together in day-to-day operations can be seen in the demo; the delivery of the messages sits in alerting.
The technical statements in this article come from the specifications themselves: RFC 8594 on the Sunset header, RFC 9745 on the Deprecation header, RFC 9110 on the HTTP date format, RFC 9651 on structured fields and RFC 8288 on the Link header. The registry entries come from the IANA HTTP field name registry and the link relation types registry. Adoption figures for the actual use of the two headers are deliberately omitted, because no citable primary source for them is available here.