# Website Screenshot & URL to PDF API (Bulk, Full Page) (`malmurk/website-screenshot-pdf`) Actor

Bulk website screenshots and URL to PDF in real Chromium: full-page or viewport PNG, desktop or mobile, cookie banners hidden, lazy images loaded, optional ad blocking, wait-for-selector. Direct file links for AI agents, reports and monitoring. $4 per 1,000 captures; failed URLs are free.

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

## Pricing

from $4.00 / 1,000 screenshot captureds

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?

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 & URL to PDF API (Bulk, Full Page)

Turn any list of URLs into **full-page PNG screenshots** and **PDFs**, rendered in a real Chromium browser. Paste hundreds of URLs, pick desktop or mobile, and get one row per page with a direct link to its PNG and PDF, the final URL after redirects, the HTTP status and the page title. **Cookie banners are hidden, lazy-loaded images are scrolled in**, and ads can be blocked.

**$4 per 1,000 captures. Failed URLs are free.** A URL that fails (unknown domain, 404, timeout, missing selector) still gets a row that tells you why, and it isn't charged.

### What does Website Screenshot & PDF do?

- Takes a **full-page screenshot** (or just the visible viewport) of every URL you give it, as PNG.
- Saves the same page as a **PDF** (URL to PDF) that looks like the screen, not like the print version.
- Works in **bulk**: paste a list of URLs or upload a text file. Duplicates are captured once.
- Captures as **desktop (1280x720) or mobile (Pixel 7)**, or any viewport size you set, e.g. 1920x1080.
- **Hides cookie and consent banners** (OneTrust, Cookiebot, Usercentrics, Didomi and more) and can **block ads**.
- Waits for the page properly: load event, DOM ready, network idle, an extra delay, or **wait for a CSS selector**.
- Gives every PNG and PDF a **direct download link**, ready for Google Sheets, Slack, a report or an AI agent.

### Example output

A real run on 12 URLs (desktop, full page). Two failed and were not charged.

| url | status_code | title | PNG size (px) | error |
|---|---|---|---|---|
| https://www.python.org | 200 | Welcome to Python.org | 1280 x 2747 | |
| https://en.wikipedia.org/wiki/Web_scraping | 200 | Web scraping - Wikipedia | 1280 x 10474 | |
| https://github.com | 200 | GitHub · Change is constant... | 1280 x 11646 | |
| https://www.bbc.com | 200 | BBC Home - Breaking News, World News... | 1280 x 13383 | |
| https://www.theverge.com | 200 | The Verge | 1280 x 20000 (cut at the 20,000 px limit) | |
| https://stackoverflow.com | 403 | Just a moment... | | `HTTP 403` (bot wall, not charged) |
| https://this-domain-does-not-exist-xyz.com | | | | `net::ERR_NAME_NOT_RESOLVED` (not charged) |

Each successful row also has `screenshot_url` (and `pdf_url` if you asked for a PDF) linking straight to the file. The **Captures** table in the Console shows a thumbnail of every screenshot.

### Who it's for

- **AI agents and MCP clients** that need to "see" a web page as an image or attach it as a PDF.
- **SEO, QA and marketing teams** checking how landing pages look on desktop and mobile.
- **Archiving and compliance**: keep a dated visual record of a page (`captured_at` is in every row).
- **Agencies** making before/after screenshots or client reports in bulk.
- **Website monitoring**: run it on a schedule and keep a screenshot history of competitor or client pages.

### How to take website screenshots in bulk

1. Click **Try for free** and create a free Apify account (no credit card needed).
2. Paste your URLs into **URLs** (one per line), or upload a text file.
3. Choose **PNG screenshot**, **PDF** or both, and **Desktop** or **Mobile**.
4. Click **Start**. Open the **Output** tab to see thumbnails and download links, or export the list as CSV, Excel or JSON.

### Features

