Scheduled Release Page Embargo Leak and Stale-Preview Gate avatar

Scheduled Release Page Embargo Leak and Stale-Preview Gate

Pricing

from $5.00 / 1,000 run starteds

Go to Apify Store
Scheduled Release Page Embargo Leak and Stale-Preview Gate

Scheduled Release Page Embargo Leak and Stale-Preview Gate

Check each scheduled release page from outside, inside its release window. The Actor fails a gate when the outcome content is public before the release time, or when the preview content is still public after it. It compares the CDN copy with a cache-buste

Pricing

from $5.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

Prove two facts for each scheduled release:

  1. The outcome content was not public before the release time.
  2. The preview content was gone after the release time.

Statistics publishers, investor-relations teams and research publishers release content at a fixed time. A deploy that is early, a preview page that stays in production, or a CDN that serves an old copy can break the embargo. Your own pre-publication check runs in your repository. This Actor checks the live site from outside, as a reader sees it.

Put the Actor on an Apify schedule every 5 minutes across the release window. In each run, the Actor requests each release URL that is inside its window. It searches the page for your preview markers and your outcome markers, and it gives a verdict. It also requests a cache-busted copy, so you can see a difference between the CDN copy and the fresh copy.

The Actor uses HTTP only. It uses no browser and no proxy. It does not run JavaScript, so it sees the HTML that the server sends.

What you get

  • One row for each release URL inside its window: the phase, the verdict, the faults, and separate fields for the CDN copy and the cache-busted copy (HTTP status, final URL after redirects, Age, Cache-Control, CDN cache-status headers, markers found, reference period match).
  • One fault row for the first fault of each class for each URL.
  • One summary row: URLs in window, faults by class, and the boolean gate_pass.
  • Optional: one JSON POST to your webhook for each first fault.

Phases and windows

PhaseTimeWhat must be true
pre-releasefrom release_at minus window_before_minutes to release_atNo outcome marker shows.
post-releasefrom release_at to release_at plus window_after_minutesAfter late_tolerance_minutes, the outcome markers show and the preview markers are gone.
outside the windowall other timesThe Actor does not request the URL and does not charge for it.

Verdicts

The Actor gives each copy a verdict. The row verdict is the most urgent verdict of the two copies.

VerdictFaultMeaning
leakyesBefore the release, an outcome marker shows.
stale-previewyesAfter the release and the tolerance, a preview marker still shows.
lateyesAfter the release and the tolerance, no outcome marker shows.
period-mismatchyesAfter the release, an outcome marker shows, but the expected_reference_period text does not.
pendingnoAfter the release, but inside the tolerance, the page has not switched yet.
oknoThe page is in the correct state for its phase.
not-checkednoThe Actor could not search any copy (timeout, DNS failure, refused target, HTTP 5xx or 429, run deadline, or charge limit).

One copy can have two faults. For example, a page that did not switch at all after the release has stale-preview and late. An HTTP 4xx page is searched like any other page, so a 404 page after the release is late.

Markers

  • A marker is a literal string. The Actor does not accept regular expressions.
  • The match ignores letter case and white space, and it decodes HTML entities.
  • The Actor searches the raw HTML (so it finds a marker in an attribute or an inline JSON script) and the text without tags (so it finds a marker that the markup splits, as in <b>August</b> 2026).
  • One marker must not be both a preview marker and an outcome marker.
  • The Actor reads at most 2 MB of each copy. A marker after 2 MB is not found, and the row shows cdn_body_truncated: true.

The cache-busted copy

When cache_bust is true, the Actor requests each URL two times:

  • the CDN copy: the URL as a reader requests it;
  • the cache-busted copy: the URL with a unique _release_gate_cb query parameter and the request headers Cache-Control: no-cache and Pragma: no-cache.

When the two copies have a different verdict or different markers, copies_differ is true. A typical case: the fresh copy is correct, but the CDN still serves the preview page after the release.

The phase ledger

The Actor keeps one entry for each release in the named key-value store release-embargo-ledger, under the record key that ledger_name gives. The key of one release is its UTC release time and its URL, so one URL on two release days is two releases. Each entry holds:

  • first_outcome_seen_at: the first check that found an outcome marker;
  • first_preview_absent_at: the first post-release check with no preview marker;
  • go_live_at and go_live_delay_minutes: the first post-release check where each copy showed the outcome and no preview (the real go-live time);
  • phases_seen: the phases that the ledger covers;
  • faults_reported: each fault class already reported, with its first time.

The instant verdicts do not need the ledger. The ledger stops a repeat charge and a repeat alert for one fault. The Actor writes the record one time, at the end of the run. The record holds at most 1,000 releases; the oldest releases go first.

Input

