After a docs change, a pricing-page update or a client preview goes live, someone usually asks: can you show me what the public page looked like on desktop and on a phone? A screenshot dropped into Slack answers the visual question. It doesn't answer the follow-ups: is this the file we archived, when was it taken, which viewport produced it, and did the page actually load or was it a 404?
This walkthrough uses the Website Screenshot Evidence Packs Actor from SteadyScrape (steady-scrape) on Apify. You give it up to five public http or https URLs. For each one it saves a PNG in a fixed viewport, the SHA-256 of that file, the page's HTTP status and final URL, a UTC time from the run's clock, a JSON manifest and a ZIP. The result is an ordinary webpage capture with integrity hashes. It is not notarized, not legal proof, and not a guarantee of historical truth.
The problem
QA engineers, agency account teams and developers need a repeatable way to:
- Capture the same public URL at a fixed desktop size and a fixed mobile size.
- Hand a teammate both the PNG and a checksum they can recompute.
- Know whether the page really answered
200, or whether the "screenshot" is of an error page or a redirect. - Keep a manifest that says what happened to every attempt.
It's a poor fit for logged-in pages, personal profiles or long-term legal archives. The Actor opens each URL directly. It doesn't crawl links, submit forms, log in, solve captchas or run JavaScript you supply. It also doesn't try to decide whether a 200 page is a login wall or a challenge page; it records the status the server sent.
What a capture record contains
Each dataset row is one URL and one viewport.
| Field | What it means |
|---|---|
status | captured, partial, refused, blocked, unsupported, failed, empty or limit_exceeded |
url, finalUrl | Sanitized URL you asked for, and where the page ended up after redirects |
viewport | desktop or mobile |
httpStatus | HTTP status of the main page, recorded by the browser session |
sha256 | SHA-256 of the stored PNG |
capturedAt | UTC time from the run's clock, not a timestamp authority |
byteLength, width, height | Size of the stored PNG |
refusedSubresourceCount | Requests the browser refused because they pointed at private or local addresses |
usable | true only for a 2xx page with a stored PNG and nothing refused |
kvKey, archiveEntry | Where the PNG lives in the run's key-value store and in the ZIP |
reasonCode | Why a row is partial, refused or failed |
The run summary is in OUTPUT. The ZIP holds manifest.json plus PNGs named like captures/01-desktop-<hash>.png. It contains no HTML or scripts.
Running it in Apify Console
- Open Website Screenshot Evidence Packs.
- Paste one to five public URLs. Don't add usernames, passwords or tokens; credential URLs are refused.
- Set
viewportstodesktop,mobileor both. Desktop is 1280×800, mobile is 390×844. - Leave
fullPageoff for a viewport-only shot. With it on, the capture is the full page, cut off at 4,000 pixels tall. - Run it, then open the dataset (one row per URL and viewport) and the key-value store (
OUTPUT, the manifest, the PNGs and the ZIP).
{ "urls": ["https://example.com/"], "viewports": ["desktop", "mobile"], "fullPage": false } Calling it from Python
import os from apify_client import ApifyClient client = ApifyClient(os.environ["APIFY_TOKEN"]) run = client.actor("steady-scrape/website-screenshot-evidence-packs").call( run_input={ "urls": ["https://example.com/", "https://www.iana.org/help/example-domains"], "viewports": ["desktop", "mobile"], "fullPage": False, } ) for row in client.dataset(run["defaultDatasetId"]).iterate_items(): print(row["status"], row["viewport"], row.get("httpStatus"), row.get("sha256"), row["url"]) To check a file later, download the PNG (or the ZIP from the archiveKey in OUTPUT) and compare:
sha256sum captures/01-desktop-*.png A real row
From a public run on https://example.com/ (desktop):
{ "url": "https://example.com/", "finalUrl": "https://example.com/", "viewport": "desktop", "status": "captured", "reasonCode": null, "httpStatus": 200, "capturedAt": "2026-11-10T22:49:06.620Z", "sha256": "9dab6e635ca5a3f4c2220115121db3387da42e6890aae19da30d0395e2039066", "byteLength": 74812, "width": 1280, "height": 800, "fullPage": false, "archiveEntry": "captures/01-desktop-9dab6e635ca5.png", "refusedSubresourceCount": 0, "usable": true } If a URL points at a private address (say http://169.254.169.254/), the row is refused with reasonCode non_public_address and nothing is fetched. A page that answers 404 or 500 still gets a PNG, but the row is partial with not_success_http_status.
Re-running later against a live page can produce different bytes and a different sha256. That's the point: the hash tells you exactly which file you're looking at.
What it costs
Pay per event:
- $0.003 per usable screenshot (one viewport of a page that answered 2xx, with nothing refused).
- $0.003 once per run that saved at least one usable screenshot.
| What you run | Math | Total |
|---|---|---|
| 1 URL, desktop only | 1 × $0.003 + $0.003 | $0.006 |
| 1 URL, desktop + mobile | 2 × $0.003 + $0.003 | $0.009 |
| 5 URLs × both viewports | 10 × $0.003 + $0.003 | $0.033 |
Refused URLs, failed loads, timeouts, non-2xx pages and partial captures are free, and a run with no usable screenshot pays nothing. There's no start fee or compute charge on top, and you can set a maximum charge per run.
Limits
- Up to 5 URLs and 10 screenshots per run, one at a time.
- Desktop 1280×800, mobile 390×844, device scale 1.
- About 15 seconds to load each page; 180 seconds per run.
- Public
http/httpsonly. Userinfo, credential query keys,localhost,.local,.internaland private or reserved IPs are refused. - Retention follows your Apify plan. The manifest's timestamp doesn't delete anything, so download the ZIP if you need it longer.
Scheduling and integrations
Start runs from Console, the Apify API or an Apify schedule (for example, every Monday on your top pages). Make, Zapier and n8n Apify modules, or Apify's MCP server for AI agents, can start the same run.
FAQ
Is a screenshot legal proof?
No. It's a webpage capture with a hash of the bytes this run stored, timed by the run's clock.
Can I screenshot a logged-in page?
No. Cookies, headers, passwords and proxy credentials aren't accepted.
Am I charged for a 404 or a failed page?
No. Only 2xx pages with a stored PNG and nothing refused are charged.
What sizes are desktop and mobile?
1280×800 and 390×844, fixed.
Try it: Website Screenshot Evidence Packs on Apify. More tools from SteadyScrape (steady-scrape) are on our Apify profile.