- **Batch input.** Paste hundreds of URLs, or upload a text file. Duplicates are captured once. Bare domains (`example.com`) get `https://` added.
- **PNG and/or PDF.** Full scrollable page or just the viewport. PDFs use screen styles and background colours, so they look like the page and not like its print version.
- **Lazy-loaded images appear.** Before a full-page shot, the page is scrolled to the bottom so images that load on scroll are in the picture.
- **Device presets.** Desktop (1280x720) or mobile (Pixel 7, 412x839, mobile user agent, touch, 2.6x pixel density). Override width and height if you need, e.g., 1920x1080.
- **Wait options.** Load event, DOM ready or network idle, an extra delay in milliseconds, and *wait for a CSS selector*. A page that never goes fully idle is still captured instead of failing.
- **Cookie banners hidden** (best effort). Common consent tools such as OneTrust, Cookiebot, Usercentrics, Didomi, Quantcast, TrustArc and Osano are hidden, and the page's scroll lock is removed. Nothing is clicked or accepted for you.
- **Optional ad blocking** for common ad and tracking networks.
- **Reliable.** Retries for timeouts, crashes and temporary server errors (429/5xx), a fresh isolated browser context per URL, browser auto-restart, and concurrency that adapts to the run's memory.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `urls` | array | | Pages to capture. Each item is `{"url": "https://..."}` or a plain string. |
| `screenshot` | boolean | `true` | Save a PNG screenshot. |
| `fullPage` | boolean | `true` | Whole scrollable page (cut at 20,000 px) instead of just the viewport. |
| `pdf` | boolean | `false` | Also save a PDF. |
| `device` | string | `desktop` | `desktop` or `mobile`. |
| `viewportWidth` / `viewportHeight` | integer | device default | Override the viewport size in CSS pixels. |
| `waitUntil` | string | `load` | `load`, `domcontentloaded` or `networkidle`. |
| `delayMs` | integer | `0` | Extra wait after the page loads. |
| `waitForSelector` | string | | Wait until this CSS selector is visible. If it never appears, the URL fails (not charged). |
| `hideCookieBanners` | boolean | `true` | Hide cookie and consent banners. |
| `blockAds` | boolean | `false` | Block ad and tracking requests. |
| `pdfFormat` | string | `A4` | `A4`, `Letter`, `Legal`, `A3` or `Tabloid`. |
| `pdfLandscape` | boolean | `false` | Landscape PDF. |
| `maxConcurrency` | integer | `3` | Pages captured in parallel (about 600 MB of memory each). |
| `navigationTimeoutSecs` | integer | `60` | Time limit to open a page and to wait for the selector. |
| `maxRetries` | integer | `1` | Retries for temporary errors. |

Example:

```json
{
  "urls": [
    { "url": "https://apify.com" },
    { "url": "https://en.wikipedia.org/wiki/Web_scraping" }
  ],
  "screenshot": true,
  "fullPage": true,
  "pdf": true,
  "device": "desktop",
  "waitUntil": "load",
  "hideCookieBanners": true
}
```

### Output

One row per URL in the dataset. The files themselves are in the run's key-value store, and `screenshot_url` / `pdf_url` link straight to them. Fields that don't apply are `null`, never an empty string.

```json
[
  {
    "url": "https://en.wikipedia.org/wiki/Web_scraping",
    "final_url": "https://en.wikipedia.org/wiki/Web_scraping",
    "status_code": 200,
    "title": "Web scraping - Wikipedia",
    "screenshot_url": "https://api.apify.com/v2/key-value-stores/<store-id>/records/screenshot-en.wikipedia.org-0dbff88b78.png",
    "pdf_url": "https://api.apify.com/v2/key-value-stores/<store-id>/records/pdf-en.wikipedia.org-0dbff88b78.pdf",
    "width": 1280,
    "height": 10474,
    "device": "desktop",
    "captured_at": "2026-10-23T02:34:32+00:00",
    "error": null
  },
  {
    "url": "https://httpbin.org/status/404",
    "final_url": "https://httpbin.org/status/404",
    "status_code": 404,
    "title": null,
    "screenshot_url": null,
    "pdf_url": null,
    "width": null,
    "height": null,
    "device": "desktop",
    "captured_at": null,
    "error": "HTTP 404"
  }
]
```

