# Website Screenshot API & PDF - Full Page, No Cookie Banners (`serla/website-screenshot-pdf`) Actor

Full-page screenshots and PDFs of any list of URLs, with cookie banners and most pop-ups hidden, lazy images loaded, animations finished and sticky headers shown once. Emoji and Chinese, Japanese and Korean text render. Desktop, tablet or phone; JPEG, PNG or WebP. Failed pages are not charged.

- **URL**: https://apify.com/serla/website-screenshot-pdf.md
- **Developed by:** [Samuel Anttila](https://apify.com/serla) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.50 / 1,000 page captureds

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 & PDF - Full Page, No Cookie Banners

Give it a list of URLs and get a clean full-page screenshot of each page (and a PDF if you want one). The things that
usually spoil website screenshots are handled for you:

- **Cookie and consent banners are hidden.** Banners from OneTrust, Cookiebot, Didomi, Quantcast, TrustArc,
  Usercentrics, CookieYes, Sourcepoint, iubenda, Complianz, Borlabs and many hand-made ones are hidden with CSS, and page
  scrolling is restored. Nothing is clicked, so no consent is given on anyone's behalf.
- **Pop-ups are hidden too.** Newsletter, app-install and notification pop-ups and closable bars that float over the
  page are hidden. Only floating elements with a close button (or marked as a modal dialog) count, so headers, menus
  and tab bars stay. You can turn this off.
- **Lazy images are loaded.** The page is scrolled through first, so images, maps and lists that load on scroll are in
  the picture, then it goes back to the top.
- **Sticky headers don't repeat or cover content.** Fixed headers, sidebars and chat buttons are shown once, where they
  sit at the top of the page (or hidden, if you choose). Sidebars that scroll inside themselves are shown in full
  instead of being cut at screen height.
- **Nothing is caught half-way.** Animations and transitions jump to their end state and videos pause before the
  capture, so charts are fully drawn and fade-in sections are visible. You can turn this off.
- **Long pages stay fast.** In our tests a plain Chrome capture of a very long page (the Wikipedia articles on Japan and
  Finland, about 47,000 pixels tall) took 53 to 68 seconds. This actor trims the page to the part being captured, and
  the same capture took about half a second.
- **Emoji and Chinese, Japanese and Korean text render** (the fonts are installed), instead of empty boxes.
- **You choose the height.** Capture the whole page, only the first screen, the top N pixels, or a single element by
  CSS selector.
- **Failed pages are not charged.** Each URL is retried (2 times by default). Pages that could not be captured,
  invalid URLs and error pages (HTTP 4xx/5xx) cost nothing; each row says whether it was charged.

### What you get

For every URL, one row in the dataset with links to the files in the run's key-value store (this row is from a real
run with "Also save a PDF" on; the site sent its English edition):

```json
{
  "url": "https://www.lemonde.fr/",
  "finalUrl": "https://www.lemonde.fr/en/",
  "statusCode": 200,
  "title": "Le Monde in English – World news, culture and opinion",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/<store id>/records/screenshot-0001-lemonde-fr-en.jpg?signature=<signature>",
  "screenshotKey": "screenshot-0001-lemonde-fr-en.jpg",
  "format": "jpeg",
  "widthPx": 1440,
  "heightPx": 6000,
  "device": "desktop-1440",
  "deviceScaleFactor": 1,
  "bytes": 730458,
  "truncated": true,
  "pdfUrl": "https://api.apify.com/v2/key-value-stores/<store id>/records/pdf-0001-lemonde-fr-en.pdf?signature=<signature>",
  "pdfKey": "pdf-0001-lemonde-fr-en.pdf",
  "pdfBytes": 9206417,
  "charged": true,
  "note": "",
  "loadTimeMs": 7106,
  "capturedAt": "2026-10-23T22:53:12.185Z"
}
```

The links carry a signature, so they open without your API token (on accounts where storage access is restricted,
plain record links do not).

`truncated` is true when the page was taller than your maximum height. `charged` is false for error pages (the
screenshot is still saved, so you can see what the site returned). Pages that could not be captured get a row with an
`error` message instead, also shown in the Note column (no charge). If a page never stopped loading in the background (live tickers, chat widgets),
it is captured once the document itself has loaded and the row gets a `note` saying so. The Output tab shows the
screenshots as a table you can scroll through.

