# Website Screenshot API — URL to PNG/JPEG/PDF for AI Agents (`samgov-tools/url-screenshot`) Actor

Screenshot any public URL to PNG, JPEG or PDF with Chromium. Predictable JSON output with a direct image URL and explicit errors, built for AI agents, API callers and Apify MCP. $2 per 1,000 screenshots; failed URLs are free.

- **URL**: https://apify.com/samgov-tools/url-screenshot.md
- **Developed by:** [AgentReady Tools](https://apify.com/samgov-tools) (community)
- **Categories:** Developer tools, AI, Automation
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.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 — URL to PNG/JPEG/PDF for AI Agents

Capture public web pages with Playwright Chromium. Each processed URL produces one JSON dataset item with a direct `screenshotUrl`, or a short `error`. Files live in the run's default key-value store. Use screenshots for visual inspection, responsive layout checks, document previews and agent workflows.

### Quick start

Run with `{}` to capture `https://example.com`, or pass:

```json
{"urls":["https://example.com"],"device":"mobile","format":"png"}
```

All options have defaults. An empty URL array also uses the example page. URL safety failures produce free error items; malformed input options fail the run. Extra unique URLs above `maxUrls` are ignored with a status message. Processing is sequential.

For local development (Node.js 22 or later):

```sh
npm ci
npx playwright install chromium
npm run build
npm test
npm start
## Optional real Chromium fixture checks:
RUN_BROWSER_TESTS=1 npm test
```

Local SDK storage is under `storage/`.

### For AI agents

Connect your MCP client to the [Apify MCP server](https://mcp.apify.com) and enable Actor `samgov-tools/url-screenshot`. To load just this Actor, use `https://mcp.apify.com?tools=samgov-tools/url-screenshot`. Sign in through the client’s OAuth flow or configure an Apify API token. Discover the exposed tool name through your client. See the [MCP documentation](https://docs.apify.com/mcp) for setup details.

Example prompt:

> Use the Apify `samgov-tools/url-screenshot` Actor to capture https://example.com on mobile. Return the screenshotUrl and title. If error is non-null, report that error instead.

Pass `urls`; leave the other fields at defaults unless needed. Read each item's `error` before using `screenshotUrl`. Signed `screenshotUrl` links work without authentication. The HTTP `status` is independent of capture success: a 403/404 page can still be captured honestly. A successful screenshot does not mean the site allowed access to its intended content.

REST API ([synchronous dataset endpoint](https://docs.apify.com/api/v2/act-run-sync-get-dataset-items-post)):

```sh
curl --fail-with-body -X POST \
  'https://api.apify.com/v2/acts/samgov-tools~url-screenshot/run-sync-get-dataset-items?timeout=300' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"urls":["https://example.com"],"format":"png"}'
```

Large batches may exceed synchronous API limits; start an asynchronous run and retrieve its dataset when finished.

JavaScript client:

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('samgov-tools/url-screenshot').call({
    urls: ['https://example.com'], fullPage: true,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) console.log(item.error ?? item.screenshotUrl);
```

#### Paying agents without an Apify account (x402 / MPP)

An agent without an Apify account can buy a prepaid Apify API token from Apify AGI (`agi.apify.com`) with x402 (USDC on Base or Solana), MPP (Tempo or Solana) or Skyfire, then use it as the Apify API token in this README's REST example (Actor `samgov-tools~url-screenshot`) or as the bearer token for the Apify MCP server. This Actor uses limited permissions, which prepaid tokens require. Apify's current terms: minimum $1, the balance is a hard spending cap, the token expires 14 days after purchase, unused balance is non-refundable, and the feature is experimental. Check `agi.apify.com` for current terms before paying.

The token pays this Actor's normal pay-per-event prices: `apify-actor-start`: $0.0005 per run, `screenshot`: $0.002 per stored capture, `full-page-or-pdf`: +$0.001 per full-page capture or PDF. See [Let your AI agent pay for Actor runs](https://docs.apify.com/agents/payments), [x402](https://docs.apify.com/platform/integrations/x402) and [MPP](https://docs.apify.com/integrations/mpp).

### Input

| Field | Default | Purpose / example |
| --- | --- | --- |
| `urls` | `["https://example.com"]` | Public URLs; trimmed, deduplicated in order. API also accepts `[{"url":"https://example.com"}]`. Missing/empty array uses default. |
| `maxUrls` | `10` | Process at most 1–100 unique URLs; e.g. `3`. |
| `fullPage` | `false` | Capture a long page; e.g. `true`. Raster height clips at 16,384 CSS px. |
| `device` | `desktop` | `desktop`: 1280×800 DPR 1; `mobile`: 390×844 DPR 2; `tablet`: 820×1180 DPR 2. Mobile uses the built-in iPhone 13 descriptor and ordinary mobile UA; mobile/tablet use touch. |
| `width` | Device width | Override viewport, 320–3840 CSS px; e.g. `1440`. |
| `height` | Device height | Override viewport, 240–2160 CSS px; e.g. `900`. |
| `format` | `png` | `png`, `jpeg`, or `pdf`; e.g. `pdf` for print output. |
| `jpegQuality` | `80` | 1–100, JPEG only; e.g. `60` for smaller files. |
| `waitUntil` | `load` | `load`, `domcontentloaded`, or `networkidle`; e.g. `domcontentloaded` for speed. Busy pages may never reach networkidle. |
| `delayMs` | `0` | Wait after load, 0–10000 ms; e.g. `1000` for client-rendered pages. |
| `blockCookieBanners` | `true` | CSS-hide common banners; e.g. `false` to inspect consent UI. No clicking or consent submission. |
| `timeoutSecs` | `30` | Navigation timeout, 5–60 seconds; e.g. `45` for a slow page. |

The console schema makes `urls` optional with both a default and prefill. Empty input captures the example page. Schema string-list editing is intended for strings; object entries are a runtime API convenience.

### Output

```json
{
  "url": "https://example.com",
  "finalUrl": "https://example.com/",
  "status": 200,
  "title": "Example Domain",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/STORE_ID/records/screenshot-1-example-com",
  "format": "png",
  "width": 1280,
  "height": 800,
  "fullPage": false,
  "bytes": 15432,
  "takenAt": "2026-11-14T12:00:00.000Z",
  "error": null
}
```

Field order is stable. `width` and `height` report the actual PNG/JPEG image dimensions in **physical pixels**, read from the PNG IHDR or JPEG SOF header. Raster pixels scale with DPR; full-page height includes the current scrollable content up to the 16,384 CSS pixel cap. Full-page capture uses Playwright `fullPage: true`, with a clip at the cap for taller pages. For successful PDF captures these fields report the configured viewport in **CSS pixels**. Failed captures return null for both dimensions. PDF uses Chromium's print layout, background printing and normal pagination; screen viewport dimensions do not specify PDF paper size. PDF layout is clipped at 16,384 CSS px and printing is limited to 50 pages. `fullPage` is always true for PDF.

On failure `screenshotUrl`, `bytes`, `width` and `height` are null; available navigation metadata remains populated. `takenAt` is an ISO timestamp of successful storage completion, or the failed attempt's start. Files use keys `screenshot-<1-based-index>-<sanitized-host>` and their actual MIME type. URLs come from the SDK's `getPublicUrl`, including signed URLs when needed. Signed `screenshotUrl` links work without authentication. Treat a signed URL as a bearer link and observe storage retention settings. A storage write that succeeds before a later URL/dataset operation fails can leave an uncharged file in the store.

### Safety and limits

- Only public HTTP/HTTPS pages and ports 80/443. No URL credentials, local hostnames, login/cookies input, user proxies, CAPTCHA solving or stealth techniques. Each URL uses a fresh context without saved cookies.
- Hostnames resolve with `dns.lookup({ all: true })`; any private/reserved answer rejects the host. Each proxy connection/request resolves once and connects to a verified literal IP; answers are not reused for later connections. Guards reject loopback, link-local/metadata, private, CGNAT, documentation, multicast and reserved ranges, including IPv4-mapped IPv6 and private addresses embedded in NAT64/6to4. WHATWG URL parsing normalizes decimal, octal and hex IPv4 literals before checking.
- A context route performs cheap scheme/host checks on main requests, subresources and iframes. Chromium uses an in-process proxy bound to a random port on 127.0.0.1 for HTTP and HTTPS/CONNECT, including loopback destinations. Every redirect hop and worker connection passes through the proxy. WebSockets are closed; service workers and downloads are blocked. CSS banner hiding does not bypass paywalls or site access controls.
- The proxy pins upstream sockets to the validated IP, preventing DNS rebinding between validation and connection. Only destination ports 80 and 443 are allowed. DNS/policy checks, upstream connections and idle sockets have a 15-second timeout. QUIC and non-proxied WebRTC UDP are disabled. Proxy sockets and the server close with the browser. Network/egress isolation remains useful defense in depth.
- One browser, concurrency 1. The browser deadline per URL is `timeoutSecs + delayMs/1000 + 30` seconds; timeout closes its context. Storage operations are awaited separately. Navigation has its own `timeoutSecs` limit. A timed-out URL produces an error item and processing continues.
- Up to 100 URLs, raster height 16,384 CSS pixels, PDF at most 50 pages, maximum stored file 20 MiB. Oversized files produce errors and are not stored. Rendering may allocate memory before the byte limit can be checked; pathological pages can still exhaust the container. Default memory 1024 MB; configurable 512–4096 MB.
- A site's 4xx/5xx page is captured with its real HTTP status; unavailable pages produce safe errors without stack traces. File storage failures produce error items; dataset write failures fail the run because results cannot be delivered reliably.

### Pricing

The Actor’s Pricing tab is authoritative. Current prices:

| Event | Charged when | Price |
| --- | --- | --- |
| `apify-actor-start` | Automatically by Apify once per run, up to 1 GB memory | $0.0005 per run |
| `screenshot` | One successfully stored capture pushed to the dataset | $0.002 ($2 per 1,000) |
| `full-page-or-pdf` | Extra for each successful full-page capture or PDF | +$0.001 per capture |

100 viewport screenshots in one run cost **$0.2005**; 100 full-page screenshots cost **$0.3005**. The synthetic `apify-actor-start` event is charged automatically by Apify; the Actor does not charge it in code or add it per URL.

Failed URLs are free; the run-start fee still applies. A PDF with `fullPage: true` gets only one extra event. Before each URL both required event budgets and their combined cost (including any platform default-dataset-item fee) are checked; if insufficient, processing stops successfully and reports stored counts. `ACTOR_MAX_PAID_DATASET_ITEMS` additionally clamps URLs attempted (including failures); zero processes none. Dataset push charge-limit results are respected. Budget checks use platform pricing. Budget checks and the two event charges are separate SDK operations, so a concurrently changing platform budget can prevent an extra charge after capture; the Actor stops when this occurs.

### FAQ

**Why is my screenshot blank or blocked?** Some pages require authentication, reject automated browsers or render slowly. Inspect `status` and `error`; try a modest delay or another wait condition. There is no bypass feature.

**Does banner hiding always work?** No. It only adds CSS for common selectors. It does not click, submit consent, or inspect closed shadow roots.

**Can I capture localhost, metadata endpoints or private apps?** No. Public pages only. Nonstandard ports are also rejected.

**Does fullPage scroll and load every lazy image?** No. It captures the current layout; it does not run an incremental scrolling crawl.

**Can I use a screenshot URL later?** Yes while the store and its access policy remain available. Signed links and run retention can limit lifetime.

### Build notes

Both Docker stages use `apify/actor-node-playwright-chrome:22-1.65.1`; npm Playwright is exactly `1.65.1`. The [official base Dockerfile](https://github.com/apify/apify-actor-docker/blob/master/node-playwright-chrome/Dockerfile) uses non-root `myuser`, `/home/myuser` and `/pw-browsers`; project copies use ownership accordingly. Browser downloading is disabled during Docker installs and the inherited browser path is preserved.

`python3 scripts/make-icon.py` regenerates the 1024×1024 browser capture icon (Pillow required). `python3 scripts/make-schemas.py` regenerates the committed schemas.

# Actor input Schema

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

Public HTTP(S) pages to capture. Default: \["https://example.com"], also used for an empty list. Pass target pages, e.g. \["https://example.com"]. Trims and deduplicates; extra URLs above maxUrls are ignored. API also accepts {"url":"https://example.com"} entries.

## `maxUrls` (type: `integer`):

Maximum unique URLs to process. Default: 10; hard maximum: 100. Use to control spend, e.g. 3. Extra URLs are ignored with a status message.

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

Capture the full scrollable page, clipped at 16384 CSS pixels high. PNG/JPEG output width and height report actual image pixels (including device scale). Default: false. Use true for long pages, e.g. an article. PDF always implies fullPage and adds the same extra pricing event.

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

Viewport preset. Default: desktop (1280x800, DPR 1). Use mobile (390x844, DPR 2, touch and standard iPhone UA) or tablet (820x1180, DPR 2, touch) to inspect responsive layout, e.g. "mobile".

## `width` (type: `integer`):

Override viewport width in CSS pixels. Default: selected device width (desktop 1280). Use for a custom layout, e.g. 1440. Range 320–3840.

## `height` (type: `integer`):

Override viewport height in CSS pixels. Default: selected device height (desktop 800). Use for a custom viewport, e.g. 900. Range 240–2160.

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

Capture file type. Default: png. Use jpeg for smaller photos or pdf for a printable document, e.g. "pdf". PDF prints backgrounds, implies fullPage and adds an extra pricing event.

## `jpegQuality` (type: `integer`):

JPEG quality from 1–100, ignored for PNG/PDF. Default: 80. Lower it to reduce file size, e.g. 60.

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

Navigation readiness condition. Default: load. Use domcontentloaded for faster captures or networkidle for pages with delayed content, e.g. "domcontentloaded". Busy pages may time out with networkidle.

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

Extra milliseconds to wait after navigation. Default: 0. Use for client-rendered content, e.g. 1000. Range 0–10000.

## `blockCookieBanners` (type: `boolean`):

Hide common consent banners using CSS only. Default: true. Use false to inspect the original consent UI, e.g. false. Does not click or grant consent; custom banners may remain.

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

Per-URL navigation timeout in seconds. Default: 30. Use a higher value for slow public pages, e.g. 45. Range 5–60; overall browser deadline adds delayMs plus 30 seconds.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "maxUrls": 10,
  "fullPage": false,
  "device": "desktop",
  "format": "png",
  "jpegQuality": 80,
  "waitUntil": "load",
  "delayMs": 0,
  "blockCookieBanners": true,
  "timeoutSecs": 30
}
```

# Actor output Schema

## `items` (type: `string`):

Dataset items with screenshotUrl or safe error.

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

Run default key-value store containing PNG/JPEG/PDF records.

# 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://example.com"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("samgov-tools/url-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://example.com"] }

# Run the Actor and wait for it to finish
run = client.actor("samgov-tools/url-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://example.com"
  ]
}' |
apify call samgov-tools/url-screenshot --silent --output-dataset

```

## MCP server setup

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