| Field | Description |
|---|---|
| `url` | The URL you gave. |
| `final_url` | Where the browser ended up after redirects. |
| `status_code` | HTTP status of the page. |
| `title` | Page `<title>`. |
| `screenshot_url` | Link to the PNG, or `null`. |
| `pdf_url` | Link to the PDF, or `null`. |
| `width`, `height` | Size of the PNG in pixels. On mobile this is the CSS size times the 2.6x pixel density. `null` when there is no PNG. |
| `device` | `desktop` or `mobile`. |
| `captured_at` | UTC time of the capture. |
| `error` | Why the URL failed, e.g. `HTTP 404`, `net::ERR_NAME_NOT_RESOLVED ...`, `Navigation timed out after 60s`. `null` on success. |

Files are named `screenshot-<host>-<hash>.png` and `pdf-<host>-<hash>.pdf`, the same for the same URL.

### How much does it cost to screenshot a website?

Pay per event: **$4 per 1,000 captures** (`capture` event, $0.004 each), plus a start fee of $0.00005 per GB of run memory ($0.0001 at the default 2 GB). One capture is one URL with all the outputs you asked for (PNG, PDF or both). Failed URLs are free. Apify platform usage is included in that price.

| Captures | Price |
|---|---|
| 100 | $0.40 |
| 1,000 | $4.00 |
| 10,000 | $40.00 |

If you set a **maximum cost per run**, the Actor stops starting new pages when the limit is reached, finishes cleanly, and tells you how many URLs were not processed.

### Use with AI agents and MCP