FieldDefaultDescription
releasestwo demo records1 to 50 records. Each record has url, release_at (ISO 8601 with an offset), 1 to 5 preview_markers, 1 to 5 outcome_markers, and an optional expected_reference_period.
window_before_minutes60Length of the pre-release phase, 1 to 240.
window_after_minutes30Length of the post-release phase, 1 to 240.
late_tolerance_minutes5Time after release_at before late and stale-preview count, 0 to 240.
cache_busttrueAlso request a cache-busted copy.
alert_webhook_urlemptyOne public HTTPS URL for fault alerts.
ledger_nameDEFAULTRecord key of the ledger. Use one name for each release calendar.
as_ofemptyOptional simulation time. See below.

Example:

{
"releases": [
{
"url": "https://www.example-stats.org/cpi/latest",
"release_at": "2026-10-15T08:30:00-04:00",
"preview_markers": ["Next release: 15 October 2026"],
"outcome_markers": ["The Consumer Price Index rose"],
"expected_reference_period": "September 2026"
}
],
"window_before_minutes": 60,
"window_after_minutes": 30,
"late_tolerance_minutes": 5
}

Simulation and the demo

Set as_of to test your markers before a release day. The Actor then picks each phase as if the time were as_of. A simulated run does not read or write the ledger, does not charge fault events and does not send alerts.

The demo input checks https://example.com/ 10 minutes after its release time and https://example.org/ 20 minutes before its release time, at 2026-09-01T12:40:00Z. If you keep the two demo records and leave as_of empty, the Actor uses that demo time. Both verdicts are ok.

Safety limits

  • Only public http and https URLs. The Actor refuses localhost, private and reserved addresses, and URLs with credentials. It checks each redirect hop again.
  • At most 5 redirects, 15 seconds for each request, 30 seconds for each copy, 2 MB for each body, 5 URLs at the same time, and 240 seconds for all requests of one run.
  • An unusable release record becomes a rejected_release row. The other records run. An input that is not usable as a whole (for example window_before_minutes: 999) gives one input_error row and no charge.
  • A failed gate is a result, not an error: the run always ends with SUCCEEDED and a status message.

Output

The record_type field separates the rows.

Release row (release): url, release_at, release_at_utc, phase, minutes_from_release, checked_at, simulated, verdict, faults, new_faults, copies_differ, preview_markers_found, outcome_markers_found, expected_reference_period, reference_period_match, live, charged, the ledger fields, and for each of the prefixes cdn_ and busted_: requested_url, reason_code, http_status, final_url, age, cache_control, cache_status, preview_markers_found, outcome_markers_found, reference_period_match, verdict, faults, body_bytes, body_truncated, redirects, response_time_ms and error.

Fault row (fault): fault_class, url, release_at, phase, minutes_from_release, verdict, cdn_verdict, busted_verdict, copies_differ, the markers found, reference_period_match, checked_at and alert_status.

Summary row (summary): gate_pass, checked_at, simulated, release_count, rejected_count, urls_in_window, urls_checked, urls_not_checked, urls_outside_window, pre_release_count, post_release_count, verdict_counts, faults_by_class, faults_total, new_faults_by_class, new_faults_total, alerts_sent, alerts_failed, releases_outside_window (with window_opens_at and window_closes_at), the window settings, ledger_used, ledger_store and ledger_name.

gate_pass is true when no URL in window has a fault, each URL in window was checked, and no release record was rejected.

Webhook payload: the fault row without record_type and alert_status, plus "event": "release-fault-flagged".

Reason codes

OK, SERVER_ERROR, TIMEOUT, DNS_FAIL, CONNECT_FAIL, BLOCKED_TARGET, REDIRECT_LOOP, RUN_DEADLINE, CHARGE_LIMIT and INVALID_RELEASE. Only OK means that the Actor searched the copy.

Pricing: pay per event

EventUnitCounted as
run-startedone Actor runCharged one time, before the Actor requests a page. An input that is not usable is not charged.
release-url-checkedone release URL checked inside its window in one run, normal and cache-busted copyCharged one time for each release row with charged: true, that is, at least one copy was searched. A URL outside its window, a not-checked URL and a rejected record are not charged.
release-fault-flaggedthe first leak, stale-preview, late or period-mismatch fault of one class for one URLCharged one time for each fault row. The ledger stops a repeat charge for the same class and URL in later runs. A simulated run does not charge this event.

Before the Actor requests a page, it limits the URLs in window to the number of release-url-checked events that the maximum total charge of the run allows. The URLs outside that limit get the reason code CHARGE_LIMIT and are not requested.

Repeat use

Each entry in your release calendar is one window. Create an Apify Task with the releases of one calendar and one ledger_name, and schedule it every 5 minutes across the release window (for example with the cron */5 7-9 15 * * for a release at 08:30 on the 15th). Runs outside each window cost only the run-started event. After the release, the ledger shows the real go-live time and each fault class that the Actor found.

Local development

uv sync
uv run pytest
uv run ruff check .