# Website Screenshot and PDF API - PNG, JPEG, PDF from any URL (`superslowsloth/website-screenshot-pdf`) Actor

Capture any public web page as PNG, JPEG or PDF with headless Chromium. Full-page, mobile and tablet presets, dark mode, wait-for-selector. One row per URL, private addresses refused, failed URLs never charged.

- **URL**: https://apify.com/superslowsloth/website-screenshot-pdf.md
- **Developed by:** [Superslow Sloth](https://apify.com/superslowsloth) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.80 / 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

## Website Screenshot and PDF API

Give it URLs, get back a PNG, JPEG or PDF of each page, plus one structured row
per URL saying what happened. Headless Chromium, no API key of your own, no
browser to run, no machine-learning models.

Built to be called by scripts and AI agents: the input is a flat JSON object, the
output is one dataset row per URL in the order you sent them, and every failure
says why in a sentence.

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `urls` | array | - | Public `http(s)` pages. Up to 100 per run. |
| `format` | `png` / `jpeg` / `pdf` | `png` | Image or print-layout PDF. |
| `device` | `desktop` / `mobile` / `tablet` | `desktop` | 1280x800 at 1x, 390x844 at 2x with touch, 820x1180 at 2x with touch. |
| `viewportWidth`, `viewportHeight` | integer | preset | Override the preset's size (200-3840 by 200-4000). |
| `fullPage` | boolean | `false` | Capture the whole scrollable page. Scrolls once first so lazy images load. Images only. |
| `maxFullPageHeight` | integer | `12000` | CSS pixels. Taller pages are cut here and the row says `truncated: true`. Capped at 8192 for the 2x presets, which is Chromium's texture limit. |
| `waitUntil` | `load` / `domcontentloaded` / `networkidle` | `load` | When navigation counts as done. |
| `delayMs` | integer | `0` | Extra wait before capturing, up to 30000. |
| `waitForSelector` | string | - | CSS selector that must be visible before capturing. Not found in time means the URL fails, uncharged. |
| `darkMode` | boolean | `false` | Sets `prefers-color-scheme: dark`. |
| `timeoutSecs` | integer | `30` | Per-page timeout, 5-120. If the load event is not reached in time but the document is there, the page is captured as it stands and the row says `wait_timed_out: true`. A page that never responds fails. |
| `captureErrorPages` | boolean | `false` | Capture pages that answer 4xx/5xx instead of failing them. |
| `pdfFormat`, `pdfLandscape`, `pdfPrintBackground` | | `A4`, `false`, `true` | Paper size, orientation and whether backgrounds print. |

### Output

The file goes to the run's key-value store and each dataset row carries its key
and public URL. One row per input URL, always, in input order:

```
index, url              position and the URL you sent
status                  ok | failed | blocked | skipped
error                   why it is not ok, in one sentence; null when ok
final_url, title        where the browser ended up, and the page title
http_status             status of the main document
output_key, output_url  the file in the key-value store, and its public URL
content_type, output_bytes
width, height           pixels of the delivered image (null for PDF)
pages                   page count of a PDF (null for images)
truncated               a full-page capture was cut at maxFullPageHeight
wait_timed_out          the wait condition was not met in time; captured as it stood
format, device, viewport_width, viewport_height, full_page
blocked_requests        sub-requests the page made at private addresses that were refused
billed_event            what this row was charged under; null if not charged
navigation_ms, capture_ms, total_ms
```

### Billing

Pay per event: a small `actor-start` fee, then one charge per file produced.

- `screenshot-captured` for a PNG or JPEG, `pdf-rendered` for a PDF.
- **Only a file that was produced, stored and is under the size limit is charged.**
  A URL that fails, times out, answers 404, is refused as private, or whose
  selector never appears gets a row with the reason and costs nothing.
- The run stops as soon as your spending limit is reached. URLs not reached get a
  `skipped` row so nothing silently goes missing.

### What it will not fetch

This Actor renders pages for anyone who can start it, so it refuses to be a way
into someone else's network. It only fetches public `http` and `https` URLs:

- `file:`, `chrome:`, `ftp:`, `data:` and every other scheme are refused.
- Loopback (`localhost`, `127.0.0.0/8`, `::1`), private ranges, link-local
  including the cloud metadata address `169.254.169.254`, CGNAT, multicast and
  their IPv6 equivalents are refused - in every spelling a browser accepts,
  including decimal, hex and octal forms and IPv4-mapped IPv6.
- A name that resolves to any private address is refused.
- The check repeats for every redirect and for every request the page itself
  makes, so a public page cannot make the browser fetch an internal address, and
  the browser's connections go through a proxy that connects only to the address
  it checked, which defeats DNS rebinding.
- Ports used by mail, databases, SSH and similar services are refused.

A refused URL gets `status: blocked`.

### Limits

100 URLs per run, 25 MB per file, full-page height capped as above, one page
timeout per URL. Pages that need a login, solve a CAPTCHA or sit behind a bot
wall are captured as the wall, not bypassed.

# Actor input Schema

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

Public http(s) pages to capture, one per entry. Up to 100 per run. Private, loopback, link-local and cloud-metadata addresses are refused, and so is any page that redirects to one. A refused or failed URL gets a row explaining why and is not charged.

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

PNG is lossless and the default. JPEG is much smaller for photographic pages. PDF prints the page with the browser's print engine and is billed as its own event.

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

Sets the viewport, pixel density, touch support and user agent. Desktop is 1280x800 at 1x, mobile is 390x844 at 2x, tablet is 820x1180 at 2x. Viewport width and height below override the preset's size.

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

Overrides the device preset's width. Leave empty to use the preset. 200 to 3840.

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

Overrides the device preset's height. Leave empty to use the preset. 200 to 4000.

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

Capture the whole scrollable page instead of just the first screen. The page is scrolled once first so lazy-loaded images load. Images only; a PDF is always the whole document.

## `maxFullPageHeight` (type: `integer`):

A full-page capture taller than this is cut off at this height and the row says truncated. Chromium cannot render more than 16384 device pixels, so mobile and tablet presets (2x) are capped at 8192 CSS pixels whatever is set here.

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

When navigation counts as finished. load waits for the load event, domcontentloaded is faster and may miss images, networkidle waits for 500 ms with no network traffic and fails on pages that never go quiet.

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

Extra time to wait after the page is ready and before capturing, for animations and late content. 0 to 30000.

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

Wait until an element matching this CSS selector is visible before capturing. If it does not appear within the timeout the URL fails with a clear error and is not charged. Leave empty to skip.

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

Ask the page for its dark colour scheme (prefers-color-scheme: dark). Pages without a dark theme look the same.

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

How long to wait for each page to load, and again for the selector. 5 to 120. A page that never responds fails and is not charged. If the load event is not reached in time but the page has its document, it is captured as it stands and the row says wait_timed_out. The capture step itself always gets at least 30 s.

## `captureErrorPages` (type: `boolean`):

By default a page that answers 4xx or 5xx fails with its status and is not charged. Turn on to capture the error page anyway; the row still reports the status.

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

Paper size when the format is PDF. Ignored for images.

## `pdfLandscape` (type: `boolean`):

Print in landscape orientation. Ignored for images.

## `pdfPrintBackground` (type: `boolean`):

Include background colours and images. Browsers leave them out of print by default, which makes many pages look blank; this is on unless you turn it off.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "format": "png",
  "device": "desktop",
  "fullPage": false,
  "maxFullPageHeight": 12000,
  "waitUntil": "load",
  "delayMs": 0,
  "darkMode": false,
  "timeoutSecs": 30,
  "captureErrorPages": false,
  "pdfFormat": "A4",
  "pdfLandscape": false,
  "pdfPrintBackground": true
}
```

# Actor output Schema

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

// Run the Actor and wait for it to finish
const run = await client.actor("superslowsloth/website-screenshot-pdf").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("superslowsloth/website-screenshot-pdf").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 superslowsloth/website-screenshot-pdf --silent --output-dataset

```

## MCP server setup

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

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/XcMSuDZlaS6Hu1IGX/builds/eulNQRHf9eoVxHh4z/openapi.json
