Third-Party API Deprecation and Sunset Header Watch
Pricing
from $10.00 / 1,000 run starteds
Third-Party API Deprecation and Sunset Header Watch
Watch the third-party API endpoints that you call in production for RFC 9745 Deprecation and RFC 8594 Sunset response headers. The Actor compares each endpoint against a stored baseline, reports new deprecations and moved sunset dates, and fails a gate wh
Pricing
from $10.00 / 1,000 run starteds
Rating
0.0
(0)
Developer
kingii98
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Share
Watch the third-party API endpoints that you call in production. Learn about a planned shutdown before the endpoint stops, and learn the same day when a version you call is gone.
The Actor sends one request to each endpoint, reads every deprecation signal the vendor sends, and compares the signal set against the baseline of the previous run. It reports each new deprecation, each gone endpoint and each moved sunset date. A gone endpoint, or a sunset date inside the warning window, sets the gate to fail.
What the Actor watches
A probe of 201 vendor endpoints on 2026-09-11 (docs/signal-probe.md) found that
only one vendor sends the RFC headers, and that a retired API version answers
HTTP 410 Gone with a notice in the error body far more often. The Actor
therefore reads all of these:
| Signal source | Example |
|---|---|
rfc-deprecation-header | RFC 9745 Deprecation: @1756339200 (Bitbucket) |
rfc-sunset-header | RFC 8594 Sunset: Tue, 14 Apr 2026 00:00:00 GMT (Bitbucket) |
link-relation | Link: <...>; rel="deprecation" or rel="sunset" |
vendor-header | x-ad-api-version-warning: ... v2.0 has been deprecated (Facebook Graph), X-Shopify-API-Deprecated-Reason, Warning: 299 - "... deprecated" |
http-410-gone | HTTP 410 on a retired version (Twitter v1, GitLab v3, Mailchimp 2.0, Etsy v2, Yelp v2, Docker Hub v1, Bintray) |
error-body-notice | "API V3 is no longer supported. Use API V4 instead." in the body of the error answer (GitLab, Twitter, Mailchimp, Bitbucket) |
An HTTP 404 alone is not a signal: a wrong path looks the same. It becomes the
change class endpoint-gone when the baseline shows that the endpoint answered
before. A successor-version link alone only says that a newer version exists.
The Actor uses HTTP only. It uses no browser and no proxy. It reads the response headers. On an error answer (HTTP 4xx) it also reads at most 64 KB of the body for a vendor notice; the body of a success answer is never read, so a data record with the word "sunset" in it can never trip it.
What you get
- One row for each endpoint: the HTTP status and the previous HTTP status, the
list of signal sources, the raw Deprecation header and its parsed date, the raw
Sunset header and its parsed date, every Link target with rel
deprecation,sunsetorsuccessor-version, every API version response header, the vendor deprecation headers, the notice found in the error body, the days until the sunset, whether the endpoint is gone, and the change class against the baseline. - One summary row: the endpoint count, the deprecated count, the gone count, the
nearest sunset date, and the boolean
gate_pass. - Optional: one JSON POST to your webhook for each endpoint whose deprecation state changed.
The baseline
The Actor keeps the last parsed header set of each endpoint in the named
key-value store api-sunset-header-baseline, under the record key that
baseline_name gives. The named store stays after the run ends. The key of one
endpoint is its method and its URL, so one URL with two methods is two endpoints.
- First run for an endpoint: the Actor writes the baseline. It reports every
present signal as
new-deprecation. The row hasbaseline_status: "created". - Later run: the Actor compares the current headers against the baseline. The
row has
baseline_status: "compared". - An endpoint that did not give usable headers (timeout, DNS failure, refused
target, HTTP 5xx or 429 after the retry): the Actor keeps the stored entry. The
row has
baseline_status: "unavailable"and no change class. An error page from a gateway must not look like a removed signal.
The Actor writes the record one time, at the end of the run. An aborted run does not change the previous baseline. Endpoints that this run did not request keep their entry. The record holds at most 500 endpoints.
Change classes
A signal is any source in the table above. A successor-version link alone
is not a signal, and a 404 alone is not a signal.
| Change class | Meaning |
|---|---|
endpoint-gone | The endpoint answers HTTP 410, or HTTP 404 where the baseline shows it answered before. This is the most urgent class. |
new-deprecation | A signal source appeared where the baseline had none, or a new source appeared next to an existing one: a Deprecation header next to an existing Sunset header, or a body notice next to an existing 410. |
sunset-earlier | The Sunset date moved earlier, or a Sunset date appeared where there was none. |
sunset-later | The Sunset date moved later, or the Sunset date disappeared while the endpoint stays deprecated. |
signal-removed | The baseline had a signal, and the endpoint now sends none. |
unchanged | No change. A reformatted header with the same date is unchanged. |
When two changes occur at the same time, the more urgent class wins:
endpoint-gone, then sunset-earlier, then new-deprecation, then sunset-later.
The gate
gate_pass is false when at least one checked endpoint is gone (HTTP 410, or
404 where the baseline answered), or has a Sunset date inside sunset_warning_days
from now. A Sunset date in the past is also inside the window. A Deprecation
header, a vendor header or a body notice without a Sunset date does not set the
gate to fail: the endpoint still answers.
A failed gate is a result, not a fault. The run writes it to the dataset and to the status message, and the run succeeds. The same applies to a changed signal, an unreachable endpoint and an unusable input entry.
Input
| Field | Type | Default | Meaning |
|---|---|---|---|
endpoints | array of 1 to 100 records | four public demo endpoints | Each record has url, method (GET, HEAD or OPTIONS, default GET), optional static headers, and optional use_auth_header. A plain URL string is a GET request. |
auth_header | secret string | none | One header that you own, in the form Name: secret. |
sunset_warning_days | integer 0 to 3650 | 90 | A Sunset date inside this window sets the gate to fail. A gone endpoint sets it to fail too. |
alert_webhook_url | HTTPS URL | empty | One JSON POST for each deprecation state change. |
baseline_name | string | DEFAULT | The record key of the baseline. Use one name for each endpoint list. |
request_timeout_seconds | integer 2 to 60 | 15 | Timeout for each request and each webhook POST. |
concurrency | integer 1 to 20 | 5 | Endpoints requested at the same time. |
max_redirects | integer 0 to 10 | 3 | Redirect hops for each request. |
max_run_seconds | integer 30 to 3600 | 240 | Deadline for the endpoint list. |
Example endpoint list:
[{"url": "https://api.vendor.example/v1/orders?limit=1", "method": "GET","headers": {"Api-Version": "2024-01-01"}, "use_auth_header": true},{"url": "https://api.other.example/v2/status", "method": "HEAD"}]
Every field except auth_header has a schema default. A run with the empty input
{} watches the four demo endpoints and succeeds. One of them, GitLab API v3, is
retired and answers 410 with a notice, so the demo run shows a gone endpoint and
a failed gate.
The secret auth header
- The Actor sends
auth_headeronly to the endpoints with"use_auth_header": true. Other vendors never receive your key. - The Actor sends it only to the origin (scheme, host and port) of the endpoint URL. A redirect to a different origin does not get it.
- The Actor never writes the secret to the log, the dataset, the baseline or the
webhook payload. The row shows only
auth_header_sent: trueand the names of the static request headers.
Safety limits
- Only
httpandhttpsURLs. No credential in a URL. localhost, and any hostname that resolves to a private or reserved address, is refused. The Actor resolves and checks each redirect hop again before it follows the hop. A redirect chain that repeats a URL stops.- An input entry that is not usable (a malformed URL, a foreign scheme, a
localhosttarget, a method other than GET, HEAD or OPTIONS, a bad header, a duplicate) becomes one row with the reason codeINVALID_ENDPOINTorBLOCKED_TARGET. It does not stop the run, and it is not charged. - At most 100 endpoints, 20 static headers for each endpoint, 20 Link targets and 20 API version headers for each row.
- One endpoint gets one request and one retry. The retry occurs after a timeout, a
connection error, or HTTP 429, 500, 502, 503 or 504. One endpoint takes at most
2 x request_timeout_seconds + 5seconds. The worst-case wall clock of the run ismin(ceil(endpoints / concurrency) x that budget, max_run_seconds). With the default fixture that is 35 seconds. - The webhook gets no redirect. After 3 failed POSTs in a row, the run stops calling it and marks the other alerts as skipped.
Output
The dataset holds one summary row and one row for each endpoint. The record_type
field separates them.
Endpoint row (record_type: "endpoint"): endpoint_url, method, final_url,
reason_code, error, http_status, previous_http_status, signal_present,
deprecated, signal_sources, endpoint_gone, vendor_headers, body_notice,
deprecation_header, deprecation_date, sunset_header, sunset_date,
days_until_sunset, sunset_within_window, previous_sunset_date,
link_targets (each with rel and url), deprecation_links, sunset_links,
successor_version_links, api_version_headers, change_class, changed,
baseline_status, first_seen, last_changed, alert_status, attempts,
redirects, auth_header_sent, request_header_names, response_time_ms and
observed_at.
Summary row (record_type: "summary"): endpoint_count, endpoints_checked,
endpoints_unavailable, deprecated_count, gone_count, sunset_within_window_count,
nearest_sunset_date, nearest_sunset_endpoint, days_until_nearest_sunset,
sunset_warning_days, gate_pass, changes_total, changes_by_class,
baselines_created, alerts_sent, alerts_failed, baseline_store,
baseline_name and observed_at.
Webhook payload: event (deprecation-change-flagged), endpoint_url,
method, change_class, http_status, previous_http_status, signal_sources,
endpoint_gone, vendor_headers, body_notice, deprecation_header, deprecation_date,
sunset_header, sunset_date, previous_sunset_date, days_until_sunset,
sunset_within_window, link_targets and observed_at.
Reason codes
OK, SERVER_ERROR, TIMEOUT, DNS_FAIL, CONNECT_FAIL, BLOCKED_TARGET,
REDIRECT_LOOP, RUN_DEADLINE, CHARGE_LIMIT and INVALID_ENDPOINT. Only OK
means that the Actor parsed the headers. An HTTP 4xx answer, for example
410 Gone, is OK: the 410 itself is the signal, and vendors often send the
Sunset header or a body notice with it.
Pricing: pay per event
| Event | Unit | Counted as |
|---|---|---|
run-started | one Actor run | Charged one time, before the Actor requests an endpoint. |
endpoint-checked | one endpoint requested and its deprecation signals parsed in one run | Charged one time for each endpoint row with reason_code: "OK". An endpoint that did not give usable headers, and an input entry that is not usable, are not charged. |
deprecation-change-flagged | one endpoint whose deprecation or sunset state changed since the baseline | Charged one time for each endpoint row with a change class other than unchanged. On the first run, each endpoint with a present signal is a new-deprecation, so it is charged. |
Before the Actor requests an endpoint, it limits the endpoint list to the number of
endpoint-checked events that the maximum total charge of the run allows. The
endpoints outside that limit get the reason code CHARGE_LIMIT, are not requested,
and keep their baseline for the next run.
Repeat use
A vendor can add a deprecation signal, or retire a version, at any time. Put the Actor on a
daily or weekly Apify schedule with one saved endpoint list and one
baseline_name. The first run creates the baseline. Each later run reports only
the endpoints whose state changed, and the gate tells you when a shutdown comes
near.
Local development
uv syncuv run pytestuv run ruff check .