### Options

| Option | What it does |
|---|---|
| Device | Desktop 1920 / 1440 / 1366, iPad, iPhone, Android phone, or a custom size. Phone and tablet presets send a mobile user agent. |
| Pixel density | 1x or retina 2x/3x. |
| Full page / Maximum height | Whole page, or stop at a height (default 15,000 px). |
| Capture one element | A CSS selector, e.g. `#pricing`. |
| Image format | JPEG (default), PNG or WebP, with quality for JPEG/WebP. |
| Also save a PDF | A4, Letter, Legal or A3, portrait or landscape, backgrounds printed. Stops after 50 pages by default (you can raise it to 1,000). |
| Hide cookie banners / Hide pop-ups / Also hide these elements | Both on by default; add your own selectors for anything else, such as a chat widget. |
| Sticky headers | Show once at the top (default), hide, or leave as is. |
| Wait until / Wait for element / Extra delay | For pages that render late. |
| Dark mode | Renders sites that support `prefers-color-scheme: dark` in dark mode. |
| Stop animations | On by default: animations end, videos pause. Off captures the page as it moves. |
| Block common ad networks | Blocks well-known ad servers (empty ad slots may stay as blank space). |
| Cookies | Your own session cookies for pages you are allowed to access. |

### Use it from code

Run it with the Apify API or any Apify client. Example input:

```json
{
  "urls": [{ "url": "https://www.theguardian.com/international" }, { "url": "https://example.com/pricing" }],
  "device": "mobile-iphone",
  "fullPage": true,
  "maxHeight": 8000,
  "format": "webp",
  "savePdf": true
}
```

### Use it from AI agents

Add the Apify MCP server (`https://mcp.apify.com`) to Claude, Cursor or another MCP client, and agents can call this
actor as a tool, for example to take a clean full-page screenshot of a page and pass on the image link.

### Pricing

Pay per event, with Apify's platform usage included:

| Event | Free plan | Starter | Scale | Business |
|---|---|---|---|---|
| Page captured | $5.00 / 1,000 | $4.50 / 1,000 | $4.00 / 1,000 | $3.50 / 1,000 |
| PDF saved (only with "Also save a PDF") | $3.00 / 1,000 | $2.70 / 1,000 | $2.40 / 1,000 | $2.00 / 1,000 |

A run start costs $0.00001. Failed pages, invalid URLs and error pages are free. A file larger than 10 MB counts once
per started 10 MB (a 54 MB PDF of a news front page with hundreds of photos counts as 6); nearly every screenshot and
PDF is under 10 MB, and the row's `note` says when a file counted more than once. You can set a maximum total charge
for a run; the actor stops when it is reached and lists the remaining URLs as skipped.

### Good to know

- Pages behind a login need your own cookies. Sites that block automated browsers may still show a block page; the row
  then has the page as the site served it, with its HTTP status.
- Chrome cannot paint one image much taller than about 16,000 device pixels, so extremely long pages are cut there
  (marked `truncated`). Use a lower pixel density or the maximum height option for very long pages.
- When "Also save a PDF" is on, the whole page is scrolled through first (not only down to the maximum height), so the
  PDF has every image. Long, photo-heavy pages make large PDFs; lower the page limit if you only need the start.
- Banner hiding covers the common consent tools and obvious consent dialogs. If a site uses an unusual one, add its
  selector to "Also hide these elements".
- Only capture pages you are allowed to capture, and respect the sites' terms.

# Actor input Schema

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

Web pages to capture. One screenshot per URL (duplicates are captured once). A URL without http(s):// gets https://.

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

Screen size and device the page is rendered for. Mobile and tablet presets also send a matching mobile user agent and touch support.

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

Only used when Device is 'Custom size'.

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

Only used when Device is 'Custom size'. The visible window height; full-page captures still include the whole page.

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

1 = standard, 2 = retina. Leave empty to use the device preset's density. Higher density means larger files.

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

Capture the whole page from top to bottom. Off = only the first screen.

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

