# Website Screenshot and PDF (`reestri/web-screenshot`) Actor

Capture a batch of public web pages as PNG, JPEG, WebP or PDF: full page or viewport, one element by CSS selector, desktop, tablet and mobile presets, dark mode, hidden cookie banners. One result per URL with file URL and size; failed URLs are not charged.

- **URL**: https://apify.com/reestri/web-screenshot.md
- **Developed by:** [Soso Pkhakadze](https://apify.com/reestri) (community)
- **Categories:** Developer tools, Automation, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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.

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

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Website Screenshot and PDF

Turn a list of public web pages into PNG, JPEG or WebP screenshots or PDF documents in one run. Capture the visible
screen or the whole scrollable page, a single element by CSS selector, on desktop, laptop, tablet or mobile, in light
or dark mode, with cookie banners and other clutter hidden. Every URL gets its own result with a direct file link,
the image size in pixels and a clear error code when something goes wrong. URLs that fail are not charged.

### What you can do with it

- **Monitor pages over time.** Schedule a run and keep a visual record of a pricing page, a landing page or a
  competitor's home page.
- **Check responsive layouts.** Capture the same pages as desktop, tablet and mobile.
- **Archive pages as PDF.** Print articles, documentation or receipts from public pages into A4 or Letter PDFs.
- **Make thumbnails and previews.** WebP and JPEG at the quality you choose keep files small.
- **Grab one component.** Capture only `header`, `#pricing` or any other element instead of the full page.
- **Give an AI agent eyes.** Results are flat JSON with a `fileUrl` per page, so an agent calling this Actor through
  the Apify MCP server gets a link it can open or pass on.

### Features

| Option | What it does |
|---|---|
| Batch of URLs | Up to 1,000 public URLs per run, 2 at a time by default (up to 5). Duplicates are captured once. |
| Formats | PNG, JPEG, WebP (quality 1 to 100) and PDF (A4, Letter, Legal, A3, Tabloid, portrait or landscape). |
| Full page or viewport | Full page captures stop at a maximum height you set (15,000 px by default) and are marked `truncated`. |
| Element capture | `selector` captures the first matching element only. |
| Device presets | Desktop 1920x1080, laptop 1366x768, tablet 800x1280 at 2x, mobile 412x915 at 2.625x, or your own width, height and pixel density. |
| Dark mode | Emulates `prefers-color-scheme: dark` (or light, or no preference). |
| Waiting | Page load, HTML parsed, network idle or first response; wait for a selector to become visible; extra delay up to 30 s. A page whose load event never arrives is still captured and marked `pageLoaded: false`, instead of failing. |
| Lazy content | Optional scroll through the page before capture so lazy images load. |
| Hide clutter | Hide any elements by CSS selector, and optionally the banners of common consent tools (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast and others). Hiding is CSS only; nothing is clicked, accepted or refused. |
| Language | Send an `Accept-Language` header to see a localised version of a site. |
| Error pages | Pages that answer 404, 403, 500 and similar are reported as errors and not charged, unless you ask to capture them. |
| Retries | Dropped connections and browser crashes are retried once by default. Timeouts are not retried. |

### What it does not do

This Actor captures what an anonymous visitor sees. It does not log in, accept cookies, solve captchas, rotate
proxies or change its identity to get past bot protection. Pages that block automated browsers come back as an
error or show the block page. URLs with credentials in them, `localhost`, private networks and non-web schemes
are refused. Only the headers `Accept-Language`, `Referer`, `DNT`, `Sec-GPC`, `Cache-Control` and `Pragma` can be set.

### Input

The only required field is `urls`. A minimal input:

```json
{
  "urls": ["https://apify.com", "https://example.com"],
  "format": "png",
  "fullPage": true
}
```

A mobile, dark mode WebP capture with banners hidden:

```json
{
  "urls": ["https://github.com"],
  "format": "webp",
  "device": "mobile",
  "colorScheme": "dark",
  "fullPage": true,
  "hideCookieBanners": true,
  "hideSelectors": ["#chat-widget"]
}
```

A German language PDF:

```json
{
  "urls": ["https://en.wikipedia.org/wiki/Tbilisi"],
  "format": "pdf",
  "pdfFormat": "A4",
  "headers": { "Accept-Language": "de-DE" }
}
```

See the Input tab for every option with its limits.

### Output

Files are stored in the run's default key-value store. The dataset has one record per input URL. `fileUrl` is a
signed link (it ends in `?signature=...`) that downloads the file without an API token, so you can hand it to another
tool or an agent directly.

A successful capture:

```json
{
  "status": "ok",
  "url": "https://example.com/",
  "finalUrl": "https://example.com/",
  "httpStatus": 200,
  "pageTitle": "Example Domain",
  "pageLoaded": true,
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/0002-example-com-a4df5e24d0.png",
  "key": "0002-example-com-a4df5e24d0.png",
  "format": "png",
  "contentType": "image/png",
  "bytes": 14723,
  "width": 1920,
  "height": 1080,
  "device": "desktop",
  "viewport": { "width": 1920, "height": 1080, "deviceScaleFactor": 1 },
  "colorScheme": "light",
  "fullPage": true,
  "selector": null,
  "truncated": false,
  "pdfFormat": null,
  "attempts": 1,
  "durationMs": 3792,
  "charged": true,
  "capturedAt": "2026-09-14T00:59:05.820Z",
  "errorCode": null,
  "errorMessage": null
}
```

A failed URL has the same fields, with `status: "error"`, `fileUrl: null`, `charged: false` and an `errorCode`:

| errorCode | Meaning |
|---|---|
| `timeout` | The HTML did not arrive within the navigation timeout, or the image took too long to render. |
| `dns_not_resolved` | The domain does not exist. |
| `http_error_status` | The page answered 400 or above; `httpStatus` has the code. |
| `tls_error` | The site's certificate is invalid. |
| `connection_refused`, `connection_reset` | The server closed or refused the connection. |
| `selector_not_found`, `wait_for_selector_timeout` | The CSS selector you gave matched nothing. |
| `private_address`, `unsupported_scheme`, `credentials_in_url`, `invalid_url` | The URL was refused before any request. |
| `browser_crashed`, `capture_failed` | The browser could not render the page, usually a very heavy page. |

`width` and `height` are the image size in pixels, so a mobile capture at 2.625x is 1082 pixels wide. For PDF they
are `null`. A record with `status: "skipped"` means the run reached your maximum cost before that URL (see Pricing).

A run summary is stored as `OUTPUT` in the key-value store: totals of ok, error and skipped records and error counts
by code. The run itself succeeds when every URL was handled, even if some of them failed, so check `status` per record.

### Pricing

Pay per event, with browser compute included:

| Event | Price |
|---|---|
| Actor start: once per run, $0.001 per GB of memory | $0.002 at the default 2 GB |
| `screenshot`: one viewport or element image (PNG, JPEG or WebP) stored | $0.002 ($2.00 per 1,000) |
| `screenshot-full-page`: one full page image stored | $0.004 ($4.00 per 1,000) |
| `pdf`: one PDF stored | $0.004 ($4.00 per 1,000) |

Examples: 1,000 viewport screenshots in one run cost $2.002. A scheduled run that captures one full page every day
costs $0.006 a day.

- A charge is made only after the file is stored. URLs that fail, time out, answer with an error status or match no
  selector cost nothing.
- The Actor respects your **maximum cost per run**. Before each capture it checks that the remaining budget covers
  it; when it does not, the remaining URLs are returned as `skipped` and nothing more is charged.
- Stored files count toward your Apify storage like any other key-value store data, and unnamed run storage is
  removed after your plan's retention period. Download files you want to keep, or copy them to a named store.

### Tips

- The default of 2 pages at a time suits most sites. Light pages can go to 4 or 5; very tall, image heavy pages are safer at 1.
- For pages that fade content in as you scroll, turn on `scrollToBottom` and add a `delayMs` of 1,000 to 2,000.
- `networkidle` suits single page apps, but some sites never stop sending requests; the capture then waits for the
  whole navigation timeout before it is taken. Prefer `load` with `waitForSelector` when you know what to wait for.
- WebP images are limited to 16,383 pixels on each side, so long full page WebP captures are shorter than PNG ones.

### Using it from code or an AI agent

Run it through the Apify API, the JavaScript or Python client, or any integration. For agents, add it to the Apify
MCP server (`https://mcp.apify.com`) and call it with a `urls` list; each result's `fileUrl` points at the stored file.

### Support

Open an issue on the Issues tab with the URL, your input and the `errorCode` you got. Public pages only, please.

# Actor input Schema

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

Public http or https pages to capture, up to 1,000 per run. A domain without a scheme gets https. Duplicates are captured once. Pages behind a login, local addresses and private networks are refused.

## `format` (type: `string`):

PNG is lossless, JPEG and WebP are smaller, PDF prints the page into a document.

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

Capture the whole scrollable page instead of the visible viewport. Very long pages are cut at the maximum height and marked truncated.

## `device` (type: `string`):

Viewport preset. Desktop 1920x1080, laptop 1366x768, tablet 800x1280 at 2x, mobile 412x915 at 2.625x (Chrome on Android). Width, height and scale below override the preset.

## `colorScheme` (type: `string`):

Emulates the visitor's light or dark mode preference. Only pages that support prefers-color-scheme change.

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

Capture only the first element matching this CSS selector, for example header or #pricing. If nothing matches, the URL is reported as selector\_not\_found and not charged. Ignored for PDF.

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

1 to 100. Ignored for PNG and PDF.

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

Elements to hide before capture, such as a chat widget, a promo bar or a cookie banner your page shows. Hiding is done with CSS; nothing is clicked or accepted.

## `hideCookieBanners` (type: `boolean`):

Also hides the containers of widely used consent tools (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast and others) with CSS. Consent is not given or refused; banners from other tools stay visible unless you add their selectors above.

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

When the page counts as ready. The HTML must arrive within the navigation timeout. The load event or network idle (500 ms without requests) is then awaited for up to the same timeout; if it never comes, the page is captured as it is and the result has pageLoaded false.

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

Wait until an element matching this selector is visible before capturing. If it does not appear within the navigation timeout, the URL is reported as wait\_for\_selector\_timeout and not charged.

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

Waits this long after the page is ready, for animations or late content. 0 to 30,000 ms.

## `scrollToBottom` (type: `boolean`):

Scrolls down the page and back to the top before capture, so lazy-loaded images appear. Useful with full page.

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

Overrides the device preset. 240 to 3,840 CSS pixels.

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

Overrides the device preset. 240 to 4,320 CSS pixels.

## `deviceScaleFactor` (type: `integer`):

1 for standard, 2 or 3 for retina-sharp images (larger files). Overrides the device preset.

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

Full page captures stop at this height in CSS pixels and are marked truncated. 1,000 to 30,000. WebP is additionally limited to 16,383 image pixels.

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

Only for PDF output.

## `pdfLandscape` (type: `boolean`):

Only for PDF output.

## `pdfPrintBackground` (type: `boolean`):

Only for PDF output. Off gives a printer-friendly white page.

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

Screen keeps the page looking as it does in a browser; print uses the site's print stylesheet if it has one.

## `headers` (type: `object`):

Optional headers, limited to Accept-Language, Referer, DNT, Sec-GPC, Cache-Control and Pragma. For example {"Accept-Language": "de-DE"} to see the German version of a site. Any other header (Authorization, Cookie, User-Agent) is ignored.

## `navigationTimeoutSecs` (type: `integer`):

How long to wait for the HTML before reporting a timeout, and again for the load state. Rendering the image has its own limit of at least 60 seconds. 5 to 90 seconds.

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

Retries after a dropped connection or a browser crash. Timeouts, DNS, TLS and HTTP error statuses are not retried. 0 to 2.

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

By default a page answering 400 or above (404, 403, 500) is reported as http\_error\_status and not charged. Turn on to capture and pay for it anyway, for example to archive your own 404 page.

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

How many pages are open at once. 1 to 5. Two suits the default 2 GB memory; raise it only for light pages.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://example.com"
  ],
  "format": "png",
  "fullPage": true,
  "device": "desktop",
  "colorScheme": "light",
  "quality": 80,
  "hideCookieBanners": false,
  "waitUntil": "load",
  "delayMs": 0,
  "scrollToBottom": false,
  "maxHeight": 15000,
  "pdfFormat": "A4",
  "pdfLandscape": false,
  "pdfPrintBackground": true,
  "pdfMedia": "screen",
  "navigationTimeoutSecs": 30,
  "maxRetries": 1,
  "captureErrorPages": false,
  "maxConcurrency": 2
}
```

# Actor output Schema

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

One record per input URL: status ok with fileUrl, contentType, bytes, width and height, or status error with errorCode and errorMessage (not charged), or skipped when the maximum charge was reached.

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

The screenshots and PDFs in the run's default key-value store, plus an OUTPUT summary record.

# 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://apify.com",
        "https://example.com"
    ],
    "format": "png",
    "fullPage": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("reestri/web-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://apify.com",
        "https://example.com",
    ],
    "format": "png",
    "fullPage": True,
}

# Run the Actor and wait for it to finish
run = client.actor("reestri/web-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://apify.com",
    "https://example.com"
  ],
  "format": "png",
  "fullPage": true
}' |
apify call reestri/web-screenshot --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,reestri/web-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/om06Eb4OLCl4UbhjG/builds/kTlIWfzkhdaVLBLKe/openapi.json
