Universal Screenshot API avatar

Universal Screenshot API

Pricing

from $6.00 / 1,000 screenshots

Go to Apify Store
Universal Screenshot API

Universal Screenshot API

Batch website screenshot and PDF capture with full-page-by-default rendering, dark mode, ad/tracker/cookie-banner blocking, SSRF-safe URL validation, and an explicit error taxonomy.

Pricing

from $6.00 / 1,000 screenshots

Rating

0.0

(0)

Developer

Barak Eliov

Barak Eliov

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Batch website screenshot and PDF capture. Full-page-by-default rendering, dark mode, ad/tracker/cookie-banner blocking, SSRF-safe URL validation, and an explicit error taxonomy with no charge on failure.

This is v1 (P0 feature set only, per the approved spec in SPEC.md). See "What's not in v1 yet" below.

What this Actor does

Give it a list of URLs and it captures a PNG/JPEG/WebP screenshot and/or a PDF for each one, using a real headless Chromium browser (Crawlee's PlaywrightCrawler). Every input URL always produces at least one dataset row per requested output type - successes and failures alike - so you never get a silently missing result.

Input example

{
"urls": ["https://apify.com", "https://example.com"],
"outputs": ["screenshot"],
"fullPage": true,
"format": "png",
"colorScheme": "no-preference"
}

Full P0 input fields (all have defaults; see .actor/input_schema.json for the authoritative list): urls, outputs, format, quality, fullPage, device, viewportWidth, viewportHeight, deviceScaleFactor, waitUntil, waitForSelector, delayMs, timeoutSecs, scrollToBottom, colorScheme, selectorsToHide, blockAds, blockTrackers, hideCookieBanners, stealthMode, proxyConfiguration. Plus cookies (P1, pre-approved - see below).

Output example

One row per URL per requested output type (real output from a local run against https://example.com):

{
"url": "https://example.com",
"finalUrl": "https://example.com/",
"status": "success",
"outputType": "screenshot",
"device": "desktop",
"format": "png",
"width": 1280,
"height": 800,
"pdfPageCount": null,
"fileSizeBytes": 11423,
"assetUrl": "https://api.apify.com/v2/key-value-stores/.../records/screenshot-...png",
"mimeType": "image/png",
"captureTimeMs": 1083,
"cacheHit": false,
"waitForSelectorTimedOut": false,
"fullPageHeightCapped": false,
"consoleErrorCount": null,
"extractedText": null,
"extractedLinks": null,
"extractedMetadata": null,
"errorCategory": null,
"errorMessage": null,
"retryCount": 0,
"scrapedAt": "2026-09-30T17:40:12.858Z"
}

A failed URL gets "status": "error", errorCategory set to one of blocked / not_found / timeout / render_error / invalid_url / upstream_changed, and every capture-specific field left null. Failed rows are never charged.

Errors (never silent, never charged)

CategoryWhen
invalid_urlMalformed URL, non-http(s) URL, or the URL resolves to a private/link-local/loopback/cloud-metadata IP address (SSRF protection, always on)
not_foundDNS failure, connection refused, 404/410
blocked401/403/429 or a bot-challenge page
timeoutNavigation/render exceeded timeoutSecs
render_errorBrowser crash or an unclassified render failure
upstream_changedReserved for graceful-degradation cases (page structure changed)

A waitForSelector that never appears is not an error: the page is still captured and waitForSelectorTimedOut: true is reported (and the successful output is charged normally).

SSRF protection (always on)

Before any browser context is opened for a URL, its hostname is DNS-resolved and every resolved IP is checked against RFC1918/link-local/loopback/CGNAT/cloud-metadata ranges (IPv4 and IPv6). A hit is rejected as invalid_url with zero navigation attempted. This is mandatory and not a togglable input.

Known limitation: this is a pre-navigation check, not a pinned connection. A target that changes DNS or redirects to a private address after the check passed would not be caught by this v1 (a real gap shared with most screenshot tools; closing it fully needs IP-pinned egress, out of scope for v1). An earlier design also re-validated the connected IP after navigation as extra defense-in-depth, but this was removed after local testing showed it produces false positives on any network path that goes through a local/corporate HTTP proxy or VPN (common setups) - it broke normal captures on non-proxied networks too aggressively to justify keeping.

blockAds/blockTrackers intercept requests to a small, curated list of well-known ad and tracker domains (see src/blocklist.ts). hideCookieBanners hides a small list of well-known cookie-consent banner selectors via CSS. Both lists are intentionally short for v1 and easy to extend - they are not a full filter-list replacement (e.g. EasyList).

Cookies (capture your own authenticated pages)

The cookies input (P1, pre-approved) lets you supply session cookies to capture pages behind your own login, e.g.:

"cookies": [{ "name": "session", "value": "abc123", "domain": "example.com", "path": "/" }]

Supply only credentials you own or are authorized to use. This does not scrape third-party logged-in areas; it lets you authenticate your own session before capture.

Devices

device selects a realistic viewport + pixel-density + user-agent preset: desktop, desktop_hd, laptop, tablet, mobile, iphone_15, pixel_8, ipad, or custom (use with viewportWidth/ viewportHeight). deviceScaleFactor left at its default (1) uses the preset's own pixel density; any other value overrides it.

Limits

  • Max 100 URLs per run (internal safety cap; there is no maxItems input field in v1).
  • Max 120s timeoutSecs, max 60s delayMs per URL. timeoutSecs bounds the entire per-URL pipeline (navigation, waitForSelector, auto-scroll, and the final screenshot/PDF capture) - exceeding it at any stage reports errorCategory: "timeout" for that URL and is not charged.
  • Full-page height is internally capped at 20,000px, a cost/reliability safety limit (not a Chromium limit - Chromium itself can render far taller pages). Pages taller than the cap are captured up to 20,000px and fullPageHeightCapped: true is set in the output row; shorter pages are unaffected and report fullPageHeightCapped: false.
  • WebP output is produced by capturing PNG and converting with sharp (Chromium's native screenshot API only supports png/jpeg).
  • stealthMode masks common automation fingerprints; it does not defeat CAPTCHAs or paywalls.
  • No login/CAPTCHA solving beyond the cookies feature above (your own sessions only).

Use with AI agents / MCP

Call the Actor with urls as an array (never a comma-separated string). Check status per row; only status: "success" rows have a populated assetUrl. outputType tells you whether a row is a screenshot or a pdf. Batch multiple URLs in one call instead of one run per URL.

FAQ

Am I charged for failed URLs or a waitForSelector timeout? No charge for failed rows. A waitForSelectorTimedOut: true row that still successfully produced an image/PDF is charged - a screenshot was in fact delivered.

Does this bypass CAPTCHAs or paywalls? No. stealthMode only reduces the chance of being blocked by basic bot detection.

Can I capture a page that needs a login? Only your own pages, via the cookies input. This Actor does not scrape third-party logged-in areas.

What's not in v1 yet (P1/P2, explicitly deferred)

devices multi-device fan-out, selectorsToBlur, elementSelector (single-element capture), customCss, omitBackground, acceptCookieConsent, blockImages, userAgent/locale/timezone/geolocation overrides, extractData (text/links/metadata/console extraction - the output fields exist and are always null for forward compatibility), pdfFormat/pdfLandscape/pdfPrintBackground (PDF is currently always A4/portrait/backgrounds-on), and a user-configurable maxConcurrency (internally fixed at 5). cookies is the one P1 field implemented in this build (explicitly pre-approved - see SPEC.md).

Local development

npm install
npx playwright install chromium # one-time, downloads a browser to the shared Playwright cache
npm run typecheck
npm test
npm run build
apify run --input-file smoke-input.json --purge