# Website Screenshot: Viewport & Full-Page Captures (`urlwright/website-screenshot`) Actor

Capture clean viewport or full-page screenshots of public URLs. Social login walls are blocked. No passwords, no cookie jars. PNG or JPEG into the key-value store with one dataset row per URL.

- **URL**: https://apify.com/urlwright/website-screenshot.md
- **Developed by:** [Urlwright](https://apify.com/urlwright) (community)
- **Categories:** Developer tools, SEO tools, Automation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: 5.00 out of 5 stars

## Pricing

from $14.00 / 1,000 screenshot captured (1×)s

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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

## Website Screenshot: Viewport & Full-Page Captures

**Urlwright.** Little tools for the web.

Clean screenshots of public pages. Viewport or full page, PNG or JPEG. One dataset row per URL, and the image in the key-value store.

v1 opens each page in a fresh headless Chromium window. It does not log in, fill forms, or keep a cookie jar. A site that needs a session will not show the logged-in page.

### What it does

1. You pass page URLs (`startUrls`) or a dataset of URLs. `startUrls` accepts plain strings and Apify request objects `{ "url": "…" }` (mixed lists are fine). Bare domains are normalised to `https://`.
2. Social hosts are rejected before any fetch: Facebook, Instagram, LinkedIn, X (Twitter), TikTok, Threads, Pinterest, Snapchat, Reddit, and the short domains `fb.com`, `fb.me`, `lnkd.in`, `t.co`, `pin.it`, `redd.it`. Subdomains of those hosts are rejected too.
3. Each remaining URL is checked for a private, link-local or metadata address. A blocked URL is written as `ssrf-blocked` and is not opened.
4. When `respectRobotsTxt` is true (the default), the actor fetches that origin's `robots.txt`, follows up to five redirects, and caches the file for the run. A matching Disallow, a 5xx, or a failed fetch is written as `robots-blocked` and is not opened. A 4xx or an empty file allows the page. If the robots URL itself is a private address, the row is `ssrf-blocked`. If a redirect of that file is a private address, the row is `robots-blocked`.
5. Each remaining URL is opened in headless Chromium and captured. The default is the viewport, 1280×720, not the full scroll height. `fullPage: true` captures the scrollable page.
6. The image is stored under `screenshotKey` (`screenshot-0001.png` or `screenshot-0001.jpg`). The dataset row carries status, HTTP status, size and that key.
7. A successful file charges the event `screenshot-captured` once. A blocked URL, a failed capture and a duplicate URL in the same run are free.

One URL that fails does not fail the run. `RUN_SUMMARY` is always written to the key-value store, including when the input is rejected.

### Who it is for

- A visual record of a public page you can open without an account.
- A fixed desktop viewport (1280×720 by default), or the full scroll height when you turn `fullPage` on.
- A list of URLs, typed or taken from another dataset, turned into PNG or JPEG files plus one row each.

Logged-in dashboards and the social hosts above are out of scope for v1.

### Input

The console prefills a small run (`https://urlwright.com/`, 5 screenshots). If you omit `maxScreenshots`, the default is 50. The hard cap in the input schema is 500.

```json
{
  "startUrls": ["https://urlwright.com/"],
  "datasetId": "",
  "datasetField": "url",
  "fullPage": false,
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "format": "png",
  "quality": 85,
  "waitUntil": "networkidle",
  "waitSecs": 0,
  "hideCookieBanners": false,
  "timeoutSecs": 60,
  "maxScreenshots": 5,
  "respectRobotsTxt": true
}
```

`fullPage` defaults to false. `viewportWidth` is 320–3840 (default 1280). `viewportHeight` is 240–2160 (default 720). With `fullPage` true the height still sets the initial window; the image is the scrollable page.

`format` is `png` or `jpeg`. `quality` (1–100, default 85) applies only to JPEG. `waitUntil` is `load`, `domcontentloaded` or `networkidle` (default `networkidle`). `timeoutSecs` is 5–120 (default 60) for navigation and the screenshot.

`waitSecs` is an integer from 0 to 30 (default 0). After the page reaches `waitUntil`, the actor waits that many seconds (`page.waitForTimeout`), then takes the screenshot. 0 skips the wait. The wait is on top of `timeoutSecs`.

`hideCookieBanners` defaults to false. When it is true, the actor hides common cookie consent banners (including Cookiebot and Usercentrics) with CSS, calls the Usercentrics consent API when the page provides it, and clicks one common accept or reject button. If that dismiss fails, the capture still runs. It does not log in, fill forms, or keep a cookie jar. It does not change the price. For the settings that keep a hero clear, see [Clean website screenshots without cookie banners](https://urlwright.com/guides/clean-screenshots-without-cookie-banners/).

`datasetId` plus `datasetField` (default `url`) reads extra targets. If the named field is missing on a row, the actor tries `url`, then `finalUrl`, then `domain`, then `inputUrl`. A bad dataset row is skipped. Duplicate URLs in one run are collapsed (trailing slash ignored).

`respectRobotsTxt` defaults to true. A disallowed path becomes a free `robots-blocked` row. Set it to false to capture that path anyway.

There is no username, password, cookie, proxy or storage-state field.

### Output

One dataset. Every field is present. Unused fields are null.

A captured page:

```json
{
  "schemaVersion": 1,
  "inputUrl": "https://urlwright.com/",
  "url": "https://urlwright.com/",
  "domain": "urlwright.com",
  "status": "ok",
  "httpStatus": 200,
  "finalUrl": "https://urlwright.com/",
  "error": null,
  "fullPage": false,
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "format": "png",
  "bytes": 32346,
  "screenshotKey": "screenshot-0001.png",
  "charged": true,
  "chargedEvents": { "screenshot-captured": 1 },
  "fetchedAt": "2026-10-26T08:00:00.000Z"
}
```

`domain` is the hostname without a leading `www.`. `bytes` is the file size. `screenshotKey` is `screenshot-` plus a four-digit index, then `.png` or `.jpg`. The bytes live in the key-value store under that key (`image/png` or `image/jpeg`). View: Screenshots.

`status` is one of:

| Status | Meaning | Charged |
|---|---|---|
| `ok` | Image stored | yes, `screenshot-captured` × 1 |
| `social-blocked` | Host on the social list; no fetch | no |
| `ssrf-blocked` | Private, link-local or metadata address | no |
| `robots-blocked` | `robots.txt` disallows the path, or the file was unreachable (5xx or network) | no |
| `error` | Navigation or screenshot failed | no |
| `invalid-input` | The input was rejected; one row, then the run stops | no |

An ok row also has `screenshotUrl`: a **signed** Apify key-value record URL for that file (`…/records/<screenshotKey>?signature=…`). Under account **Restricted** General resource access this link opens anonymously without an API token; it stays valid while the record exists (and until the store’s URL-signing key rotates). `screenshotKey` is still the KV key. `deviceScaleFactor` is echoed on every row.

A blocked or failed row has `charged: false`, `screenshotKey: null`, `screenshotUrl: null`, `bytes: null`, and `chargedEvents` with zero counts for all capture events. `error` is `social-blocked:<host>`, `ssrf-blocked`, `robots-blocked`, or the capture message.

`RUN_SUMMARY` counts `targets`, `ok`, `failed`, `socialBlocked`, `ssrfBlocked`, `robotsBlocked`, and `chargedEvents`, and echoes `deviceScaleFactor`, `waitSecs` and `hideCookieBanners`. `stoppedReason` is `completed`, `no_targets`, `invalid_input` or `aborted`. On invalid input the summary also has `fatalError`.

A URL already captured in the same run is not captured or billed again when that run is migrated or resurrected.

### Pricing

Pay per event. Urlwright pays platform compute. One charge per successful image file; the event name depends on `deviceScaleFactor`. There is no start event. Social blocks, SSRF blocks, robots blocks, failed captures and duplicates are free. From 21 Nov 2026, a run with at least one paid result is topped up to **$0.01** via `minimum-fee` ($0.0001 units on every tier); empty or blocked runs stay free.

| Event | Scale | FREE | BRONZE | SILVER | GOLD |
|---|---:|---:|---:|---:|---:|
| `screenshot-captured` | 1× | $0.016 | $0.015 | $0.0145 | $0.014 |
| `screenshot-captured-2x` | 2× | $0.024 | $0.0225 | $0.02175 | $0.021 |
| `screenshot-captured-3x` | 3× | $0.032 | $0.030 | $0.029 | $0.028 |
| `screenshot-captured-4x` | 4× | $0.040 | $0.0375 | $0.03625 | $0.035 |
| `minimum-fee` | — | $0.0001 | $0.0001 | $0.0001 | $0.0001 |

PLATINUM and DIAMOND match GOLD. Minimum max total charge per run: $0.05 (so one 4× capture fits). Prices are live on the private actor (GET-verified). Margin evidence: `docs/margin-evidence.md`.

### Limits and runtime

Suggested run options: build `latest`, 2048 MB memory, timeout long enough for your URL count. Default memory is 2048 MB. The maximum is 4096 MB. A single public page on urlwright.com finished navigation and screenshot in about one second at 2048 MB (viewport and full page). 4096 MB did not improve that page. Heavier pages can need the higher memory.

Each URL starts its own headless Chromium. The browser user agent is `UrlwrightBot/1.0 (website-screenshot; +https://urlwright.com/bot)`. JavaScript runs. Cookies and storage state are not loaded. The start URL is rejected when it resolves to a private, link-local or metadata address, or when DNS fails. That row is free.

`respectRobotsTxt` defaults to true. Before a capture, the actor fetches `/robots.txt` on that origin with the Urlwright user agent, follows up to five redirects, and caches the file for the rest of the run. A Disallow that matches the page, a 5xx, or a network failure skips the URL: status `robots-blocked`, no image, no charge. A 404, any other 4xx, or an empty file allows the capture. The robots URL goes through the same private-address guard as the page. If that URL itself is blocked, the row is `ssrf-blocked`. If a redirect of `robots.txt` is blocked, the file counts as unreachable and the row is `robots-blocked`. Set `respectRobotsTxt` to false to skip this check.

v1 does not emulate a phone model, switch a colour scheme, or compare two screenshots. `hideCookieBanners` (default false) is a best-effort banner hide, not a login. `waitSecs` (0 to 30, default 0) pauses after `waitUntil` and before the screenshot. The image is the page as served to the viewport you set (or the full scroll height when `fullPage` is true), at `deviceScaleFactor` 1–4 (default 1). Higher scale means more device pixels for the same CSS viewport — sharper bitmaps, larger files, higher event price.

The schema allows at most 500 screenshots. The console prefill is 5. The default when the field is omitted is 50. Each page waits at most `timeoutSecs` (default 60).

### FAQ

**Does it log in or keep cookies?** No. No username, password, cookie jar or saved browser state. You get the page an anonymous browser gets.

**Why was a social URL not captured?** Facebook, Instagram, LinkedIn, X, TikTok, Threads, Pinterest, Snapchat, Reddit and their short domains are rejected before any request. The row is `social-blocked` and is free.

**Does it respect robots.txt?** Yes, by default. It downloads `robots.txt` for the page origin and skips a path UrlwrightBot is disallowed from. That row is `robots-blocked` and free. A missing file (404) allows the capture. A server error or a network failure while reading `robots.txt` also skips the page. Set `respectRobotsTxt` to false to capture anyway.

**Viewport or full page?** The default is the 1280×720 viewport (`fullPage: false`). `fullPage: true` captures the scrollable page. Both use the same event when the file is stored.

**PNG or JPEG?** `format` is `png` (default) or `jpeg`. `quality` applies only to JPEG (default 85). The key ends in `.png` or `.jpg`.

**Will one bad URL fail the run?** No. Other URLs continue. Rejected input writes one `invalid-input` row and stops. `RUN_SUMMARY` is always written.

**Can it hide a cookie banner?** Yes, when `hideCookieBanners` is true. It hides common consent banners (including Cookiebot and Usercentrics) with CSS, calls the Usercentrics consent API when the page provides it, and clicks one common accept or reject button. If that fails, the screenshot still runs. It does not log in and it does not keep a cookie jar. There is no phone preset, no dark-mode switch and no before/after comparison. Set `viewportWidth` / `viewportHeight` for the window size, and `deviceScaleFactor` 2–4 for retina-style bitmaps (@2x / @3x / @4x).

**Can it wait for a late hero?** Yes. `waitSecs` (0 to 30, default 0) waits that many seconds after the page is ready and before the screenshot. Use a few seconds on marketing sites that stay blank at `domcontentloaded`. The same capture event is charged. A longer wait uses more compute time, not a new event.

**What does a screenshot cost?** One successful file is one event. At 1× (default) that is `screenshot-captured` (FREE $0.016 / GOLD $0.014). At 2× / 3× / 4× the events are `screenshot-captured-2x` / `-3x` / `-4x` with higher list prices. Blocked and failed URLs are free. See the Pricing table.

**What is device scale / @2x?** `deviceScaleFactor` is Playwright’s device pixel ratio. 1 keeps CSS pixels = image pixels. 2 writes roughly four times as many pixels for the same viewport (retina-style). It does not make the site “better”; it only increases bitmap sharpness and file size. Prefer 1× unless you need a sharp asset for high-DPI displays.

**Where is the image URL?** On ok rows, `screenshotUrl` is a signed Apify KV record URL (`?signature=`). You can open it in a browser or `curl` without an API token even when the account uses Restricted General resource access (Anyone-with-ID would also work with the unsigned form, but signed is what we emit). The link is permanent for that record for as long as the file remains in the store. You can also download via `screenshotKey` in the run’s default key-value store with a token.

### No warranty

The output depends on the sites you open. Pages change, block automated browsers, or look different once a person is logged in. A screenshot is not proof of how a page appeared to every visitor, and it is not legal advice. No warranty is given that the capture matches a particular browser, or that the file remains available after the run storage expires.

# Actor input Schema

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

Public HTTP(S) pages to capture. Domains are normalised to https://. Accepts plain URL strings or Apify request objects with a url field (e.g. { "url": "https://…" }). items is omitted so both shapes pass platform validation.

## `datasetId` (type: `string`):

Dataset whose rows supply extra page URLs (field from datasetField).

## `datasetField` (type: `string`):

Field name on dataset rows. Fallback order when missing: url, finalUrl, domain, inputUrl.

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

Capture the full scrollable page instead of the viewport only.

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

Browser viewport width.

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

Browser viewport height (ignored for layout when fullPage is true, but still sets the initial window).

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

Playwright deviceScaleFactor: 1 (default CSS pixels), 2 (@2x), 3 (@3x) or 4 (@4x). Only 1, 2, 3 or 4 are accepted. Higher values produce sharper bitmaps and cost more. Viewport width/height stay in CSS pixels.

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

PNG (lossless) or JPEG (smaller).

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

Only used when format is jpeg (1–100).

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

Playwright load state before capture.

## `waitSecs` (type: `integer`):

Seconds to wait after the page reaches waitUntil and before the screenshot. Use this when a hero or animation paints late. 0 skips the wait. This is on top of the navigation timeout.

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

Best-effort hide of common cookie consent banners, including Cookiebot and Usercentrics: CSS, the Usercentrics consent API when the page provides it, then one click on a common accept or reject button. A failed dismiss does not fail the capture. This does not log in, fill forms, or keep a cookie jar.

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

Per-page navigation and screenshot timeout.

## `maxScreenshots` (type: `integer`):

Hard cap on pages captured in this run.

## `respectRobotsTxt` (type: `boolean`):

Skip pages disallowed for UrlwrightBot.

## Actor input object example

```json
{
  "startUrls": [
    "https://urlwright.com/"
  ],
  "datasetField": "url",
  "fullPage": false,
  "viewportWidth": 1280,
  "viewportHeight": 720,
  "deviceScaleFactor": 1,
  "format": "png",
  "quality": 85,
  "waitUntil": "networkidle",
  "waitSecs": 0,
  "hideCookieBanners": false,
  "timeoutSecs": 60,
  "maxScreenshots": 5,
  "respectRobotsTxt": true
}
```

# Actor output Schema

## `overview` (type: `string`):

Dataset rows for each requested URL.

## `runSummary` (type: `string`):

Counts, charged events and why the run stopped.

# 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": [
        "https://urlwright.com/"
    ],
    "maxScreenshots": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("urlwright/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 = {
    "startUrls": ["https://urlwright.com/"],
    "maxScreenshots": 5,
}

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

```

## MCP server setup

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