Give Claude, Cursor, ChatGPT or your own agent a "screenshot this page" or "save this page as PDF" tool through the [Apify MCP server](https://docs.apify.com/platform/integrations/mcp). Add this server URL to your MCP client:

```
https://mcp.apify.com?tools=malmurk/website-screenshot-pdf
```

Then ask, for example: *"Take mobile screenshots of these 5 landing pages and give me the links."* The agent gets back a row per URL with `screenshot_url` and `pdf_url`, which it can open, show or pass to a vision model.

### Integrations and API

- **Make, Zapier and n8n:** screenshot new URLs from a sheet, form or RSS feed and send the PNG or PDF to Google Drive, Slack, Notion or email. See the [Make](https://docs.apify.com/platform/integrations/make), [Zapier](https://docs.apify.com/platform/integrations/zapier) and [n8n](https://docs.apify.com/platform/integrations/n8n) guides.
- **Schedules:** capture the same pages every day or week with [Apify Schedules](https://docs.apify.com/platform/schedules) for a visual history.
- **Webhooks:** get notified when a run finishes ([webhooks](https://docs.apify.com/platform/integrations/webhooks)).
- **API:** one HTTP call runs the Actor and returns the rows with file links.

```bash
curl -X POST "https://api.apify.com/v2/acts/malmurk~website-screenshot-pdf/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"urls": [{"url": "https://example.com"}], "fullPage": true, "pdf": true}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("malmurk/website-screenshot-pdf").call(run_input={
    "urls": [{"url": "https://example.com"}, {"url": "https://www.python.org"}],
    "device": "mobile", "pdf": True,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(row["url"], row["screenshot_url"], row["pdf_url"], row["error"])
```

### Memory and speed

The default 2 GB of memory runs 3 pages in parallel. Each page needs about 600 MB, and the Actor lowers the parallelism automatically if the run has less memory. Long, heavy pages (big news homepages with full-page PNG plus PDF) can need more than 1 GB on their own, so stay at 2 GB or above for those. More memory also gives the run more CPU, so pages render faster.

### Limits

- Pages behind a login, paywall or CAPTCHA are captured as they appear to a logged-out visitor. The Actor does not log in, solve CAPTCHAs or bypass bot protection.
- It only visits the URLs you give it. It doesn't follow links or crawl.
- Full-page screenshots stop at 20,000 px of page height, so endless feeds don't produce huge files.
- Cookie banner hiding is best effort. An unusual banner can still show. Use `delayMs` or `waitForSelector` for pages that draw content late.
- Output formats are PNG and PDF (no JPEG or WebP).

### FAQ

**Can I use it from an AI agent or via API?**
Yes. Call the Actor with the `urls` input and read the dataset; each row has direct file links. See [Use with AI agents and MCP](#use-with-ai-agents-and-mcp).

**How do I convert a URL to PDF without a screenshot?**
Set `"screenshot": false` and `"pdf": true`. Pick the paper size with `pdfFormat` and turn on `pdfLandscape` for wide pages.

**Can I take a 1920x1080 screenshot?**
Yes: set `viewportWidth` to 1920 and `viewportHeight` to 1080. Turn `fullPage` off to get exactly that size.

**A page came out blank or half-loaded.**
Try `"waitUntil": "networkidle"`, add `"delayMs": 3000`, or wait for a selector that appears when the content is ready.

**Am I charged for pages that failed?**
No. Only successful captures are charged. Failed URLs get a row with the reason in `error`.

**Is it legal?**
Taking a screenshot of a public page is generally fine, but you are responsible for the URLs you submit and for how you use the images (copyright, terms of service, personal data).

### Related Actors

- [Businesses Without Websites Finder](https://apify.com/malmurk/osm-no-website-leads): find local businesses with no website, a dead link or only a Facebook page, then screenshot their current presence for your pitch.
- [PDF Text Extractor with OCR](https://apify.com/malmurk/pdf-text-extractor): turn the PDFs you capture, or any other PDF, Word file or scan, into text and Markdown for AI.

# Actor input Schema

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

Pages to capture, one per line. A bare domain such as 'example.com' gets https:// added. Duplicates are captured once. Only these pages are visited; links are not followed.

## `screenshot` (type: `boolean`):

Save a PNG screenshot of each page.

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

Capture the whole scrollable page (the page is scrolled first so lazy-loaded images appear). Off = only the visible viewport. Very long pages are cut at 20,000 px.

## `pdf` (type: `boolean`):

Also save a PDF of each page (rendered with screen styles and background colours, like the screenshot).

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

Desktop: 1280x720 Chrome on Windows. Mobile: Pixel 7 (412x839, 2.6x pixel density, mobile user agent, touch).

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

Override the device's viewport width, e.g. 1920. Leave empty to use the device default.

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

Override the device's viewport height, e.g. 1080. With 'Full page' off, this is the screenshot height.

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

When the page counts as loaded. 'Network idle' waits for background requests to stop (best for single-page apps); if a page never goes idle it is captured anyway after 20 s.

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

Wait this many milliseconds after the page has loaded, e.g. for animations or charts.

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

Optional. Wait until an element matching this CSS selector is visible, e.g. '#main-chart'. If it never appears, the URL is reported as an error (not charged).

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

Best effort: hides common cookie/consent banners (OneTrust, Cookiebot, Usercentrics, Didomi, Quantcast and many others) and unlocks scrolling. Nothing is clicked or accepted.

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

Block requests to common ad and tracking networks and hide standard ad slots. Ad space may stay as an empty box.

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

Paper size of the PDF.

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

Landscape orientation for the PDF.

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

How many pages to capture at once. Each page needs about 600 MB of memory, so with the default 2 GB run memory at most 3 run in parallel (the Actor lowers it automatically). Give the run more memory to go faster.

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

Maximum time to open a page and to wait for the selector.

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

Retries for timeouts, crashes and temporary server errors (HTTP 429/5xx). Unknown domains and 404s are not retried.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://en.wikipedia.org/wiki/Web_scraping"
    }
  ],
  "screenshot": true,
  "fullPage": true,
  "pdf": false,
  "device": "desktop",
  "waitUntil": "load",
  "delayMs": 0,
  "hideCookieBanners": true,
  "blockAds": false,
  "pdfFormat": "A4",
  "pdfLandscape": false,
  "maxConcurrency": 3,
  "navigationTimeoutSecs": 60,
  "maxRetries": 1
}
```

# Actor output Schema

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

No description

## `files` (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"
        },
        {
            "url": "https://en.wikipedia.org/wiki/Web_scraping"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("malmurk/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" },
        { "url": "https://en.wikipedia.org/wiki/Web_scraping" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("malmurk/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"
    },
    {
      "url": "https://en.wikipedia.org/wiki/Web_scraping"
    }
  ]
}' |
apify call malmurk/website-screenshot-pdf --silent --output-dataset

```

## MCP server setup

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