# Bulk Website Screenshots: Full Page, PDF, No Cookie Banners (`opendata-tools/bulk-website-screenshots`) Actor

Screenshot a list of URLs or call it as an instant API: full-page or viewport, PNG, JPEG, WebP or PDF, desktop, tablet or mobile, dark mode. Cookie banners and ads hidden. $0.75 per 1,000 screenshots ($0.52 on Business) + usage. No screenshot fee for failed pages.

- **URL**: https://apify.com/opendata-tools/bulk-website-screenshots.md
- **Developed by:** [Open Data Tools](https://apify.com/opendata-tools) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.52 / 1,000 screenshots

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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

### Bulk Website Screenshots: Full Page, PDF, No Cookie Banners

This tool takes screenshots of a list of web pages in one run. Paste in your URLs and get a clean image (or PDF) of each page: the whole page from top to bottom or just the first screen, on desktop, laptop, tablet or mobile, in PNG, JPEG, WebP or PDF. Cookie pop-ups and ads are hidden automatically, and lazy-loaded images are loaded before the capture so long pages don't come out half empty. You pay $0.75 per 1,000 screenshots on the Free plan ($0.52 on Business) plus Apify platform usage. Pages that fail have no screenshot fee, though time spent counts as usage.

#### Who it's for

- Agencies and freelancers capturing client sites, competitor pages or portfolio thumbnails.
- SEO and marketing teams keeping a visual record of landing pages, ads and search pages over time.
- Developers and QA teams checking how pages look on different devices after a release.
- Researchers, archivists and compliance teams saving proof of what a page showed on a given day.
- AI agents and automations that need a picture of a page.

#### Why use it

- **Full-page shots that work**: the page is scrolled first so lazy-loaded images and sections appear, then captured in one piece. Very long pages are cut at a height you choose instead of failing, and empty space at the bottom of a page is trimmed off so the image ends where the content ends.
- **No cookie banners, ads or empty ad boxes**: uses the same block lists as browser ad blockers (EasyList, EasyPrivacy and Fanboy's Cookie List) plus our own rules for the big consent providers (OneTrust, Cookiebot, Usercentrics, Quantcast, Didomi, TrustArc, Sourcepoint and more). Sign-up, newsletter, donation and "turn off your ad blocker" pop-ups are hidden too, and pages they freeze are unlocked. Nothing is clicked or accepted on your behalf.
- **No screenshot fee for failed pages**: pages that don't load, return an error, show a bot check, come out blank or nearly blank, show the site's own error page (such as "Oops, unexpected error" or "Something went wrong"), show only loading placeholders (grey boxes where the content should be) or are file downloads get a row with a plain-English reason and no screenshot fee, though time spent counts as usage.
- **Slow pages still get captured**: the **Time limit per page (seconds)** (`pageTimeoutSecs`, 25 seconds by default) covers loading only. If a page is still loading when it is reached, the tool stops waiting and captures what has appeared so far, with the note *captured before the page finished loading*. This also covers pages whose fonts or scripts never finish. Scrolling and taking the screenshot get their own time after that (about 22 seconds at the default settings, or less if loading ran long). If a full-page shot still can't be taken in time (for example on a page whose animations keep the browser busy), the first screen is captured instead, marked `truncated` with the note *only the first screen was captured*. Only if nothing at all has appeared by the limit is the page marked failed (no screenshot fee, though time spent counts as usage), with the tip to raise **Time limit per page (seconds)**.
- **Every format and device**: PNG, JPEG, WebP or A4 PDF; desktop, HD, Retina, laptop, tablet, mobile, iPhone or Android; light or dark mode; transparent backgrounds; or your own screen size.
- **Just the part you want**: capture a single element with a CSS selector, wait for a specific element to appear, or hide extra things like chat widgets.
- **Fast and lean in bulk**: several pages are captured at once, set automatically from the run's processor power. Video, trackers and decorative animation are switched off, so each capture uses far less computing time.
- **Stays within your budget**: set a maximum cost per run and the run stops taking new screenshots once it is reached (see [Staying within budget](#staying-within-budget)).

#### How to use it

1. Add your pages under **URLs to capture**. Bare domains like `example.com` work too.
2. Choose a **Format** and **Device**. Leave **Full page** on to capture whole pages.
3. Click **Start**.
4. Open the **Output** tab. The overview shows a thumbnail, the URL, the status and any reason a page wasn't captured. Each row has a `screenshotUrl` link to the file. All files are also in the run's key-value store.
5. To capture pages regularly, create a schedule with the same input and connect an integration (Google Drive, Slack, Zapier, Make, a webhook) to send the files on.

#### Pricing

You pay per screenshot captured and saved, plus Apify platform usage (the computing time of your run). Failed, blocked, blank, error-page, placeholder-only and skipped pages have no screenshot fee, though the time spent trying them counts as usage.

| Your Apify plan | Per 1,000 screenshots |
|---|---|
| Free | $0.75 |
| Starter | $0.70 |
| Scale | $0.62 |
| Business and above | $0.52 |

Each run also has a start fee of $0.0002 at the default 4 GB memory ($0.00005 per GB). An Instant API instance pays the same each time it starts.

Platform usage depends on the pages. We measured it on build 0.1.8 with the default settings (4 GB memory, full page, desktop). The figures below are total usage, including storage and data transfer, per 1,000 screenshots captured, at the Free plan rate:

- **Typical sites** (small business sites, blogs, documentation, SaaS homepages): about $1.50 of usage per 1,000, so about $2.25 in total with the $0.75 screenshot fee. In a test of 50 such sites, 48 were captured, 1 was blocked and 1 failed.
- **Hard sites** (big news sites and online shops with heavy bot protection, very long pages and slow scripts): about $3.56 of usage per 1,000 captured, so about $4.31 in total. In a test of 50 such sites, 29 were captured, 16 were blocked and 5 failed. Blocked and failed pages have no screenshot fee, but the time spent on them still counts as usage, which is why this figure is higher.

Usage is lower on higher Apify plans and for first-screen-only captures.

Example on the Free plan: 500 typical pages costs $0.38 in screenshot fees plus about $0.75 of usage, about $1.13 in total.

#### Staying within budget

- **Batch runs**: in Apify Console, set **Maximum cost per run** in the run options before you click **Start**. When starting a run through the API, pass `maxTotalChargeUsd` (in the Python `apify-client`, `max_total_charge_usd`). Once the limit is reached the run stops taking new screenshots, the remaining pages get `skipped` rows with no screenshot fee, and the run ends cleanly. A run stopped this way is not marked Failed.
- **Instant API**: Apify's Standby mode currently has no query parameter or header for setting a maximum charge per request. An answer of `402` means the API has reached its spending limit for now; nothing is charged. To cap your total spending, set a usage limit on your Apify account (Billing, Limits), or use batch runs with a maximum cost per run.

#### Good to know

- Pages are loaded with animations reduced and video switched off, which looks the same in a still image and makes captures faster.
- Some sites use strong bot protection and show a check page instead of the real page. Those rows are marked `blocked` and have no screenshot fee, though time spent counts as usage.
- Only public websites can be captured. Private and internal network addresses are refused.
- Mobile, tablet, Retina, iPhone and Android captures use a higher pixel density, so images are 2 to 3 times the listed width.
- WebP images can't be taller than 16,383 pixels, so very long WebP captures are cut at that height.
- Full-page captures stop at **Max page height (px)** (15,000 px by default) and at 40 million pixels in total, so they stay quick to capture. That only matters at high pixel density: on `desktop-retina`, for example, the limit is about 6,900 px of page. Taller pages are cut, marked `truncated` and noted, for example *only the top 15,000 px of the page was captured (height limit)*.
- If a full page can't be captured in time, the top four screens are captured instead (a shorter page is retaken whole), or the first screen if even that isn't possible. When less than the whole page is captured, the row shows `truncated: true` and a note saying which. Pages that scroll inside a box instead of the browser window (common on app-style sites) are opened up so the whole page is captured; if the box can't be opened up, only the first screen is captured, with `truncated: true` and a note.
- Empty space at the bottom of a full-page capture (a screen or more of the page's plain background) is trimmed off. Coloured sections such as footers are kept. A page cut at the height limit still shows `truncated: true` after trimming.
- A page counts as blank (no screenshot fee) when it is all one colour, or almost all one colour with no text or only "Loading...". A page counts as an error page (no screenshot fee) when its text is mainly an error message such as "Oops", "unexpected error", "something went wrong", "page not found" or "service unavailable", with little else on it, or when the page's main content is such a message (or its main heading is, with little else under it), even with the site's normal header, menus and footer around it. Help and FAQ pages that mention an error within longer content, articles that explain an error (such as "Error 404: what it means") and restaurant menus are captured and charged as normal. Minimalist pages with real content are captured and charged as normal.
- A page counts as loading placeholders (no screenshot fee) when most of it is flat light-grey boxes where the content should be and it has little text, usually because it was still loading at the time limit. Pages with grey sections, card grids, product pictures or minimalist designs are captured and charged as normal.
- Files larger than 9 MB can't be saved. Very tall PNGs are saved as JPEG instead (noted in the row), or lower **Max page height (px)**.

### API and reference

This section is for developers and AI agents.

#### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | required | Pages to capture, as `{"url": "..."}` objects or plain strings. Bare domains get `https://` added. Duplicates are captured once. Non-http(s) URLs get a `failed` row with no screenshot fee. |
| `format` | `png`, `jpeg`, `webp`, `pdf` | `png` | File type. PDF is A4 with backgrounds, laid out like the screen version. |
| `fullPage` | boolean | `true` | Whole page (after scrolling to load lazy content) or only the first screen. Ignored for PDF and element captures. |
| `device` | `desktop`, `desktop-hd`, `desktop-retina`, `laptop`, `tablet`, `mobile`, `iphone`, `android` | `desktop` | 1440x900, 1920x1080, 1440x900 @2x, 1280x800, 820x1180 @2x, 390x844 @2x, 393x852 @3x, 412x915 @2.625x, each with a matching browser identity. |
| `hideCookieBanners` | boolean | `true` | Hide consent pop-ups (Fanboy's Cookie List plus our own rules) and unlock scrolling. Nothing is accepted. |
| `hidePopups` | boolean | `true` | Hide other pop-ups covering the page (sign-up, newsletter, donation, app, ad-blocker notices). |
| `blockAds` | boolean | `true` | Block ads and trackers (EasyList, EasyPrivacy) and hide empty ad slots. |
| `darkMode` | boolean | `false` | Ask the site for its dark theme (prefers-color-scheme: dark). |
| `transparentBackground` | boolean | `false` | PNG/WebP: pages without their own background come out see-through. |
| `waitUntil` | `domcontentloaded`, `load`, `networkidle` | `load` | How long to wait before capturing. Pages that never reach it are captured anyway with a note. |
| `delaySecs` | integer | `0` | Extra pause before capturing (0 to 30). Visible images are always waited for. |
| `maxPageHeight` | integer | `15000` | Full-page captures are cut at this height in CSS pixels (500 to 30,000); the row shows `truncated: true`. |
| `quality` | integer | `80` | JPEG and WebP quality (10 to 100). |
| `selector` | string | none | Capture only the first element matching this CSS selector. No match gives a `failed` row with no screenshot fee. |
| `waitForSelector` | string | none | Wait until an element matching this selector is visible before capturing. If it never appears, the row is `failed` (no screenshot fee). |
| `hideSelectors` | string\[] | `[]` | Extra CSS selectors to hide before capturing. |
| `viewportWidth`, `viewportHeight` | integer | device size | Override the screen size. |
| `pageTimeoutSecs` | integer | `25` | Time limit for loading each page (15 to 180). A page still loading at the limit is captured as it is and marked *captured before the page finished loading* (charged only if something real is on it: a blank page or bot check has no screenshot fee). A page that sends nothing at all in time is marked failed (no screenshot fee, though time spent counts as usage) and is not tried again; raise this for very slow sites. Quick network errors, and pages the browser closed unexpectedly, are tried once more on a fresh page. Pages that show a bot check or refuse access (HTTP 401/403/429) are given up on straight away and marked `blocked`, also with no screenshot fee. |
| `maxConcurrency` | integer | from memory | Pages captured at once (1 to 16). Default: 3 per 4 GB (one processor core per 4 GB on Apify). |

#### Output

One dataset row per URL.

| Field | Type | Description |
|---|---|---|
| `url` | string | Input URL |
| `finalUrl` | string | URL after redirects |
| `status` | string | `ok`, `failed`, `blocked` or `skipped`. Only `ok` rows are charged. |
| `reason` | string | Why a page wasn't captured |
| `httpStatus` | integer | HTTP status of the page |
| `pageTitle` | string | Page title |
| `screenshotUrl` | string | Direct link to the file |
| `screenshotKey` | string | Key of the file in the run's default key-value store |
| `format` | string | `png`, `jpeg`, `webp` or `pdf` |
| `device` | string | Device used |
| `fullPage` | boolean | Whole page captured |
| `width`, `height` | integer | Image size in pixels (null for PDF) |
| `bytes` | integer | File size |
| `truncated` | boolean | Page was cut: taller than `maxPageHeight` (or the 40-million-pixel limit), too tall to capture in full in time, or only the first screen could be captured (see `note`) |
| `note` | string | Anything worth knowing, e.g. *captured before the page finished loading*, *only the first screen was captured* or *only the top of the page was captured (too tall to capture in time)* |
| `loadMs` | integer | Time to load and capture |
| `capturedAt` | string | ISO 8601 time |

Example row:

```json
{
  "url": "https://example.com",
  "finalUrl": "https://example.com/",
  "status": "ok",
  "reason": null,
  "httpStatus": 200,
  "pageTitle": "Example Domain",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/example-com-desktop-1a2b3c4d5e.png",
  "screenshotKey": "example-com-desktop-1a2b3c4d5e.png",
  "format": "png",
  "device": "desktop",
  "fullPage": true,
  "width": 1440,
  "height": 900,
  "bytes": 18734,
  "truncated": false,
  "note": null,
  "loadMs": 1820,
  "capturedAt": "2026-10-22T15:00:00+00:00"
}
```

The key-value store also holds `SUMMARY` (counts and why the run stopped, if it stopped early).

#### Run outcome

- A run succeeds if at least one page was captured.
- A run that captured nothing (every page failed or was blocked) ends as **Failed** with the reason in the status message, so schedules and integrations don't treat it as a success. The exception is a run stopped by your own maximum cost per run.
- If the run timeout gets close, remaining pages get `skipped` rows with no screenshot fee and the run ends cleanly.

#### API example

Uses apify-client 3.x. On older versions, use `run["defaultDatasetId"]`.

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("opendata-tools/bulk-website-screenshots").call(run_input={
    "startUrls": [{"url": "https://example.com"}, {"url": "https://apify.com"}],
    "format": "png",
    "device": "desktop",
    "fullPage": True,
})
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item["status"], item["url"], item["screenshotUrl"] or item["reason"])
```

#### Instant screenshot API (one URL, one image)

Need a screenshot inside your own app, one page at a time? Call the always-on API and get the image straight back. Most answers take 5 to 30 seconds. The first call after an idle spell takes about 20 seconds longer while the API starts. No run to start, no dataset to read.

```bash
curl -H "Authorization: Bearer <YOUR_API_TOKEN>" \
  "https://opendata-tools--bulk-website-screenshots.apify.actor/screenshot?url=https://example.com&format=png&fullPage=true" \
  -o example.png
```

Python:

```python
import requests

r = requests.get(
    "https://opendata-tools--bulk-website-screenshots.apify.actor/screenshot",
    params={"url": "https://example.com", "format": "png", "fullPage": "true"},
    headers={"Authorization": "Bearer <YOUR_API_TOKEN>"},
    timeout=180,
)
if r.status_code == 200:
    open("example.png", "wb").write(r.content)
else:
    print(r.status_code, r.json())
```

JavaScript (Node 18+):

```javascript
const params = new URLSearchParams({ url: "https://example.com", format: "png", fullPage: "true" });
const res = await fetch(`https://opendata-tools--bulk-website-screenshots.apify.actor/screenshot?${params}`, {
  headers: { Authorization: "Bearer <YOUR_API_TOKEN>" },
});
if (res.ok) {
  const fs = await import("node:fs/promises");
  await fs.writeFile("example.png", Buffer.from(await res.arrayBuffer()));
} else {
  console.log(res.status, await res.json());
}
```

- Same options as above, as query parameters (or a JSON body with `POST`). `hideSelectors` is a comma-separated list.
- `response=json` saves the file and returns its link plus width, height, page title and final URL, instead of the image.
- Answers: `200` the screenshot (charged), `422` the page could not be captured (no screenshot fee, though time spent counts as usage; the reason is included), `400` bad input, `429` busy (wait for `Retry-After` and try again), `402` the maximum charge is reached (nothing charged; see [Staying within budget](#staying-within-budget)).
- Image answers carry `X-Screenshot-Width`, `X-Screenshot-Height`, `X-Screenshot-Final-Url`, `X-Screenshot-Truncated` and `X-Screenshot-Note` headers. A page captured at the time limit says *captured before the page finished loading* in `X-Screenshot-Note` (or in `note` with `response=json`).
- Same price per screenshot as batch runs. The API runs on your Apify account: you pay usage while it handles your requests and for about 60 seconds after the last one, then it shuts down. Failed and blocked pages have no screenshot fee, though time spent counts as usage.
- Each API instance captures 3 pages at once and queues up to 6 more. Requests beyond that, or ones that would wait more than 60 seconds, get `429` with `Retry-After` (5 or 10 seconds).
- Sending more than 9 requests at once can start extra API instances, and each one is billed for its start-up and about 60 seconds of idle time.

#### Credits

Ad, tracker and cookie-banner blocking uses [EasyList, EasyPrivacy](https://easylist.to/) and [Fanboy's Cookie List](https://secure.fanboy.co.nz/) (GPLv3 / CC BY-SA 3.0), read by Brave's [adblock engine](https://github.com/brave/adblock-rust).

# Actor input Schema

## `startUrls` (type: `array`):

Pages to screenshot. Bare domains like example.com get https:// added. Duplicates are captured once.

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

File type for each capture. PNGs over 9 MB are saved as JPEG instead (noted in the row).

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

Capture the whole page from top to bottom (scrolling first so lazy-loaded images appear). Off = only the first screen.

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

Screen size, pixel density and browser identity used to load each page.

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

Hides consent pop-ups from the common providers (OneTrust, Cookiebot, Usercentrics, Quantcast, Didomi and more) and unlocks pages they freeze. Nothing is clicked or accepted.

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

Hides sign-up, newsletter, donation, app and "turn off your ad blocker" pop-ups that cover the page. Turn off if you need to capture a page's own pop-up.

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

Stops requests to well-known ad networks and hides common ad slots.

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

Tells the site the visitor prefers dark mode. Only sites that support it will change.

## `transparentBackground` (type: `boolean`):

PNG and WebP only: pages that don't set their own background come out see-through, handy for logos and widgets.

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

How long to wait before capturing. Pages that never finish loading are still captured once the wait runs out.

## `delaySecs` (type: `integer`):

Optional pause after loading, for animations or content that appears late. Visible images are always waited for.

## `maxPageHeight` (type: `integer`):

Very long pages are cut off at this height (the row shows truncated: true). WebP is limited to 16,383 pixels tall.

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

Higher is sharper but bigger. Ignored for PNG and PDF.

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

Optional. Capture just the first matching element, e.g. #pricing or main. Pages without a match are marked failed (no screenshot fee).

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

Optional. Wait until an element matching this selector is visible before capturing, e.g. #results or .price. If it never appears, the page is marked failed (no screenshot fee).

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

Optional. Extra things to hide before capturing, e.g. .chat-widget or #newsletter-popup.

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

Optional. Overrides the device's screen width.

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

Optional. Overrides the device's screen height (the size of a non-full-page capture).

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

Time limit for loading each page. A page still loading at the limit is captured as it is, with the note 'captured before the page finished loading'. A page that sends nothing in time is marked failed (no screenshot fee, though time spent counts as usage) and is not tried again. Raise it for very slow sites.

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

Optional. By default this is set from the run's memory: 3 pages per 4 GB (Apify gives one processor core per 4 GB). More memory means faster runs.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://www.bbc.co.uk/news"
    }
  ],
  "format": "png",
  "fullPage": true,
  "device": "desktop",
  "hideCookieBanners": true,
  "hidePopups": true,
  "blockAds": true,
  "darkMode": false,
  "transparentBackground": false,
  "waitUntil": "load",
  "delaySecs": 0,
  "maxPageHeight": 15000,
  "quality": 80,
  "hideSelectors": [],
  "pageTimeoutSecs": 25
}
```

# Actor output Schema

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

All rows in the default dataset, with thumbnails.

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

The image and PDF files, in the default key-value store.

# 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 = {
    "startUrls": [
        {
            "url": "https://apify.com"
        },
        {
            "url": "https://www.bbc.co.uk/news"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("opendata-tools/bulk-website-screenshots").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 = { "startUrls": [
        { "url": "https://apify.com" },
        { "url": "https://www.bbc.co.uk/news" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("opendata-tools/bulk-website-screenshots").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 '{
  "startUrls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://www.bbc.co.uk/news"
    }
  ]
}' |
apify call opendata-tools/bulk-website-screenshots --silent --output-dataset

```

## MCP server setup

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

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/qUeB4MiIuCEHhHN2G/builds/UZg99tadG2mqMGjk1/openapi.json
