# Website Screenshot API: Full Page, Bulk & PDF (`tobbyb/website-screenshot`) Actor

Website screenshot API for bulk, true full-page captures from URLs or sitemaps: lazy images loaded, sticky headers and cookie banners removed. PNG/JPEG/WebP/PDF, change detection. No subscription: $0.004 per screenshot, under half the entry-plan rate of ScreenshotOne and Urlbox. Failed URLs free.

- **URL**: https://apify.com/tobbyb/website-screenshot.md
- **Developed by:** [Tobby Tools](https://apify.com/tobbyb) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Stats:** 1 total users, 0 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 screenshots

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Website Screenshot API: Full Page, Bulk & PDF

**Full-page website screenshots that are actually complete: in bulk, from URLs or a sitemap, for $0.004 each with no subscription.**

| Proof | |
|---|---|
| Won a 30-site head-to-head on Apify (18 Oct 2026) | **24 / 30** complete and visually correct, vs 19 and 18 for two other screenshot Actors ([how we tested](#how-it-compares)) |
| Price | $0.004 per screenshot, **less than half** the per-screenshot price of the entry plans at ScreenshotOne, Urlbox and ScreenshotAPI.net ([price table](#price-comparison-with-screenshot-apis)) |
| Failed URLs | **Free**. A page that can't be captured comes back as an error item with a fix, not as a picture of a bot wall |
| Open examples | Copy-paste curl, Node.js, Python, n8n, Google Sheets and GitHub Actions examples: [github.com/tobbytools/website-screenshot-examples](https://github.com/tobbytools/website-screenshot-examples) |

Take a **website screenshot** of any public page, or **bulk screenshots** of thousands of URLs or a whole sitemap, in one run. You get a **true full page screenshot**: lazy-loaded images are scrolled into view, sticky and fixed headers are not repeated down the page, cookie banners are dismissed, and emoji and Chinese/Japanese/Korean text render correctly. Export to PNG, JPEG, WebP or PDF and use it as a **screenshot API** from Make, Zapier, n8n, Google Sheets or your own code.

**No monthly subscription: $0.004 per screenshot, and you pay only for screenshots delivered.** That is less than half the per-screenshot price of the entry plans at ScreenshotOne ($0.0085), Urlbox ($0.0098) and ScreenshotAPI.net ($0.009) (list prices checked 18 Oct 2026, see the [price comparison](#price-comparison-with-screenshot-apis)).

### What this website screenshot tool does

- **Full page screenshots that are actually complete.** The page is scrolled so lazy images load. Tall pages up to 30,000 px are captured in slices without the blank blocks, cut-offs or repeated headers other tools leave. Every result says whether the page was captured completely (`fullPageComplete`).
- **Bulk screenshots.** Paste a list of URLs, or give a `sitemap.xml` and every page is captured. Runs in parallel with automatic retries.
- **Devices.** Desktop (1440 px), Full HD, laptop, tablet (iPad) and mobile (iPhone), or set your own viewport width, height and pixel ratio.
- **Formats.** PNG, JPEG, WebP and PDF (A4/Letter, screen or print CSS), alone or together.
- **Element screenshots.** Capture just one element with a CSS selector, such as a pricing table, chart or tweet embed.
- **Waiting.** Wait for a selector to appear, add a delay, or choose the load event.
- **Clean shots.** Cookie and consent banners are blocked or accepted, ad slots are collapsed, chat widgets and pop-ups are hidden. You can hide your own selectors, click elements first, or inject CSS.
- **Dark mode, reduced motion, locale, time zone, cookies, custom user agent,** plus optional Apify Proxy.
- **Visual change detection.** Give the run a monitor name and every run is compared with the previous one. You get the % of pixels changed, a `changed` flag over your threshold, and a red-highlighted diff image. Schedule it to watch competitors, landing pages or regressions.
- **Clear errors, never charged.** A URL that cannot be captured (DNS failure, 404, timeout, bot wall) comes back as an error item that says what went wrong and how to fix it, and you are not charged for it.
- **Google Sheets-friendly output.** Every row has a ready-made `=IMAGE("…")` formula and a flat "Sheets" dataset view. A `GALLERY.html` report shows every screenshot on one page.

### Use cases

- **Competitor and landing-page monitoring.** Schedule a run with `changeDetectionName`; you get the % of pixels changed, a `changed` flag and a red-highlighted diff image when a page moves.
- **Visual QA after a deploy.** Capture every page in your `sitemap.xml` on desktop and mobile and compare with the last run.
- **Client and SEO reports.** Export the "Sheets" view: every row has a ready-made `=IMAGE()` formula, and `GALLERY.html` shows all shots on one page.
- **Archiving and evidence.** Full-height PNG/JPEG or PDF of a page as it looked today, with `fullPageComplete` telling you whether the whole page was captured.
- **Page images for AI agents and automations.** Call it from n8n, Make, Zapier, the Apify API, or Apify's MCP server so an agent can "look" at a page.

### Quick start: how to take a full page screenshot

1. Add one or more URLs (or a sitemap URL).
2. Pick devices and formats. The defaults are desktop, JPEG, full page.
3. Click **Start**. Each screenshot is in the **Output** tab and the key-value store. Open `GALLERY.html` for a visual overview.

To call it as a screenshot API, run the Actor synchronously and read the dataset:

```bash
curl -X POST "https://api.apify.com/v2/acts/tobbyb~website-screenshot/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls":["https://stripe.com"],"devices":["desktop","mobile"],"formats":["png"]}'
```

### Input example

```json
{
  "urls": ["https://stripe.com", "https://www.theguardian.com/international"],
  "sitemapUrls": [],
  "devices": ["desktop", "mobile"],
  "formats": ["jpeg", "pdf"],
  "fullPage": true,
  "waitForSelector": "",
  "darkMode": false,
  "changeDetectionName": "competitor-homepages",
  "changeThresholdPercent": "1"
}
```

### Output example (real result)

```json
{
  "status": "ok",
  "url": "https://stripe.com/",
  "title": "Stripe | Financial Infrastructure to Grow Your Revenue",
  "device": "desktop",
  "viewport": "1440x900@1x",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/<store-id>/records/stripe-com-desktop-06a231.jpg?signature=…",
  "format": "jpeg",
  "imageWidth": 1440,
  "imageHeight": 15455,
  "fileSizeBytes": 2196536,
  "captureMode": "full-page",
  "pageHeight": 15455,
  "capturedHeight": 15455,
  "fullPageComplete": true,
  "httpStatus": 200,
  "stickyElementsFixed": 1,
  "overlaysHidden": 1,
  "lazyLoadScrollSteps": 2,
  "totalTimeMs": 22246,
  "sheetsImageFormula": "=IMAGE(\"https://api.apify.com/v2/key-value-stores/<store-id>/records/stripe-com-desktop-06a231.jpg?signature=…\")",
  "change": null,
  "charged": true
}
```

An error item (not charged):

```json
{
  "status": "error",
  "url": "https://medium.com/",
  "errorCode": "BLOCKED",
  "errorTitle": "Blocked by a bot check",
  "howToFix": "The site showed a CAPTCHA / \"verify you are human\" page. This Actor does not bypass bot protection. Try the proxy, capture a different page, or get permission from the site owner.",
  "charged": false
}
```

### How it compares

We captured the same 30 real pages (Stripe, the Guardian, BBC, Wikipedia, Vercel, emoji sites, Japanese Wikipedia, news sites with sticky headers and lazy-loaded feeds) with three Actors on the Apify platform, on 18 Oct 2026:

| | This Actor | Generic screenshot Actor A | Budget screenshot API B |
|---|---|---|---|
| Complete & visually correct | **24 / 30** | 19 / 30 | 18 / 30 |
| Full page captured (reachable pages) | **27 / 28** | 25 / 28 | 21 / 28 |
| Colour emoji | ✅ | ❌ (mono/boxes) | ✅ |
| Consent / sign-in pop-ups left in the image | 1 | 4 | 3 |
| Bot-blocked pages | free error item with a fix | image of the block page | image of the block page |
| Time for the 30 URLs | 652 s | more than 2,488 s (stopped by us at 29 URLs) | 258 s |

Evidence: side-by-side images of all 30 pages and every competitor run ID are in [the example repo's benchmark folder](https://github.com/tobbytools/website-screenshot-examples/tree/main/benchmark). 13 of the 30 rows were judged on captured height vs. true page height only; the rest were inspected visually.

### Price comparison with screenshot APIs

List prices from each provider's public pricing page, **checked 18 Oct 2026**, monthly billing. "Per screenshot" assumes you use the whole monthly quota; if you use less, a subscription costs more per screenshot. We compared prices only, not output quality.

| | Monthly fee | Included | Per screenshot | 1,000 screenshots/month costs |
|---|---|---|---|---|
| **This Actor** | **none** | pay per use | **$0.004** | **$4** |
| ScreenshotOne Basic | $17 | 2,000 | $0.0085 | $17 |
| Urlbox Hi-Fi | $49 | 5,000 | $0.0098 | $49 |
| ScreenshotAPI.net Essential | $9 | 1,000 | $0.009 | $9 |
| ApiFlash Lite | $7 | 1,000 | $0.007 | $7 |

At higher volumes, bigger plans can cost less per screenshot than this Actor (for example ApiFlash Medium, $35 for 10,000 = $0.0035). Scraping APIs with a screenshot option can also be cheaper if you use the whole quota (ScrapingBee Hobby: $19 for 15,000 JavaScript-rendered screenshots). Urlbox's $19 Lo-Fi plan (2,000) is for sites you own and gives only thumbnails of other sites. ScreenshotOne and ApiFlash also have free tiers of 100 screenshots a month. Here, a full page screenshot counts as one screenshot, however tall it is, and failed URLs are free.

### FAQ

**How long can a full page screenshot be?** Up to 30,000 px by default (`maxHeight`). Very tall pages are cut there and flagged with `fullPageComplete: false` and `truncatedReason`, so you always know.

**Why is my screenshot missing content further down?** Some sites load content only while you scroll. This happens automatically. If a part still loads slowly, add `waitForSelector` or a `delayMs`.

**Can it capture pages behind a login?** Pass session `cookies` for the site. Only capture pages you are allowed to access.

**Does it bypass CAPTCHAs or bot protection?** No. Blocked pages come back as a free error item with advice. You can try Apify Proxy.

**Can I screenshot just one element?** Yes. Set `selector` to a CSS selector.

**How do I monitor a page for visual changes?** Set `changeDetectionName` and schedule the Actor. Each run compares with the previous one and reports `change.diffPercent`, `change.changed` and a diff image.

**How do I show screenshots in Google Sheets?** Export the "Sheets" view, or use the `sheetsImageFormula` column directly. The image URLs are signed and keep working.

**Is there a free trial?** Apify's free plan includes monthly platform credit you can spend on this Actor. A run with the default input (one desktop JPEG of example.com) costs one screenshot, $0.004.

**How do I call it from code?** Use the `run-sync-get-dataset-items` endpoint shown in the quick start, or the `apify-client` package for Node.js or Python. Ready-to-run examples: [github.com/tobbytools/website-screenshot-examples](https://github.com/tobbytools/website-screenshot-examples).

**What am I charged for?** $0.004 per screenshot file delivered (one URL × device × format). There is no subscription or start fee. Failed URLs and error items are free. You can cap spend with the run's maximum cost setting, and the run stops before going over it.

**Is it legal to take screenshots of websites?** Capturing public pages for monitoring, archiving, QA or research is common. Respect each site's terms and copyright, don't capture personal data you have no right to process, and don't republish screenshots in ways the rights holder hasn't allowed.

# Actor input Schema

## `urls` (type: `array`):

Pages to capture, one per line. Bare domains like example.com work too. Each URL × device is one screenshot.

## `sitemapUrls` (type: `array`):

Sitemap XML URLs (indexes and .xml.gz are fine) or a site homepage, in which case the sitemap is found via robots.txt. Every page URL in them is captured, up to "Max URLs from sitemaps".

## `maxSitemapUrls` (type: `integer`):

Upper limit on page URLs taken from the sitemaps.

## `devices` (type: `array`):

Pick one or more. Each device is a separate screenshot (and a separate charge).

## `formats` (type: `array`):

One or more. Each file is charged as one screenshot. WebP pages taller than 16,383 px are saved as JPEG.

## `fullPage` (type: `boolean`):

Capture the whole scrollable page (on) or just the first screen (off).

## `maxHeight` (type: `integer`):

Safety cap for endless pages. If a page is taller, the first part is captured and the result says so (fullPageComplete=false). Use it to grab only the top N pixels too.

## `selector` (type: `string`):

Capture only this element, e.g. #pricing or .hero. Leave empty for the whole page.

## `viewportWidth` (type: `integer`):

Overrides the device width (e.g. 1366). Leave empty to use device presets.

## `viewportHeight` (type: `integer`):

Viewport height for custom size (also the height of a non-full-page shot).

## `deviceScaleFactor` (type: `string`):

Override the device pixel ratio. 2 = retina. Bigger files.

## `quality` (type: `integer`):

Higher = sharper and larger files.

## `pdfFormat` (type: `string`):

single-page = one long page exactly like the screen.

## `pdfMedia` (type: `string`):

screen = looks like the website; print = the site's print stylesheet.

## `waitUntil` (type: `string`):

When the page counts as loaded.

## `waitForSelector` (type: `string`):

Wait until this element is visible before capturing (e.g. a chart or price).

## `waitForSelectorTimeoutSecs` (type: `integer`):

Give up and return a free WAIT_FOR_SELECTOR_TIMEOUT error after this.

## `delayMs` (type: `integer`):

Additional wait before capturing, for animations or slow widgets.

## `scrollToLoadLazyContent` (type: `boolean`):

Scrolls through the page first so lazy-loaded images and scroll-triggered sections render. Fast (about 1–4 s per page).

## `dismissCookieBanners` (type: `boolean`):

Blocks common consent-manager scripts, clicks "Accept" when a banner still appears, and hides leftovers.

## `blockAds` (type: `boolean`):

Faster loads and no empty ad boxes. Also hides chat widgets.

## `stickyElements` (type: `string`):

How fixed headers, chat bubbles and bottom bars are handled on full-page shots.

## `hideSelectors` (type: `array`):

Anything else to remove before capturing, e.g. .promo-bar.

## `clickSelectors` (type: `array`):

Elements to click before capturing (e.g. a tab or "Show more").

## `customCss` (type: `string`):

Injected before capturing.

## `darkMode` (type: `boolean`):

Emulates prefers-color-scheme: dark.

## `reducedMotion` (type: `boolean`):

Asks the site to skip animations (prefers-reduced-motion), so nothing is captured mid-fade.

## `userAgent` (type: `string`):

Override the browser user agent.

## `locale` (type: `string`):

Accept-Language / navigator.language, e.g. de-DE.

## `timezone` (type: `string`):

IANA time zone, e.g. Europe/Berlin.

## `cookies` (type: `array`):

Cookies to set, e.g. \[{"name":"session","value":"…","domain":".example.com"}]. Only for pages you are allowed to access.

## `ignoreHttpsErrors` (type: `boolean`):

Capture sites with expired/self-signed certificates.

## `captureErrorPages` (type: `boolean`):

Also capture pages that answer 4xx/5xx (charged). Off = they become free error items.

## `proxyConfiguration` (type: `object`):

Optional. Use Apify Proxy for sites that block datacenter IPs. No CAPTCHA or bot-check bypass.

## `changeDetectionName` (type: `string`):

Turn on visual change detection: give the monitor a name (e.g. competitors). Each run compares every page with the previous run under this name and reports diffPercent plus a diff image. Schedule the Actor to monitor.

## `changeThresholdPercent` (type: `string`):

A page counts as changed when at least this % of pixels differ.

## `saveDiffImage` (type: `boolean`):

Saves an image with changed pixels highlighted in red.

## `maxUrls` (type: `integer`):

Hard cap on URL × device captures in one run.

## `maxConcurrency` (type: `integer`):

Pages captured in parallel. Default: 3 per 2 GB of memory.

## `pageTimeoutSecs` (type: `integer`):

Per page. Slow pages fail with a free TIMEOUT item.

## `maxRetries` (type: `integer`):

Retries for timeouts and connection errors.

## `galleryReport` (type: `boolean`):

Saves a GALLERY page with thumbnails, change badges and errors.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "maxSitemapUrls": 100,
  "devices": [
    "desktop"
  ],
  "formats": [
    "jpeg"
  ],
  "fullPage": true,
  "maxHeight": 30000,
  "quality": 85,
  "pdfFormat": "A4",
  "pdfMedia": "screen",
  "waitUntil": "load",
  "waitForSelectorTimeoutSecs": 15,
  "delayMs": 0,
  "scrollToLoadLazyContent": true,
  "dismissCookieBanners": true,
  "blockAds": true,
  "stickyElements": "smart",
  "darkMode": false,
  "reducedMotion": true,
  "locale": "en-US",
  "ignoreHttpsErrors": false,
  "captureErrorPages": false,
  "changeThresholdPercent": "1",
  "saveDiffImage": true,
  "maxUrls": 1000,
  "pageTimeoutSecs": 45,
  "maxRetries": 1,
  "galleryReport": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `sheets` (type: `string`):

No description

## `gallery` (type: `string`):

No description

## `files` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("tobbyb/website-screenshot").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": ["https://example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("tobbyb/website-screenshot").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://example.com"
  ]
}' |
apify call tobbyb/website-screenshot --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,tobbyb/website-screenshot"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/rRtAAAIco2kfqjJgs/builds/yRA1r5Al66Ibwb7ge/openapi.json
