Third-Party API Deprecation and Sunset Header Watch avatar

Third-Party API Deprecation and Sunset Header Watch

Pricing

from $10.00 / 1,000 run starteds

Go to Apify Store
Third-Party API Deprecation and Sunset Header Watch

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

kingii98

Maintained by Community

Actor 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 sourceExample
rfc-deprecation-headerRFC 9745 Deprecation: @1756339200 (Bitbucket)
rfc-sunset-headerRFC 8594 Sunset: Tue, 14 Apr 2026 00:00:00 GMT (Bitbucket)
link-relationLink: <...>; rel="deprecation" or rel="sunset"
vendor-headerx-ad-api-version-warning: ... v2.0 has been deprecated (Facebook Graph), X-Shopify-API-Deprecated-Reason, Warning: 299 - "... deprecated"
http-410-goneHTTP 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, sunset or successor-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 has baseline_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 classMeaning
endpoint-goneThe endpoint answers HTTP 410, or HTTP 404 where the baseline shows it answered before. This is the most urgent class.
new-deprecationA 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-earlierThe Sunset date moved earlier, or a Sunset date appeared where there was none.
sunset-laterThe Sunset date moved later, or the Sunset date disappeared while the endpoint stays deprecated.
signal-removedThe baseline had a signal, and the endpoint now sends none.
unchangedNo 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

FieldTypeDefaultMeaning
endpointsarray of 1 to 100 recordsfour public demo endpointsEach 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_headersecret stringnoneOne header that you own, in the form Name: secret.
sunset_warning_daysinteger 0 to 365090A Sunset date inside this window sets the gate to fail. A gone endpoint sets it to fail too.
alert_webhook_urlHTTPS URLemptyOne JSON POST for each deprecation state change.
baseline_namestringDEFAULTThe record key of the baseline. Use one name for each endpoint list.
request_timeout_secondsinteger 2 to 6015Timeout for each request and each webhook POST.
concurrencyinteger 1 to 205Endpoints requested at the same time.
max_redirectsinteger 0 to 103Redirect hops for each request.
max_run_secondsinteger 30 to 3600240Deadline 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_header only 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: true and the names of the static request headers.

Safety limits

  • Only http and https URLs. 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 localhost target, a method other than GET, HEAD or OPTIONS, a bad header, a duplicate) becomes one row with the reason code INVALID_ENDPOINT or BLOCKED_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 + 5 seconds. The worst-case wall clock of the run is min(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

EventUnitCounted as
run-startedone Actor runCharged one time, before the Actor requests an endpoint.
endpoint-checkedone endpoint requested and its deprecation signals parsed in one runCharged 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-flaggedone endpoint whose deprecation or sunset state changed since the baselineCharged 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 sync
uv run pytest
uv run ruff check .