Stop a full-page capture at this height (CSS pixels). Use it to capture only the upper part of long pages. 0 = no limit (very tall pages are still cut at about 16,000 device pixels, which is Chrome's limit; the output says 'truncated').

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

Optional. Capture only the first element matching this selector, e.g. 'main', '#pricing' or '.chart'.

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

JPEG (the default) keeps full pages small and fast. PNG is lossless and sharpest for text; WebP is the smallest. Set JPEG/WebP quality below.

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

1-100. Ignored for PNG.

## `savePdf` (type: `boolean`):

Print each page to PDF as well (charged as a separate PDF event).

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

Paper size of the PDF.

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

Print the PDF in landscape orientation instead of portrait.

## `pdfMaxPages` (type: `integer`):

Long, photo-heavy pages make very large PDFs (a news front page with 330 photos came to about 54 MB). The PDF stops after this many pages; raise it if you need the whole document.

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

Hides the banners of common consent tools (OneTrust, Cookiebot, Didomi, Quantcast, TrustArc, Usercentrics, CookieYes, Sourcepoint, iubenda and more) and similar dialogs, and restores page scrolling. Nothing is clicked, so no consent is given.

## `hidePopups` (type: `boolean`):

Hides newsletter, app-install and notification pop-ups and closable bars that float over the page. Only floating elements with a close button (or marked as a modal dialog) are hidden, so headers, menus and tab bars stay.

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

CSS selectors to hide before capturing, e.g. chat widgets or newsletter pop-ups.

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

In full-page captures, fixed headers, sidebars and chat buttons can repeat or cover content. 'Show once' pins them where they sit at the top of the page.

## `scrollToLoad` (type: `boolean`):

Scrolls through the page first so lazy-loaded images, maps and lists appear, then returns to the top.

## `scrollTimeoutSecs` (type: `integer`):

Upper limit for the lazy-loading scroll (pages with endless feeds stop here).

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

When the page counts as loaded before the extra waits.

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

Optional CSS selector that must be visible before the capture (e.g. a chart that renders late).

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

Waits this long after everything has loaded, for animations to finish.

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

Render the page as if the visitor's system uses dark mode (for sites that support it).

## `stopAnimations` (type: `boolean`):

Animations and transitions jump to their end state and videos pause, so nothing is caught half-way and long pages capture faster. Turn off to capture the page exactly as it moves.

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

Blocks requests to well-known ad servers. Empty ad slots may remain as blank space.

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

Optional cookies for pages that need your own session, e.g. \[{"name": "session", "value": "...", "domain": ".example.com"}]. Only use cookies you are allowed to use.

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

Optional. Overrides the browser's user agent.

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

How long to wait for one page to load before the attempt counts as failed.

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

Failed pages are retried this many times. Failed pages are never charged.

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

How many pages are captured at the same time. Leave empty to capture one page per GB of memory the run has (4 at the default 4 GB). Lower it for very heavy pages if runs fail with memory errors.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://apify.com"
    }
  ],
  "device": "desktop-1440",
  "fullPage": true,
  "maxHeight": 15000,
  "format": "jpeg",
  "quality": 85,
  "savePdf": false,
  "pdfFormat": "A4",
  "pdfLandscape": false,
  "pdfMaxPages": 50,
  "hideCookieBanners": true,
  "hidePopups": true,
  "stickyElements": "once",
  "scrollToLoad": true,
  "scrollTimeoutSecs": 20,
  "waitUntil": "networkidle2",
  "delayMs": 500,
  "darkMode": false,
  "stopAnimations": true,
  "blockAds": false,
  "pageTimeoutSecs": 60,
  "maxRetries": 2
}
```

# Actor output Schema

## `captures` (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": [
        {
            "url": "https://apify.com"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("serla/website-screenshot-pdf").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": [{ "url": "https://apify.com" }] }

# Run the Actor and wait for it to finish
run = client.actor("serla/website-screenshot-pdf").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": [
    {
      "url": "https://apify.com"
    }
  ]
}' |
apify call serla/website-screenshot-pdf --silent --output-dataset

```

## MCP server setup

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

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/QPdNnauZc68tzg0GP/builds/nAvWpkF50FtKvIEW2/openapi.json
