# Website Screenshot API: Full Page, Mobile & PDF (`fairfetch/website-screenshot-api`) Actor

Capture PNG, JPEG or PDF screenshots of any public web page with headless Chrome. Full-page, custom viewport, mobile device emulation, dark mode, cookie-banner hiding. Public link per screenshot. Pay only for real captures: $3 per 1,000 screenshots, $10 per 1,000 full-page or PDF, $0.005 per run.

- **URL**: https://apify.com/fairfetch/website-screenshot-api.md
- **Developed by:** [Naveen Raja](https://apify.com/fairfetch) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.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?

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 & PDF Screenshots (pay per screenshot)

Take **screenshots of any public web page** with a real headless Chrome browser and get a **public link to each image**. PNG, JPEG or PDF. Full-page or viewport, custom screen size, **mobile and tablet emulation**, dark mode, lazy-load scrolling and automatic **cookie-banner hiding**.

**Price: $3 per 1,000 screenshots, $10 per 1,000 full-page screenshots or PDFs, plus $0.005 per run.** You pay only for real captures. Blank pages, bot walls and challenge pages, error pages, pages that never finish loading and URLs that fail are **free**, and no file is stored for them. Apify platform costs are included, so the price is all you pay (plus Apify's standard start fee of $0.00005 per GB of memory per run).

| What you run | Cost |
|---|---|
| 1 screenshot (one API call) | $0.008 |
| 100 screenshots in one run | $0.305 |
| 100 full-page screenshots or PDFs in one run | $1.005 |
| A daily run with 50 screenshots, for a month | about $4.65 |

### What it does

For each URL you give it, the Actor:

1. Opens the page in headless Chromium (Playwright) with your viewport or device preset.
2. Waits for the page to load (your choice of `load`, `domcontentloaded`, `networkidle` or `commit`, plus an optional extra delay).
3. Optionally scrolls to the bottom to trigger lazy-loaded images, and hides cookie banners and any elements you list.
4. Checks that the page it is about to capture is real content: HTTP status below 400 **on the final page** (after redirects and JavaScript navigations), not blank, not a bot wall or challenge, not an error page and not just loading placeholders. A page that looks blank or like a challenge gets a few more seconds to finish (and one retry) before it is given up as free.
5. Takes the screenshot (PNG/JPEG) or PDF, charges it (`screenshot` or `full-page-screenshot`), and only then saves the file to the run's key-value store.
6. Returns one dataset item with the page title, final URL, HTTP status, image size and a **public screenshot URL**.

### Use cases

- **AI agents and LLM tools**: let an agent "look" at a page, verify a deployment or attach a visual to a report, paying per screenshot.
- **Website monitoring and QA**: capture pages on a schedule to spot visual regressions or broken layouts.
- **Link previews and thumbnails** for directories, newsletters, CMSs and social posts.
- **Archiving and records**: keep dated PNG or PDF copies of web pages, ads or price pages.
- **Responsive design checks**: capture the same page as iPhone, Pixel, iPad and desktop.
- **SEO and competitor research**: collect visual snapshots of landing pages at scale.

### How to use

1. Paste your **URLs** (one per line; `https://` is added if missing).
2. Pick a **format** (PNG, JPEG or PDF) and whether you want a **full-page** screenshot.
3. Optional: choose a **device preset**, viewport size, dark mode, extra delay or elements to hide.
4. Run. Each screenshot appears in the dataset with a link, and the files are in the run's key-value store.

#### Input example

```json
{
  "urls": ["https://apify.com", "https://www.wikipedia.org", "https://news.ycombinator.com"],
  "format": "png",
  "fullPage": false,
  "viewportWidth": 1280,
  "viewportHeight": 800,
  "device": "none",
  "waitUntil": "load",
  "delayMs": 0,
  "hideCookieBanners": true,
  "hideSelectors": ["#intercom-container"],
  "timeoutSecs": 30
}
```

#### Output example

A real item from a cloud run (20 Oct 2026, run OxJOnFUripcoAQNno). `screenshotUrl` is a public link to the record in the run's key-value store (the `signature` parameter is added by Apify for stores that are not public).

```json
{
  "url": "https://www.wikipedia.org",
  "finalUrl": "https://www.wikipedia.org/",
  "title": "Wikipedia",
  "httpStatus": 200,
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/screenshot-0002-www.wikipedia.org-ab2a63b2.png?signature=<signature>",
  "screenshotKey": "screenshot-0002-www.wikipedia.org-ab2a63b2.png",
  "format": "png",
  "width": 1280,
  "height": 800,
  "fullPage": false,
  "device": null,
  "fileSizeBytes": 190588,
  "takenAt": "2026-10-20T05:55:13Z",
  "error": null,
  "errorKind": null,
  "chargedEvent": "screenshot",
  "note": null
}
```

A URL that does not give a real page is a free row without a file, with the reason in `error` and `errorKind` (from the same run):

```json
{
  "url": "https://www.nytimes.com",
  "finalUrl": "https://www.nytimes.com/",
  "httpStatus": 403,
  "screenshotUrl": null,
  "error": "HTTP 403: the page returned an error (not charged).",
  "errorKind": "http_error"
}
```

#### Output fields

| Field | Description |
|---|---|
| `url`, `finalUrl` | Requested URL and the URL of the page that was captured (after redirects and JavaScript navigations) |
| `title`, `httpStatus` | Page title and HTTP status of the captured page |
| `screenshotUrl`, `screenshotKey` | Public link and key of the file in the run's default key-value store (empty for free rows) |
| `format`, `width`, `height`, `fileSizeBytes` | File type and image size in pixels (device pixels for 2x/3x presets; empty for PDF) |
| `fullPage`, `device` | Capture settings used |
| `takenAt` | ISO 8601 time of capture |
| `chargedEvent` | The event you paid for this row: `screenshot` ($0.003) or `full-page-screenshot` ($0.01); `null` for free rows |
| `error`, `errorKind` | `null` for a charged screenshot. Otherwise why there is no screenshot (free): `invalid_url`, `private_address`, `dns_not_found`, `unreachable`, `http_error`, `blocked` (bot wall or challenge), `error_page`, `blank` (empty page or loading placeholders), `too_large`, `timeout`, `browser_error`, `proxy_error` |
| `note` | Extra information, e.g. the page was still loading when the timeout hit, or an invalid CSS selector you gave was skipped |

The **Screenshots** view shows the images as thumbnails; use the **PDF and file links** view for PDF runs. A `SUMMARY` record with counts per error kind is saved in the key-value store.

### Options

- **Format**: PNG (lossless), JPEG (set quality 1-100) or PDF (A4, Letter, Legal, A3; uses screen styles and backgrounds).
- **Full-page**: captures the whole scrollable page, up to 20,000 image pixels tall (longer pages are cut there to keep files and memory reasonable).
- **Viewport**: 200-3840 x 200-2160 px. Default 1280 x 800.
- **Device emulation**: iPhone 15, iPhone 15 Pro Max, iPhone 17 Pro, iPhone SE, Pixel 7, Pixel 9, Galaxy S24, iPad Pro 11, iPad Mini and Desktop Chrome HiDPI. Sets screen size, pixel ratio, touch and user agent.
- **Color scheme**: light or dark (`prefers-color-scheme`).
- **Wait until** + **extra delay**: control when the capture happens. If a page never finishes loading, the Actor waits up to 10 seconds for the chosen state and then captures what has rendered (and says so in `note`).
- **Scroll to bottom first**: triggers lazy-loaded images before a full-page capture.
- **Hide cookie banners**: hides common consent pop-ups (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast, TrustArc, Osano, Klaro, iubenda and generic cookie banners) with CSS. Nothing is clicked and no consent is given. The page's scroll lock is removed only when a banner was found.
- **Hide elements**: your own CSS selectors to hide (chat widgets, newsletter pop-ups, ads). An invalid selector is skipped and named in `note`; the others still work.
- **Ignore HTTPS errors**: off by default, so a page with an invalid certificate fails (free) instead of being captured like a trusted page.
- **Skip the content check**: off by default. The content check makes blank pages, bot walls, challenge pages and error pages free (no file). It is careful, but if one of your legitimate pages is wrongly treated as blank or blocked, turn this on: every page that loads with an HTTP status below 400 is then captured and charged.
- **Parallel browser tabs**, **retries** and an optional **proxy** under Advanced. Proxy: the Apify datacenter proxy or your own `http://` proxy URLs. Residential and Google SERP proxy groups are not supported: the run stops with "Invalid input" and nothing is charged.

### Pricing

Pay per event, no subscription and no usage fees:

| Event | Price |
|---|---|
| `run-start`: once per run that has at least one valid URL | **$0.005** |
| `screenshot`: one real viewport capture (PNG or JPEG; page loaded with HTTP status below 400 and real content) | **$0.003** ($3 per 1,000) |
| `full-page-screenshot`: one real full-page capture or PDF | **$0.01** ($10 per 1,000) |
| Actor start (standard Apify event, per run and GB of memory) | $0.00005 |

- Each capture is charged exactly one of `screenshot` or `full-page-screenshot`; the `chargedEvent` field says which.
- The run start fee is charged once per run, also when every URL then fails, and never again after a platform migration. It is not charged when the input is invalid (no valid URL, unsupported proxy) or when your maximum charge cannot cover the fee plus one capture: the run then stops at once with a clear message and costs nothing.
- Free, with no file stored: HTTP errors (4xx/5xx), bot walls and challenge pages, error pages served as normal pages, blank pages and pages stuck on loading placeholders, pages that never load, invalid URLs and private addresses.
- Set **Maximum cost per run** in the run options (or `maxTotalChargeUsd` via API) to cap spending. The Actor never starts a screenshot it cannot charge for, never stores one it did not charge, and stops cleanly with a count of the URLs it skipped.

### Use it from the API or an AI agent

Run synchronously and get results in one call (keep your own API token in the `APIFY_TOKEN` environment variable):

```bash
curl -X POST "https://api.apify.com/v2/acts/fairfetch~website-screenshot-api/run-sync-get-dataset-items?maxTotalChargeUsd=0.05" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com"], "fullPage": true, "format": "jpeg"}'
```

The response is a JSON array; open `screenshotUrl` to get the image. The Actor uses pay-per-event pricing without usage fees and runs with limited permissions, so AI agents can call it through the Apify MCP server and pay per run.

### Performance and limits

- Measured on Apify (20 Oct 2026) with 15 URLs (3 light pages and 12 popular home pages), 3 parallel tabs: viewport screenshots took about 37-42 seconds at the default 2 GB of memory (28 s of work at 4 GB). Full-page screenshots with scrolling of 12 popular home pages took 3-4 minutes at 2 GB and about 1.5 minutes at 4 GB. Full-page and PDF captures use at most one tab per GB of memory and render one page at a time, so give big full-page batches 4 GB.
- At the default settings that is roughly 1,000+ viewport screenshots per hour. The default run timeout is 1 hour: raise it for bigger batches. The Actor stops cleanly before the timeout and tells you how many URLs it did not reach.
- Each URL gets at most the navigation timeout + extra delay + 60 seconds (90 seconds by default), retries included.
- Only public `http`/`https` pages. Private and local network addresses are blocked for every request the browser makes, including redirects, JavaScript navigations, frames, images, scripts and WebSockets. With a proxy (Apify datacenter or your own), the Actor still checks every host name with its own DNS first, but the proxy server resolves the name again and makes the connection.
- A few sites cannot be resolved from the network of the Apify platform; they end as a free `dns_not_found` row. With the Apify proxy turned on, the proxy resolves such names in its own network.
- Pages behind logins, paywalls or strong bot protection often return HTTP 403/429 or a challenge page. These are free; the Actor does not solve CAPTCHAs. Detection of bot walls, error pages and loading placeholders works on the page content and is not perfect: a rare challenge page can slip through, and a page with almost no content can be treated as blank.
- Files are stored in the run's default key-value store and follow your Apify data retention settings. Download them if you need to keep them longer.
- PNG captures larger than about 8.5 MB are saved as JPEG automatically (noted in `note`); larger JPEGs are re-encoded at lower quality, and anything still above 8.5 MB is a free `too_large` row.

### FAQ

**Do I pay for pages that fail?**
No. Apart from the $0.005 run start fee, you pay only for real captures. HTTP errors, bot walls and challenge pages, error pages, blank or still-loading pages, network errors, timeouts with nothing rendered and invalid URLs are free, and no file is stored for them.

**Does it accept cookie banners?**
No. It hides known banners with CSS so the content underneath is visible. It does not click buttons or give consent.

**Can I capture pages that need a login?**
No. The Actor does not handle credentials or sessions; it is meant for public pages. URLs with a user name or password in them are refused.

**How do I get the image file itself?**
Open `screenshotUrl`, or download the record by `screenshotKey` from the run's key-value store in Apify Console or via API.

**Why does the image width differ from my viewport?**
Device presets and "Desktop Chrome HiDPI" use 2x or 3x pixel ratios, so an iPhone 15 screenshot (393 px wide) is 1179 px wide.

**Can I take thousands of screenshots in one run?**
Yes, within the run timeout: about 1,000 viewport screenshots per hour at the default settings. For bigger batches raise the run timeout (or split the list into several runs), and use 4 GB of memory for full-page captures. If the timeout comes close, the Actor stops cleanly and reports the URLs it skipped.

**Can I use a residential proxy?**
No. A browser downloads several MB per page, and residential traffic would cost more than the screenshot. Use the Apify datacenter proxy (default group) or your own `http://` proxy.

### Legal

Use this Actor only for public web pages you are allowed to access. It does not log in, bypass access controls or solve CAPTCHAs. Respect the terms of the sites you capture, copyright in their content, and applicable privacy laws; you are responsible for how you use the screenshots.

# Actor input Schema

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

Web pages to capture, one per line. "https://" is added when missing. Only public http(s) pages are supported.

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

PNG (lossless), JPEG (smaller files) or PDF (printable document of the page). PDFs are charged as full-page captures ($0.01 each).

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

Capture the whole scrollable page instead of just the visible viewport (pages taller than 20,000 px are cut at 20,000 px). Full-page captures cost $0.01 each instead of $0.003.

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

Browser window width. Ignored when a device preset is selected.

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

Browser window height (also the screenshot height when full-page is off). Ignored when a device preset is selected.

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

Emulate a phone, tablet or HiDPI desktop (screen size, pixel ratio, touch and user agent). Choose "None" to use the viewport size above.

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

1-100. Only used for JPEG.

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

Emulate light or dark mode (prefers-color-scheme).

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

Only used for PDF output.

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

When navigation counts as finished: "load" (default), "domcontentloaded" (faster), "networkidle" (no network activity for 500 ms; best for heavy JavaScript apps, slower) or "commit".

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

Additional wait after the page has loaded, e.g. for animations or late content.

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

Scroll through the page before capturing to trigger lazy-loaded images (useful with full-page screenshots).

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

Maximum time for the page HTML to arrive. After that, the Actor waits up to 10 more seconds for the "Wait until" state and then captures whatever has rendered. Each URL gets at most this timeout + extra delay + 60 seconds in total, retries included.

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

Hide common cookie-consent pop-ups (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast and generic banners) with CSS. Nothing is clicked or accepted.

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

Extra elements to hide before capturing, e.g. chat widgets or ads: "#intercom-container", ".newsletter-popup". Invalid selectors are skipped and listed in the item's note.

## `ignoreHttpsErrors` (type: `boolean`):

Capture pages whose HTTPS certificate is invalid or self-signed. Off by default, so a page served with a bad certificate fails (free) instead of being captured like a trusted page.

## `skipContentCheck` (type: `boolean`):

Off by default: blank pages, bot walls, challenge pages and error pages served as normal pages are free and get no file. Turn this on to capture (and pay for) every page that loads with an HTTP status below 400, for example if a legitimate page of yours is wrongly treated as blank or blocked.

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

Pages captured at the same time. 3 suits the default 2 GB of memory. Full-page and PDF captures use at most 1 tab per GB of memory (2 at 2 GB); give the run more memory to capture more of them at once.

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

How many times to retry a page after a network error or a browser crash (not after HTTP errors, bot walls or timeouts). All attempts share the time limit per URL.

## `proxyConfiguration` (type: `object`):

Optional. Use the Apify datacenter proxy (default group) or your own http:// proxy URLs if some sites block the default IP addresses. Residential and Google SERP proxy groups are not supported (the run stops with "Invalid input" and nothing is charged).

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://www.wikipedia.org"
  ],
  "format": "png",
  "fullPage": false,
  "viewportWidth": 1280,
  "viewportHeight": 800,
  "device": "none",
  "quality": 80,
  "colorScheme": "light",
  "pdfFormat": "A4",
  "waitUntil": "load",
  "delayMs": 0,
  "scrollToBottom": false,
  "timeoutSecs": 30,
  "hideCookieBanners": true,
  "hideSelectors": [
    "#intercom-container",
    ".newsletter-popup"
  ],
  "ignoreHttpsErrors": false,
  "skipContentCheck": false,
  "maxConcurrency": 3,
  "maxRetries": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `screenshots` (type: `string`):

Dataset items: url, finalUrl, title, httpStatus, screenshotUrl, width, height, format, takenAt, error, errorKind.

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

List of saved screenshot 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 = {
    "urls": [
        "https://apify.com",
        "https://www.wikipedia.org",
        "https://news.ycombinator.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fairfetch/website-screenshot-api").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",
        "https://news.ycombinator.com",
    ] }

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

```

## MCP server setup

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

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/PCzgdIYbz5hgkvfEj/builds/I9tdO2lwAGJOTCC3m/openapi.json
