Pagewright: Verified Page Screenshots avatar

Pagewright: Verified Page Screenshots

Pricing

$10.00 / 1,000 verified pages

Go to Apify Store
Pagewright: Verified Page Screenshots

Pagewright: Verified Page Screenshots

Full-page and viewport PNG screenshots, checked before they're returned. Blank renders, soft 404s served under HTTP 200, and error pages are refused with a reason instead of being passed off as successes. You aren't billed for a refused page.

Pricing

$10.00 / 1,000 verified pages

Rating

0.0

(0)

Developer

Brenden Bushman

Brenden Bushman

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

a month ago

Last modified

Share

Screenshots that are checked before they are returned, and refusals are never billed.

A screenshot Actor that hands back a blank image, a soft 404, or an error page has succeeded by every metric the platform records: the run exits SUCCEEDED, the dataset has a row, the image is there. The caller finds out later, usually by looking. Pagewright measures the render and refuses the ones that are not worth having.

What it does

  • Full-page or viewport PNG screenshots, configurable viewport width, height and device scale.
  • Batch: 1–500 URLs per run, one dataset item per URL whatever the outcome.
  • Wait strategy: network-idle (bounded, so it captures anyway if the network never settles, since plenty of ordinary pages hold a connection open indefinitely) or a fixed delay.
  • Four checks on every render, each reported separately.
  • A refused page emits no billable event at all. You are not charged for a screenshot the Actor itself would not stand behind.

The checks

CheckWhat it measuresRefuses?
httpOkthe response status was below 400yes → HTTP_ERROR
nonBlankthe image carries detail rather than being near-uniformyes → BLANK_RENDER
notErrorPagethe title and body lead carry no generic error-page wordingyes → ERROR_PAGE
consentDetecteda known consent platform was presentno, flag only

Two more refusals come from the browser itself: TIMEOUT and BLOCKED_BY_SITE. Those five are the whole vocabulary, and nothing outside it is ever returned.

Consent walls are flagged, not refused. They are common enough that refusing them would make the Actor useless, and you are better placed than we are to decide whether a cookie banner matters for your screenshot.

A check that could not run reports null, never true. If the response never arrived, or the image could not be decoded, or the page text could not be read, the check says so. In strict mode a null refuses, for the same reason a false does: neither one is evidence that the render is good.

Input

FieldTypeDefaultNotes
urlsstring[]nonerequired, 1 to 500
fullPagebooleanfalsewhole scrollable page instead of the viewport
viewportobject1280×800 @1×width, height, deviceScaleFactor
waitStrategynetworkIdle | delaynetworkIdle
waitMsinteger1000used by delay
timeoutMsinteger30000per page
verificationstrict | offstrictsee below

A value outside its declared bound is refused, not clamped. If your input gets quietly corrected, what ran is no longer what you asked for.

verification: "off"

Every check still runs and every result is still reported. off only stops them refusing. Opting out of enforcement is not opting out of information, so you can see exactly what a lenient run let through. A browser-level failure (timeout, blocked fetch) is still not a success.

Output

One dataset item per URL:

{
"url": "https://example.com",
"status": "ok",
"refusalReason": null,
"errorText": null,
"imageUrl": "https://api.apify.com/v2/key-value-stores/.../records/screenshot-0000.png",
"httpStatus": 200,
"renderedAt": "2026-07-30T18:20:11+00:00",
"bytes": 48213,
"dimensions": { "width": 1280, "height": 800 },
"checks": {
"httpOk": true,
"nonBlank": true,
"notErrorPage": true,
"consentDetected": false
}
}

status is ok, refused, or error. The checks object is present and complete on all three, so you can always see what was actually measured rather than just the verdict.

A refusal looks like this, and costs nothing:

{
"url": "https://example.com/missing",
"status": "refused",
"refusalReason": "ERROR_PAGE",
"errorText": null,
"imageUrl": null,
"httpStatus": 200,
"checks": { "httpOk": true, "nonBlank": true, "notErrorPage": false, "consentDetected": false }
}

Note the httpStatus: 200. That case is the reason this Actor exists.

A render that failed outright says why, and also costs nothing:

{
"url": "https://example.com/unreachable",
"status": "error",
"refusalReason": null,
"errorText": "net::ERR_NAME_NOT_RESOLVED; unmeasured: body_text, png, title",
"imageUrl": null,
"httpStatus": null,
"checks": { "httpOk": null, "nonBlank": null, "notErrorPage": null, "consentDetected": null }
}

On an error every measurement is absent, which is what makes it an error rather than a refusal, so errorText is the whole of what you have to go on. Read it, don't parse it: the wording can change between runs and carries no compatibility promise. Branch on status and refusalReason.

Pricing

Pay-per-event. page-rendered is charged once per verified success. Refusals and errors don't emit a billable event at all, so there's nothing to charge for.

Limits

  • PNG only. No JPEG, WebP or PDF.
  • Consent walls are detected, not dismissed.
  • No authenticated pages, no proxy rotation, no anti-bot evasion. You supply the URL; if a site blocks automated fetches, you get BLOCKED_BY_SITE and no charge.
  • notErrorPage reads the title plus the first 600 characters of body text. An error page leads with its message. Scanning a whole long article for the same phrases would start refusing pages that simply discuss outages.
  • The blankness check is a near-uniformity measure. A page whose entire content amounts to a few dozen pixels is treated as blank, because at that point you can't tell the two apart.

Development

py -3.14 -m venv .venv
.venv\Scripts\pip install -r requirements.txt
.venv\Scripts\python -m playwright install chromium
.venv\Scripts\python tools\run_tests.py # the whole guard suite
.venv\Scripts\python tools\ast_guard.py # structural guards over the AST
.venv\Scripts\python tools\build_input_schema.py # regenerate .actor/input_schema.json
.venv\Scripts\python tools\calibrate_entropy.py # re-measure the blankness band
.venv\Scripts\python tools\live_sample.py # measure the live refusal rate
.venv\Scripts\python tools\publication_gate.py # grade the retained sample

docs/ACCEPTANCE.md records which guard holds which clause of the governing contract, and what is deliberately not proven.