Website Screenshot API
Pricing
from $5.00 / 1,000 screenshots
Website Screenshot API
Screenshot any list of URLs as PNG, JPEG or WebP: desktop, mobile, tablet or custom size, full page or a single element. Hides cookie banners and ads. Pay only for successful screenshots.
Pricing
from $5.00 / 1,000 screenshots
Rating
0.0
(0)
Developer
Djaber Guassmi
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
3 days ago
Last modified
Categories
Share
Take screenshots of any list of web pages: desktop, mobile, tablet or a custom size; the visible viewport, the full scrolling page, or a single element. Get PNG, JPEG or WebP images with cookie banners and ads removed. You pay only for screenshots that succeed. Failed URLs cost nothing.
What it does
For each URL in urls, the Actor:
- Opens the page in a real Chromium browser that emulates the device you picked.
- Waits until the page is loaded (
waitUntil), without hanging on sites that never stop loading. - Removes cookie and consent popups and ads, and loads lazy images if you want the full page.
- Saves the image to the run's key-value store.
- Adds one dataset item per URL with the image link, page title, image size and an
ok/failedstatus plus an error message when something went wrong.
One bad URL never stops the run. Temporary errors (timeouts, network errors, HTTP 429/5xx) are retried with exponential backoff, and every attempt has a hard time limit.
Why use this instead of other screenshot Actors
It was built to fix the most common complaints in reviews and issues of other screenshot tools:
| Common complaint | What this Actor does |
|---|---|
| "It still used up my credits" when the result didn't work | Pay per successful screenshot. Failed, skipped and 404 URLs are never charged. |
| Slow and expensive (minutes and cents per screenshot) | A fixed, low price per screenshot. One shared browser, pages captured in parallel, ads blocked. |
| Full page only shows the part above the fold; missing lazy-loaded images | Real full-page capture in one pass, after scrolling through the page so lazy images load. |
| Endless scrolling makes runs take forever | Lazy-load scrolling is limited to 10 seconds and maxFullPageHeight. |
| Cookie banners cover the content | Banners are dismissed or hidden by default, and the scroll lock they leave behind is removed. |
| Sticky headers repeated across the image | No stitching, so a sticky header appears once, at the top. Hide it completely with hideSelectors. |
| Can't set the image height / can't capture only the top or one part | custom device with any width/height, or selector to capture one element. |
| Runs time out or hang forever | Per-attempt timeout, and a "soft" wait: if a page never reaches networkidle, it's captured anyway. |
| Out-of-memory crashes and frozen runs | Parallelism is automatically limited to what the run's memory and CPU can handle. |
| Emoji and Asian text render as boxes | Emoji and CJK fonts are installed. |
Certificate errors (ERR_CERT_AUTHORITY_INVALID) | Pages with invalid or self-signed certificates are still captured. |
| No dataset output, hard to use from the API | Every URL produces one dataset item, so run-sync-get-dataset-items returns everything you need. |
Input example
Only urls is required. Everything else is optional.
{"urls": ["https://apify.com", "news.ycombinator.com", "https://example.com/missing-page"],"device": "mobile","fullPage": true,"format": "webp","quality": 80,"waitUntil": "load","extraDelayMs": 500,"hideCookieBanners": true,"blockAds": true,"darkMode": false,"concurrency": 5}
All input fields
| Field | Type | Default | Description |
|---|---|---|---|
urls | string[] | (required) | Pages to capture. https:// is added if missing. |
device | desktop | mobile | tablet | custom | desktop | Desktop 1920x1080; mobile 390x844 @2x; tablet 820x1180 @2x; custom uses width/height/deviceScaleFactor. |
width, height | integer | 1920, 1080 | Viewport size for custom. |
deviceScaleFactor | number | 1 | Pixel ratio for custom (1 to 3). |
fullPage | boolean | false | Capture the whole page instead of the viewport. |
selector | string | (none) | CSS selector: capture only this element. |
format | png | jpeg | webp | png | Image format. |
quality | integer | 80 | 1 to 100, for jpeg and webp. |
waitUntil | load | domcontentloaded | networkidle | load | Load event to wait for (soft: never fails the URL). |
extraDelayMs | integer | 0 | Extra wait before the capture. |
hideCookieBanners | boolean | true | Dismiss or hide cookie and consent popups. |
blockAds | boolean | true | Block ad and tracker requests and hide empty ad slots. |
darkMode | boolean | false | Emulate prefers-color-scheme: dark. |
concurrency | integer | 5 | Max pages in parallel; capped at 2 per CPU core (2 GB = 1 page, 8 GB = 4). |
timeoutSecs | integer | 60 | Time limit for one attempt at one URL. |
maxRetries | integer | 1 | Retries for temporary errors, with 1 s, 2 s, 4 s... backoff. |
hideSelectors | string[] | [] | Extra CSS selectors to hide (chat widgets, sticky headers...). |
scrollToLoadLazyContent | boolean | true | With fullPage, scroll first so lazy content loads. |
maxFullPageHeight | integer | 15000 | Height cap in CSS pixels for full-page and element shots. |
proxyConfiguration | object | (none) | Optional proxy for sites that block data centers. |
Output example
One dataset item per input URL, in the same form for successes and failures:
[{"url": "https://apify.com","finalUrl": "https://apify.com/","screenshotUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/screenshot_0001_apify-com.webp","screenshotKey": "screenshot_0001_apify-com.webp","title": "Apify: Full-stack web scraping and data extraction platform","width": 780,"height": 14230,"format": "webp","device": "mobile","status": "ok","error": null,"httpStatus": 200,"truncated": false,"attempts": 1,"takenAt": "2026-10-17T19:30:12.511Z","billedAs": "full-page-screenshot"},{"url": "https://example.com/missing-page","finalUrl": null,"screenshotUrl": null,"screenshotKey": null,"title": null,"width": null,"height": null,"format": "webp","device": "mobile","status": "failed","error": "HTTP 404 Not Found","httpStatus": 404,"truncated": false,"attempts": 1,"takenAt": "2026-10-17T19:30:09.004Z","billedAs": null}]
screenshotUrlopens the image directly. The same file is in the run's default key-value store underscreenshotKey(screenshot_<position in input>_<host>.<format>).widthandheightare the real pixel dimensions of the image (viewport x pixel ratio).statusisokonly when the image was stored. Onlyokitems are charged;billedAssays whether asscreenshotorfull-page-screenshot(null= not charged).truncated: truemeans the page was taller thanmaxFullPageHeightand was cut off.
Pricing
This Actor uses pay-per-event pricing:
| Event | Price | When it's charged |
|---|---|---|
screenshot | $0.005 per screenshot ($5 per 1,000) | Viewport or element (selector) screenshot, once the image is stored |
full-page-screenshot | $0.01 per full-page screenshot ($10 per 1,000) | fullPage: true screenshot, once the image is stored |
Full-page screenshots cost more because the whole page is rendered and encoded (often 10-15x the
pixels of one screen). If you only need what's visible, leave fullPage off. Each dataset item's
billedAs field shows which event it was charged as.
- Failed URLs (timeouts, 404s, unknown domains, missing selector, invalid URL) are never charged.
- Retries are free: a URL that succeeds on its third attempt is charged once.
- Platform compute is included in the price, so slow sites don't cost you more.
- If you set a maximum cost per run, the Actor stops starting new screenshots when the limit is
reached and reports the rest as
failedwith the reason "maximum cost per run was reached" (not charged).
Use from the API, code or AI agents
For AI agents (Apify MCP server): call this Actor with {"urls": [...]} and read the dataset
items. Every input URL gets exactly one item. Check status: when it's "ok", use screenshotUrl.
When it's "failed", error says why in plain language and nothing was charged. Use
device: "mobile" for a phone view, selector for one element, and fullPage: true only when the
whole page is needed (it costs $0.01 instead of $0.005).
One HTTP call, results back directly (waits until the run finishes):
curl -X POST "https://api.apify.com/v2/acts/<username>~website-screenshot-api/run-sync-get-dataset-items?token=<YOUR_TOKEN>" \-H "Content-Type: application/json" \-d '{"urls": ["https://apify.com"], "fullPage": true, "format": "jpeg"}'
JavaScript:
import { ApifyClient } from 'apify-client';const client = new ApifyClient({ token: process.env.APIFY_TOKEN });const run = await client.actor('<username>/website-screenshot-api').call({ urls: ['https://apify.com'] });const { items } = await client.dataset(run.defaultDatasetId).listItems();console.log(items.map((i) => (i.status === 'ok' ? i.screenshotUrl : `${i.url}: ${i.error}`)));
Monitoring pages: schedule the Actor (for example daily) to keep a visual history of a page.
Tips and limitations
- SPAs and dashboards: use
waitUntil: "networkidle"and maybeextraDelayMs: 1000. - Element not found: the URL fails with
No element matches selector "..."and isn't charged. Check the selector in your browser's DevTools. - Bot protection: some sites (Cloudflare challenges, login walls) answer with 403 or a challenge
page. A 403 is reported as
failed; tryproxyConfigurationwith residential proxies. - HTTP errors: pages that answer with HTTP 4xx/5xx are reported as
failedand not charged, even if they show an error page. - Very long pages are cut at
maxFullPageHeight(default 15,000 px). WebP images can't be taller than 16,383 pixels, so WebP full-page shots are limited to that height. - Cookie banner removal covers the common consent platforms and thousands of site-specific banners,
but not every one in existence. Add stubborn ones to
hideSelectors.