# Website Screenshot API (`josh99smith/website-screenshot-api`) Actor

Screenshot any URL in a real browser: full-page or viewport PNG/JPEG, desktop and mobile presets, dark mode, cookie banners hidden, optional PDF. Pay per page.

- **URL**: https://apify.com/josh99smith/website-screenshot-api.md
- **Developed by:** [Joshua Smith](https://apify.com/josh99smith) (community)
- **Categories:** Developer tools, Automation, Open source
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.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

This **website screenshot API** turns any URL into a full-page screenshot or PDF with one call. Paste a list of websites, pick a device (desktop, laptop, tablet, mobile or custom viewport), choose PNG or JPEG, and get back image files plus a structured record for each page. Cookie banners are hidden automatically, lazy-loaded images are scrolled into view, sticky headers are captured once instead of covering the page, emoji and CJK text render correctly, and pages that fail to load are reported **free of charge**.

It runs in headless Chromium on Apify's infrastructure, so there is nothing to install, and it can be scheduled, called from code, or wired into Zapier, Make, n8n and the Apify MCP server.

### Features

- Full-page or viewport screenshots of a list of URLs in bulk, as PNG or JPEG
- Web page to PDF (A4, Letter or Legal)
- Desktop, laptop, tablet and mobile presets, custom viewport, dark mode
- Cookie banners hidden, sticky headers pinned, emoji and CJK fonts included
- Capture a single element, wait for an element, block images or fonts
- Logged-in pages via your own cookies and headers; scheduling and integrations via Apify

### What can you use Website Screenshot API for?

- **Visual monitoring**: schedule daily captures of landing, pricing or competitor pages.
- **Link previews and thumbnails** for directories, newsletters and CMS cards.
- **Design and QA reviews**: the same pages on laptop, tablet and mobile, light and dark.
- **Compliance and evidence**: archive how a page looked on a given date, as image and PDF.
- **AI agents**: give an agent eyes on the web through the Apify MCP server.

### How it works

Each URL is opened in headless Chromium with the viewport, pixel density and user agent of the chosen device. The Actor waits for the page to load (by default until the network goes idle), scrolls to the bottom so lazy-loaded content appears, hides consent pop-ups with CSS (nothing is clicked or accepted on your behalf), pins fixed and sticky elements so they appear once, waits an optional delay, and captures the image. Files go to the run's key-value store and the dataset record links to them.

### How to use it

1. Paste your URLs into **Website URLs**, one per line.
2. Pick a **Device preset**, **Image format** and whether you want the **Full page** or only the first screen.
3. Optionally enable **Also render a PDF**, **Dark mode**, add **Hide elements** selectors (chat widgets, newsletter pop-ups), or set **Wait for element**, **Cookies**, **Extra HTTP headers** and **Block resource types** under Advanced.
4. Click **Start**. Each finished page appears in the **Output** tab with a preview; the files are in the **Storage** tab (or via API).

```json
{
    "urls": ["https://apify.com", "https://www.wikipedia.org"],
    "device": "laptop",
    "format": "jpeg",
    "quality": 80,
    "fullPage": true,
    "hideCookieBanners": true,
    "unstickFixed": true,
    "waitForSelector": ".pricing-table",
    "cookies": [{ "name": "session", "value": "<YOUR_SESSION>", "domain": ".example.com", "path": "/" }],
    "extraHeaders": { "Accept-Language": "de-DE" },
    "blockResources": ["media"],
    "renderPdf": false
}
```

| Input                                                             | Default                           | What it does                                                                                                         |
| ----------------------------------------------------------------- | --------------------------------- | -------------------------------------------------------------------------------------------------------------------- |
| `device` + `viewportWidth`, `viewportHeight`, `deviceScaleFactor` | `laptop`                          | Presets, or `custom` with any size from 320x240 to 3840x2160 at 1x to 3x.                                            |
| `fullPage`                                                        | `true`                            | Whole scrollable page, or only the viewport.                                                                         |
| `unstickFixed`                                                    | `true`                            | Pin fixed and sticky headers, bars and chat bubbles so each appears once in full-page captures.                      |
| `waitForSelector`                                                 | none                              | Wait for this element (up to `timeoutSecs`); if it never appears the page is still captured with a `warnings` entry. |
| `hideCookieBanners`, `hideSelectors`                              | `true`, `[]`                      | Hide 60+ known consent pop-ups plus your own selectors.                                                              |
| `cookies`                                                         | `[]`                              | Playwright cookies (`name`, `value`, `domain`, `path`) set before navigation. Never logged.                          |
| `extraHeaders`                                                    | `{}`                              | Sent only to the page's own domain and subdomains, never to third-party hosts. Never logged.                         |
| `blockResources`                                                  | `[]`                              | Abort `image`, `media`, `font`, `stylesheet`, `script` or `xhr` requests; the last three change rendering.           |
| `waitUntil`, `delayMs`, `autoScroll`, `timeoutSecs`, `maxRetries` | `networkidle`, 500, `true`, 60, 1 | Loading and timing controls.                                                                                         |

### Output

```json
{
    "url": "https://www.wikipedia.org",
    "finalUrl": "https://www.wikipedia.org/",
    "success": true,
    "statusCode": 200,
    "title": "Wikipedia",
    "screenshotUrl": "https://api.apify.com/v2/key-value-stores/AbCdEf123/records/screenshot-002-www-wikipedia-org.jpg",
    "screenshotKey": "screenshot-002-www-wikipedia-org.jpg",
    "format": "jpeg",
    "width": 1366,
    "height": 1101,
    "fullPage": true,
    "device": "laptop",
    "sizeBytes": 172806,
    "pdfUrl": "https://api.apify.com/v2/key-value-stores/AbCdEf123/records/pdf-002-www-wikipedia-org.pdf",
    "renderTimeMs": 1139,
    "warnings": ["waitForSelector timed out"],
    "fetchedAt": "2026-09-18T20:41:07.000Z"
}
```

Pages that could not be captured are still listed, so nothing goes missing from your batch:

```json
{
    "url": "https://this-domain-does-not-exist.example",
    "success": false,
    "errorType": "dns",
    "error": "page.goto: net::ERR_NAME_NOT_RESOLVED ...",
    "fetchedAt": "..."
}
```

| Field                             | Description                                                                                    |
| --------------------------------- | ---------------------------------------------------------------------------------------------- |
| `screenshotUrl` / `screenshotKey` | Direct link to the image and its key in the key-value store.                                   |
| `pdfUrl` / `pdfKey`               | Present when **Also render a PDF** is on.                                                      |
| `width` / `height`                | Pixel dimensions of the captured image (height is the full page height when `fullPage` is on). |
| `statusCode`                      | HTTP status of the page. Error pages (404, 500) are still captured so you can see them.        |
| `title`                           | The page title.                                                                                |
| `warnings`                        | Only present when something non-fatal happened, e.g. `waitForSelector timed out`.              |
| `errorType`                       | For failures: `invalid-url`, `dns`, `timeout`, `blocked`, `http-error`, `network` or `other`.  |

### Fixes for the common screenshot problems

The complaints users report most often about free screenshot tools, and what this Actor does about each:

- **Cookie banners cover the page**: 60+ consent pop-ups are hidden with CSS by default; add your own in **Hide elements**.
- **Sticky headers repeat or cover content in full-page screenshots**: `unstickFixed` pins fixed and sticky elements once at their natural place and moves bottom bars to the end of the page.
- **Emoji and Chinese, Japanese or Korean text render as boxes**: Noto Color Emoji and Noto CJK fonts are installed.
- **Cannot set the height or dimensions**: use the `custom` preset with `viewportWidth`, `viewportHeight` and `deviceScaleFactor`; the record reports the exact `width` and `height`.
- **Timeouts and pages that load forever still cost credits**: a failed page is a free `success: false` record with an `errorType`. You pay only for delivered images.
- **Captured before the content appears**: `waitForSelector`, `delayMs` and `autoScroll`.
- **Logged-in or personalised pages**: pass your own `cookies` and `extraHeaders` (never logged). Use only sessions you are entitled to.
- **Heavy pages are slow**: `blockResources` drops images, media or fonts.

### Use it from the API, Python, JavaScript or an AI agent

One HTTP call runs the Actor and returns the dataset records with every `screenshotUrl`:

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

Python, with the [apify-client](https://docs.apify.com/api/client/python) package:

```python
from apify_client import ApifyClient

client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("josh99smith/website-screenshot-api").call(
    run_input={"urls": ["https://apify.com"], "device": "laptop", "fullPage": True, "renderPdf": True}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["url"], item.get("screenshotUrl"), item.get("pdfUrl"))
```

JavaScript, with the [apify-client](https://docs.apify.com/api/client/js) package:

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: '<YOUR_API_TOKEN>' });
const run = await client.actor('josh99smith/website-screenshot-api').call({
    urls: ['https://apify.com'],
    device: 'laptop',
    format: 'png',
    fullPage: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((item) => item.screenshotUrl));
```

It is also a tool in the Apify MCP server for AI agents, and connects to Zapier, Make, n8n and Google Sheets in the Integrations tab.

### Pricing: how much does it cost to screenshot a website?

You pay a **flat price per captured screenshot** and, if enabled, a flat price per **PDF**; both are shown next to the Start button. Pages that fail to load cost nothing, and there is no per-run start fee. Set a maximum cost per run and the Actor stops cleanly when it is reached.

For comparison, screenshot SaaS products typically charge a monthly subscription for a fixed quota; here you only pay for the pages you actually capture.

### Tips

- **Speed**: `networkidle` gives the best render; switch to `load` or `domcontentloaded` and block images for large batches of simple pages.
- **Thumbnails**: turn off **Full page** and combine with `device: "mobile"` for app-store style captures.
- **Cleaner captures**: hide chat bubbles, promo bars or "download our app" overlays with **Hide elements**.
- **Blocked or geo-restricted sites**: enable Apify Proxy (residential groups available) in **Proxy configuration**.
- **Memory**: each concurrent tab needs roughly 200 to 400 MB; raise run memory with concurrency.

### FAQ

#### Are cookie banners accepted or dismissed?

No. They are hidden visually with CSS so the capture is clean. No consent choice is made on the visited site.

#### Why is the screenshot dark or light?

A light colour scheme is requested unless **Dark mode** is on. Sites with a dark design (like Apify's) still render dark.

#### Can it log in or click through pop-ups?

It does not fill in forms or click. To capture a page as a logged-in user, pass the session cookies from your own browser in **Cookies** (and any required headers in **Extra HTTP headers**); they are set before navigation and never logged. Pop-ups are hidden with CSS, not clicked.

#### What are the limits on page size, timeouts and batch size?

No fixed cap on URLs; the run stops cleanly at the maximum cost you set. Each page gets the configured **Page timeout** (10 to 180 s) and up to 3 retries; concurrency is capped at 20 tabs. Very long pages are captured in full, but extremely tall images (tens of thousands of pixels) may be truncated by Chromium.

#### Is it legal to screenshot a website?

The Actor loads public web pages in a browser, like a visitor would, at low request rates. You are responsible for how you use the captured images and for respecting the target sites' terms and copyright.

### Related Actors by the same developer

[Website Tech Stack Detector](https://apify.com/josh99smith/tech-stack-detector), [Google Autocomplete Scraper](https://apify.com/josh99smith/google-autocomplete-scraper), [App Reviews Scraper](https://apify.com/josh99smith/app-reviews-scraper), [PageSpeed Insights Audit](https://apify.com/josh99smith/pagespeed-insights-audit), [Remote Jobs Aggregator](https://apify.com/josh99smith/remote-jobs-aggregator), [PDF Text Extractor](https://apify.com/josh99smith/pdf-text-extractor), [Sitemap URL Extractor](https://apify.com/josh99smith/sitemap-url-extractor), [RSS Feed to JSON](https://apify.com/josh99smith/rss-feed-to-json).

### Support

Report problems or request features in the **Issues** tab. Feature requests such as scripted interactions are welcome.

# Changelog

This Actor's version history is a separate document: https://apify.com/josh99smith/website-screenshot-api/changelog.md

# Actor input Schema

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

One URL per line. The scheme is optional (`example.com` works). Duplicates are removed.

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

Viewport, pixel density and user agent preset. Choose `custom` to set width, height and scale factor yourself.

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

Capture the whole scrollable page instead of just the first screen.

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

PNG is lossless; JPEG files are much smaller and support the quality setting.

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

1 to 100. Ignored for PNG.

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

Hide the most common cookie / consent pop-ups (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast and 60+ others) with CSS before capturing. Nothing is clicked or accepted.

## `renderPdf` (type: `boolean`):

Additionally save the page as a PDF (billed as a separate PDF event).

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

Paper size used when rendering PDFs.

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

Emulate `prefers-color-scheme: dark` so sites with a dark theme render it.

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

Only used with the `custom` device preset.

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

Only used with the `custom` device preset.

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

1 = standard, 2 = retina, 3 = high-density phone. Only used with the `custom` preset.

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

When the page is considered ready. `networkidle` additionally waits (up to 15 s) for background requests to settle after the load event, which gives the most complete render.

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

Additional wait after the page is ready, for animations or late scripts.

## `autoScroll` (type: `boolean`):

Scroll through the page first so lazy-loaded images render in full-page screenshots.

## `unstickFixed` (type: `boolean`):

Un-stick fixed and sticky elements in full-page captures, so headers, navbars and floating bars appear once at their natural place instead of repeating or covering content. Only applies when Full page is on.

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

Wait until this element exists before capturing, e.g. `.chart-rendered` or `#main img`. Waits up to the page timeout; if it never appears the page is still captured and the record gets a `warnings` entry.

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

Extra CSS selectors to hide before capturing, e.g. `.chat-widget`, `#newsletter-popup`.

## `clipSelector` (type: `string`):

Screenshot a single element, e.g. `#pricing-table`, instead of the page.

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

How many pages to render in parallel. Each browser tab uses roughly 200 to 400 MB of memory.

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

Give up on a page after this long.

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

Retry a page that failed to load this many times before reporting it.

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

Cookies to set before loading each page, for logged-in or consent-preset captures. Playwright format: `[{"name": "session", "value": "abc", "domain": ".example.com", "path": "/"}]`. Treated as secrets: values are never logged or stored in the output. Only use sessions you are allowed to use.

## `extraHeaders` (type: `object`):

Headers sent with every request, e.g. `{"Authorization": "Bearer ...", "Accept-Language": "de-DE"}`. Treated as secrets: values are never logged or stored in the output.

## `blockResources` (type: `array`):

Abort requests for these resource types to speed up captures of heavy pages. Blocking `image`, `media` or `font` is usually safe; blocking `stylesheet`, `script` or `xhr` (XHR and fetch) changes how the page renders.

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

Optional. Use Apify Proxy for sites that block cloud IP addresses or to render geo-specific content. Proxy traffic is billed by Apify separately.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://www.wikipedia.org",
    "https://github.com"
  ],
  "device": "laptop",
  "fullPage": true,
  "format": "png",
  "quality": 80,
  "hideCookieBanners": true,
  "renderPdf": false,
  "pdfFormat": "A4",
  "darkMode": false,
  "viewportWidth": 1366,
  "viewportHeight": 768,
  "deviceScaleFactor": 1,
  "waitUntil": "networkidle",
  "delayMs": 500,
  "autoScroll": true,
  "unstickFixed": true,
  "hideSelectors": [],
  "maxConcurrency": 5,
  "timeoutSecs": 60,
  "maxRetries": 1,
  "cookies": [],
  "extraHeaders": {},
  "blockResources": [],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One record per URL with the screenshot URL, dimensions, page title and status.

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

The captured images (screenshot-*) and PDFs (pdf-*).

## `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": [
        "https://apify.com",
        "https://www.wikipedia.org",
        "https://github.com"
    ],
    "cookies": [],
    "extraHeaders": {},
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("josh99smith/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://github.com",
    ],
    "cookies": [],
    "extraHeaders": {},
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("josh99smith/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://github.com"
  ],
  "cookies": [],
  "extraHeaders": {},
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call josh99smith/website-screenshot-api --silent --output-dataset

```

## MCP server setup

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