# Website Screenshot API (Full Page, Mobile, No Cookie Banners) (`isnard/website-screenshot`) Actor

Full-page screenshots without cookie banners or ads, as PNG, JPEG or PDF. $4 per 1,000. Pages that return 4xx or 5xx are delivered but not charged.

- **URL**: https://apify.com/isnard/website-screenshot.md
- **Developed by:** [Matias Isnard](https://apify.com/isnard) (community)
- **Categories:** Developer tools, Automation
- **Security:** Limited permissions
- **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 takens

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 API (Full Page, Mobile, No Cookie Banners)

Full-page screenshots without cookie banners or ads, as PNG, JPEG or PDF. $4 per 1,000. Pages that return 4xx or 5xx are delivered but not charged.

Give it a list of URLs. For each one you get a PNG, JPEG or PDF taken with a real Chromium browser, saved in the run's key-value store, and a dataset item with a link to the file, the final URL after redirects, the HTTP status, the page title, how long it took and, if it failed, why.

Cookie consent banners are hidden by default. You can capture the visible part of the page or the full page, on a desktop, mobile or tablet viewport, or only one element.

The Actor is built to finish. Every page load has a timeout, a page that never fires its load event is captured as it is instead of failing, a failed attempt is retried once, and a URL that cannot be captured gets a result with the error. One bad URL never fails the run, and if Chromium crashes a new one is started for the remaining URLs.

Only the URLs you give are opened. Nothing else is crawled.

### Input

```json
{
    "urls": ["https://apify.com", "www.wikipedia.org"],
    "format": "png",
    "fullPage": false,
    "viewport": "desktop",
    "waitUntil": "load",
    "delayMs": 0,
    "selector": "",
    "hideCookieBanners": true,
    "clickCookieAccept": false,
    "blockAds": false,
    "timeoutSecs": 30,
    "retries": 1
}
```

- `urls` (required): pages to capture. `https://` is added when there is no scheme. Duplicates are captured once. Invalid lines are skipped with a warning.
- `format`: `png` (default), `jpeg` (quality 80, much smaller files) or `pdf`. PDF prints the page with backgrounds on Letter pages, and ignores `fullPage` and `selector`.
- `fullPage`: capture the whole scrollable page instead of only the viewport. The page is scrolled once first so lazy images load. Very tall pages are cut at 12,000 pixels (6,000 CSS pixels on mobile and tablet, which use 2x pixels).
- `viewport`: `desktop` 1366x768, `mobile` 390x844 with an iPhone user agent and touch, `tablet` 820x1180 with an iPad user agent, or `custom` with `viewportWidth` and `viewportHeight`.
- `waitUntil`: `load` (default) or `networkidle` (no requests for 0.5 s). Many news and shopping sites never reach network idle, so `load` is the safer choice.
- `delayMs`: extra wait before the screenshot, for animations or late content. Up to 30,000.
- `selector`: CSS selector. When set, only the first matching element is captured.
- `hideCookieBanners` (default `true`): hides consent banners with CSS. Nothing is clicked. See below.
- `clickCookieAccept` (default `false`): also tries to click the "Accept" button of the banner. This gives consent on your behalf, so it is off unless you turn it on.
- `blockAds` (default `false`): blocks requests to about 30 common ad and tracking networks (DoubleClick, Taboola, Outbrain, Criteo and others). Pages load faster, ad slots may stay as empty boxes.
- `timeoutSecs` (default 30): how long to wait for the page to load in each attempt.
- `retries` (default 1, up to 3): extra attempts after a failure.

### How a page is loaded

1. Open the URL and wait for the HTML to be parsed.
2. Wait for `waitUntil` (load or network idle) for the rest of `timeoutSecs`. If it does not happen in time, the page is captured as it is and `loadTimedOut` is `true`. A tracker that never finishes loading does not cost you the screenshot.
3. Click the consent button if `clickCookieAccept` is on, inject the banner-hiding CSS, scroll through the page for full page captures, wait `delayMs`, hide remaining consent boxes, take the screenshot.

If an attempt fails (connection error, browser error, the screenshot itself fails or the attempt takes more than about 2 minutes), it is retried and each retry waits for less: network idle, then load, then only the parsed HTML plus up to 5 seconds. Hosts that do not exist are not retried.

Up to 3 pages are open at the same time with 2 GB of memory (2 for full page captures), and more with more memory. All pages share one browser.

### Cookie banners

With `hideCookieBanners` on, a stylesheet hides the containers of OneTrust, Cookiebot, Didomi, Quantcast, Sourcepoint, Usercentrics, TrustArc, Cookie Consent, CookieYes, Complianz, iubenda, Termly, Osano and Funding Choices, plus elements with ids or classes like `cookie-banner`, `cookie-consent`, `cookie-notice` or `consent-wall`. It also unlocks scrolling when the banner had locked it. Right before the screenshot, fixed-position boxes whose text mentions cookies or consent (in English, Spanish, French, German and Italian) are hidden too, which catches most custom banners.

Hiding is not clicking. The site still thinks you have not answered, so content that only loads after consent (some embedded videos, some ads) does not appear. If you need that, turn on `clickCookieAccept`.

### Output

One dataset item per URL. Real output from a test run:

```json
{
    "url": "https://www.spiegel.de",
    "takenAt": "2026-10-30T05:06:20+00:00",
    "format": "png",
    "viewport": "desktop",
    "attempts": 1,
    "screenshotUrl": "https://api.apify.com/v2/key-value-stores/rJVBKcPKnQf4brJof/records/0003-www.spiegel.de-03e0ef35.png?signature=...",
    "screenshotKey": "0003-www.spiegel.de-03e0ef35.png",
    "finalUrl": "https://www.spiegel.de/",
    "statusCode": 200,
    "title": "DER SPIEGEL | Online-Nachrichten",
    "loadMs": 1653,
    "waitUntilUsed": "load",
    "loadTimedOut": false,
    "error": null,
    "attemptErrors": [],
    "durationMs": 1772
}
```

A URL that could not be captured has no `screenshotUrl` and the reason in `error`, for example `Error: Page.goto: net::ERR_NAME_NOT_RESOLVED at https://nonexistent-host.invalid/`.

- `screenshotUrl`: link to the file. It works without an API token, so you can put it in a spreadsheet or send it to someone.
- `screenshotKey`: the key of the file in the run's default key-value store, for downloading with the API.
- `statusCode`: status of the main document. A 404 or 403 page is still captured, so you can see what the site showed.
- `loadMs`: time of the attempt that worked. `durationMs`: total time for the URL including failed attempts.
- `attemptErrors`: the error of every failed attempt, also when a later attempt worked.

The files are stored in the run's default key-value store, so they are kept as long as your plan keeps run storage. Download them if you need them for longer.

### Pricing

Pay per event: `screenshot-taken`, once per URL whose screenshot was saved and whose page answered with a status below 400. You are not charged for URLs that failed, and not for error pages (4xx and 5xx) even though you get their screenshot. Many sites answer automated browsers with a 403 page, and paying for those would not be fair.

You can cap the cost of a run with "Maximum cost per run". The Actor stops cleanly when the cap is reached.

### Limitations

- Pages are loaded from Apify's servers in the US, without a proxy. Some sites block automated browsers and show a 403 or a challenge page (in tests: nytimes.com, ebay.com and etsy.com). Those are captured but not charged.
- Content behind a login is not supported. There are no cookies, headers or authentication options.
- Full page captures of long, heavy pages take 20 to 70 seconds each. Viewport captures usually take 1 to 10 seconds.
- Pages taller than the cap are cut. Pages that scroll inside an inner container instead of the page itself come out as a viewport-sized image even with `fullPage`.
- The consent banner list covers the common frameworks. A custom banner that is not fixed on the screen and has no usual name can still show.
- One viewport, format and wait setting per run. For both desktop and mobile, run twice.

### Run locally

```bash
python -m venv .venv
.venv/Scripts/pip install -r requirements.txt   # on Linux/macOS: .venv/bin/pip
.venv/Scripts/python -m playwright install chromium-headless-shell
## put input in storage/key_value_stores/default/INPUT.json
.venv/Scripts/python -m my_actor
python -m unittest discover -s tests -t .
```

Author: Matias Isnard.

### FAQ

**What is charged?**
Each URL whose screenshot was saved and whose page answered below 400. Failed URLs and error pages are free.

**Can it capture pages behind a login?**
No. There are no cookie, header or authentication options.

**How long does a screenshot take?**
Viewport captures usually take 1 to 10 seconds. Full page captures of long pages take 20 to 70 seconds.

**Can I get desktop and mobile in one run?**
No, one viewport per run. Run it twice.

### Related tools

- [Broken Link Checker for Your Sitemap](https://apify.com/isnard/broken-link-checker): find broken links on the same site.
- [Bulk Article Extractor: Clean Text & Markdown for LLMs](https://apify.com/isnard/article-extractor): get the page text instead of an image.
- [Website Technology Detector](https://apify.com/isnard/website-tech-detector): see which tools the page uses.

# Actor input Schema

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

Pages to capture, one per line. https:// is added when no scheme is given. Duplicates are captured once.

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

PNG is lossless. JPEG (quality 80) is much smaller. PDF prints the page with backgrounds on Letter-size pages; Full page and Element selector are ignored for PDF.

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

Capture the whole scrollable page instead of only the visible viewport. The page is scrolled once first so lazy images load.

## `viewport` (type: `string`):

Desktop is 1366x768. Mobile is 390x844 (iPhone user agent, 2x pixels). Tablet is 820x1180 (iPad user agent, 2x pixels). Custom uses the width and height below.

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

Only used with the Custom viewport.

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

Only used with the Custom viewport.

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

Load waits for the load event. Network idle also waits until there are no requests for 0.5 s, which some busy sites never reach. When an attempt times out, the retry waits for less: Network idle, then Load, then DOM ready.

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

Wait this long after the page loads, before the screenshot. Useful for animations and late content.

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

Optional CSS selector. When set, only the first matching element is captured. A missing element is reported as an error.

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

Hide consent banners of OneTrust, Cookiebot, Didomi, Quantcast, Sourcepoint, Usercentrics, TrustArc and similar, plus generic cookie bars and fixed boxes that mention cookies or consent. Nothing is clicked.

## `clickCookieAccept` (type: `boolean`):

Also try to click the accept button of the consent banner before the screenshot. This gives consent on your behalf and can make the site load more ads and trackers.

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

Block requests to common ad and tracking networks (DoubleClick, Taboola, Outbrain, Criteo and others). Ad slots may stay as empty boxes.

## `timeoutSecs` (type: `integer`):

Maximum time for the page to load in each attempt.

## `retries` (type: `integer`):

Extra attempts when loading or capturing fails. Each retry waits for less than the attempt before it. Hosts that do not exist are not retried.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://www.wikipedia.org"
  ],
  "format": "png",
  "fullPage": false,
  "viewport": "desktop",
  "viewportWidth": 1366,
  "viewportHeight": 768,
  "waitUntil": "load",
  "delayMs": 0,
  "hideCookieBanners": true,
  "clickCookieAccept": false,
  "blockAds": false,
  "timeoutSecs": 30,
  "retries": 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": [
        "https://apify.com",
        "https://www.wikipedia.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("isnard/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://apify.com",
        "https://www.wikipedia.org",
    ] }

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

```

## MCP server setup

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