# Website Screenshot & HTML to PDF Generator API (`power_on/screenshot-url-pdf`) Actor

Input: page URLs, or your own HTML. Output: one row per capture with a link to the file — PNG, JPEG, WebP or PDF; full page, viewport, custom size or one CSS-selected element. Banners hidden, ads blocked, lazy images loaded. Device presets, retina, dark mode, locale. Failed URLs are not charged.

- **URL**: https://apify.com/power_on/screenshot-url-pdf.md
- **Developed by:** [Power On Labs](https://apify.com/power_on) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Features:** Limited permissions
- **Stats:** 19 total users, 3 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $7.00 / 1,000 screenshots

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
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 & PDF Generator API

A **website screenshot API** that captures any web page as a PNG, JPEG, WebP or PDF —
full page, visible viewport, or a single element. Built for the part every other
screenshot tool leaves broken: the cookie banner covering the hero image, the lazy
images that never loaded, the ad slot that left a grey hole in the middle of your
capture.

Give it a list of URLs — or your own HTML, for documents that have no address yet. Get
back clean images (or PDFs) and a dataset row per capture with a direct file link —
usable from the Apify API, the REST API, or the visual UI, with no server of your own
to run or maintain.

### What makes this website screenshot API different

Most "URL to image" tools stop at rendering the page and hoping for the best. This one
handles the reasons a plain screenshot looks broken:

| | |
|---|---|
| **Cookie banners removed** | Consent overlays from OneTrust, Cookiebot, Didomi, Quantcast, Usercentrics, Sourcepoint, Iubenda and 40+ others are stripped before the capture. The default **removes** the banner rather than clicking it, so no consent is given and no tracking cookies are set. Switch to "reject all" or "accept all" if you need a real click instead. |
| **Ads blocked and their empty slots collapsed** | Blocking ad requests alone leaves grey boxes labelled "Advertisement". Those get cleaned up too, so a full-page screenshot doesn't look broken. |
| **Lazy-loaded images actually load** | The page is scrolled before a full-page capture, so images below the fold render instead of coming back blank. |
| **Real device presets** | Desktop 1920, laptop 1366, iPad and iPhone viewports with matching user agent, touch support and pixel density — not just a resized browser window. |
| **Retina / high-DPI output** | 2x and 3x pixel density for crisp images in presentations, decks and documentation. |
| **Element screenshots** | Give a CSS selector and capture just that element — a pricing table, a chart, a single card — instead of the whole page. |
| **Webpage-to-PDF export** | Single-page PDF sized to the real page height, or standard A4, as an alternative output to an image. |
| **HTML to PDF** | Send your own HTML instead of a URL — an invoice, a report, a receipt — and get it back as a PDF or an image, using the same rendering engine. |
| **Batch capture in parallel** | Many URLs in one run, with automatic retries. One broken URL doesn't kill the run — you get a row explaining what went wrong and the rest complete normally. |

### Quick start

Minimal input — just a list of URLs:

```json
{ "urls": ["https://example.com"] }
```

Call it however fits your stack: the Apify API and REST endpoints, the official
Node.js and Python clients, or no-code via Zapier, Make or n8n through Apify's
integrations. A minimal call with the JavaScript client:

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

const client = new ApifyClient({ token: '<APIFY_TOKEN>' });
const run = await client.actor('power_on/screenshot-url-pdf').call({
  urls: ['https://example.com'],
  device: 'desktop',
  format: 'png',
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].fileUrl);
```

And with the Python client:

```python
from apify_client import ApifyClient

client = ApifyClient('<APIFY_TOKEN>')
run = client.actor('power_on/screenshot-url-pdf').call(
    run_input={'urls': ['https://example.com'], 'format': 'pdf'}
)
items = client.dataset(run['defaultDatasetId']).list_items().items
print(items[0]['fileUrl'])
```

### HTML to PDF

Instead of a URL, you can send the HTML yourself. It is rendered in the same browser
and comes back in the same output formats — useful for invoices, reports, receipts and
email templates that exist as markup and have no public address:

```json
{
  "html": "<h1>Invoice 2026-0042</h1><table>...</table>",
  "format": "pdf",
  "fullPage": false
}
```

`fullPage: false` gives a standard **A4** page, which is what a printable document
usually wants; `fullPage: true` gives a single page as wide as the viewport and as tall
as the content. Images, CSS and fonts must use absolute URLs (`https://…`) or data
URIs: inline HTML has no address of its own for relative paths to resolve against. When
`html` is set, `urls` is ignored.

### Input parameters

Give it either `urls` or `html`. Everything else has a sensible default.

```json
{
  "urls": [
    "https://example.com",
    "https://news.ycombinator.com"
  ],
  "device": "desktop",
  "fullPage": true,
  "format": "png",
  "scaleFactor": 2,
  "dismissCookieBanners": "hide",
  "blockAds": true
}
```

Useful extras: `selector` (capture one element), `hideSelectors` (kill a sticky header
or a chat widget), `customCss`, `darkMode`, `waitForSelector`, `delayMs`, `locale`,
`timezone`, `cookies`, `headers` and `basicAuthUsername` / `basicAuthPassword` for pages
behind a login, and `proxyConfiguration` for geo-specific pages. `cookies` and `headers`
are stored encrypted and kept out of the run log, so a session token or an
`Authorization` header is safe to put there. The full list, with descriptions and
defaults, is in the **Input** tab.

### Output

One dataset row per URL:

```json
{
  "url": "https://example.com",
  "ok": true,
  "statusCode": 200,
  "title": "Example Domain",
  "format": "png",
  "width": 1920,
  "height": 3480,
  "pageHeight": 3480,
  "truncated": false,
  "bytes": 214233,
  "device": "desktop",
  "scaleFactor": 2,
  "durationMs": 1842,
  "key": "001-example.com.png",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/.../records/001-example.com.png"
}
```

`width` and `height` describe **what is actually in the file**, in CSS pixels — the
whole page, the viewport, the A4 sheet, or the element you selected — so the file is
`width x height x scaleFactor` real pixels. `pageHeight` is how tall the document was,
and `truncated` says whether the capture stopped short of it.

The image or PDF is stored in the run's key-value store, and `fileUrl` is a direct
link to it — no separate download step. Failed URLs come back with `ok: false` and
the reason in `error`, instead of stopping the run.

`loadState` says how far the page had loaded when it was captured, and `loadComplete`
whether that is as far as you asked with `waitUntil`. A page that has its content but is
still waiting on a slow ad or tracker is not left hanging: 15 seconds after its content
arrives it is captured as it is, with `loadComplete: false`, instead of failing.

### Typical use cases

- **Open Graph and social preview images** — generate the image your link preview
  uses when shared on Slack, X or LinkedIn
- **Visual monitoring** — capture the same pages on a schedule and compare them over
  time to catch layout regressions or unwanted changes
- **Webpage-to-PDF export** — archive a page as a PDF with the layout intact, for
  compliance, records or offline reading
- **Design and QA review** — desktop, tablet and mobile capture in one run, side by
  side
- **Documentation and changelogs** that need current, accurate screenshots without a
  human taking them by hand

### Pricing

Pay per screenshot: **$0.01** per successful capture, less on higher Apify plans. A
failed URL is not charged. There's no monthly subscription and no minimum commitment —
you pay for what you actually capture, unlike most standalone screenshot APIs that
charge a fixed monthly plan whether you use it or not.

**From October 18, 2026, the run's Apify platform usage is billed to your account as
well**, as with most Store Actors. At the default 2 GB of memory a one-URL run uses
roughly **$0.001–0.003** of platform time, the higher end when the server starts cold.
Many URLs in one run are cheaper per screenshot than one run per URL, because the
start-up is paid once.

### FAQ

**Is there a free website screenshot API?**
This Actor has no free tier of its own, but every new Apify account starts with free
monthly platform credit, which covers a number of screenshots before any charge
applies.

**Can I take a full-page screenshot, not just the visible area?**
Yes — set `fullPage: true` (the default). The page is scrolled first so lazy-loaded
images below the fold render before the capture.

**Can I convert a webpage to PDF instead of an image?**
Yes — set `format: "pdf"`. With `fullPage: true` you get a single-page PDF sized to the
real page height, up to 100,000 pixels; past that the PDF stops there, the run says so
in the log, and the row comes back with `truncated: true` and the real `pageHeight`.
With `fullPage: false` you get standard A4 instead.

**Does it handle cookie consent banners automatically?**
Yes, by default. Overlays from the major consent management platforms are removed
before capture, without clicking "accept" — so no consent is granted and no tracking
cookie is set. You can switch to a real "accept all" or "reject all" click instead.

**Can I screenshot just one element instead of the whole page?**
Yes — pass a CSS `selector` and only that element is captured.

**Does this work with pages behind a login?**
Yes, via `cookies`, `headers`, or `basicAuthUsername` / `basicAuthPassword`, depending
on how the site authenticates. All of those are secret input fields: they are stored
encrypted and never printed in the run log.

### Known limitations

Worth knowing before you run it:

- On a few sites an ad container also holds real content, and its reserved empty space
  survives the clean-up. Removing it would risk deleting real content, so it stays.
- Very tall pages: an image is clipped only if the browser engine refuses the capture
  outright, and then the row says `truncated: true` — in practice captures well past
  20,000 pixels come back whole. A full-page PDF is capped at 100,000 pixels, which no
  ordinary page reaches, and a page past it is reported as `truncated` rather than
  quietly cut.
- Pages behind a bot wall may need `proxyConfiguration` and a longer `timeoutSecs`.
- After the page's content arrives, the wait for `load` is capped at 15 seconds and the
  wait for network idle at 8. If you need something specific on screen, use
  `waitForSelector` or `delayMs`, which wait as long as you say.
- Parallel pages are limited to about one per GB of run memory (2 GB by default, so
  two at once): more pages than that compete for the CPU and make the run slower, not
  faster. Give the run more memory for bigger batches, up to 4 GB.
- Video and animation are captured as a still frame, at whatever moment the page is in.

### Related tools

**Also from Power On Labs:**

- [Sitemap URL Extractor](https://apify.com/power_on/sitemap-url-list) —
  turns a domain into every page URL it publishes. Its output is already the list
  this Actor takes in `urls`, so the two chain: sitemap first, capture second, when
  you want a screenshot of every page rather than of the home page.
- [Tech Stack Audit](https://apify.com/power_on/tech-stack-audit) —
  what each of those pages is built with: CMS, framework, CDN, analytics and tracking
  pixels, with the evidence for every detection.
- [PDF to JSON Extractor](https://apify.com/power_on/pdf-to-json-extractor) —
  the other direction: a PDF back into tables, key fields and Markdown.

Need the page's text or structured data instead of a picture of it? Apify's
[Website Content Crawler](https://apify.com/apify/website-content-crawler) extracts
clean text and markdown from a site — a common pairing with this Actor when you need
both a visual and the content.

Worked examples from real runs, with the input used and the output it gave:
[screenshot a web page](https://power-on-labs.pages.dev/screenshot-a-web-page) and
[HTML to PDF](https://power-on-labs.pages.dev/html-to-pdf) on the Power On Labs site.

### Support

Found a page it handles badly? Open an issue on this Actor with the URL and the
settings you used. Broken pages are the fastest way to make it better, and they get
fixed.

# Actor input Schema

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

Pages to capture. One screenshot (or PDF) is produced per URL. Leave empty if you are passing HTML instead.

## `html` (type: `string`):

HTML to render instead of a URL — an invoice, a report, a receipt, an email template. Produces one capture, in whichever output format is selected. For a printable A4 document, choose PDF and turn "Full page" off. Images, CSS and fonts must be referenced by absolute URL (https://...) or embedded as data URIs, because inline HTML has no page address to resolve relative paths against. When this field is filled in, the URLs above are ignored.

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

Viewport, user agent and pixel density. Choose 'Custom' to set width and height yourself.

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

Viewport width in pixels. Used only when the device preset is "Custom".

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

Viewport height in pixels. Used only when the device preset is "Custom", and ignored for full-page captures.

## `scaleFactor` (type: `integer`):

Pixel density of the output: 1 for standard, 2 for retina, 3 for extra high resolution.

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

Capture the entire scrollable page instead of just the visible viewport.

## `selector` (type: `string`):

Optional CSS selector. When set, only that element is captured.

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

Output type. PNG for lossless quality, JPEG or WebP for smaller files, PDF for a printable document.

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

Compression quality for JPEG and WebP, from 1 to 100. Ignored for PNG and PDF.

## `transparentBackground` (type: `boolean`):

Keeps the page background transparent instead of white. PNG only, and only works if the page itself has no background.

## `dismissCookieBanners` (type: `string`):

Cookie and consent overlays ruin screenshots. 'Hide' removes them from the page without clicking anything, which is the most reliable and does not accept any tracking.

## `blockAds` (type: `boolean`):

Faster, more consistent screenshots and no empty ad slots.

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

CSS selectors to hide before capturing, e.g. sticky headers or chat widgets.

## `customCss` (type: `string`):

Injected before the capture.

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

Renders the page as if the visitor preferred a dark colour scheme.

## `locale` (type: `string`):

Browser language, e.g. en-US, it-IT, de-DE.

## `timezone` (type: `string`):

IANA name, e.g. Europe/Rome.

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

How long to wait for the page before capturing. "Network idle" is the most complete and the slowest. Once the content has arrived, the wait for the full load is capped at 15 s (network idle at 8 s): a page still loading after that is captured as it is, and the row says so in loadState.

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

Wait until this CSS selector appears before capturing.

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

Wait a little longer after loading, for animations or late content.

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

Scrolls through the page first so lazy-loaded images appear in a full-page capture.

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

How long to give a single URL before giving up on it.

## `concurrency` (type: `integer`):

How many URLs to capture at the same time. Capped at about one page per GB of run memory (2 GB by default).

## `retries` (type: `integer`):

How many times to retry a URL that failed before recording it as an error.

## `headers` (type: `object`):

Extra HTTP headers sent with every request, as a JSON object. Stored encrypted and kept out of the run log, so an Authorization header is safe to put here.

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

Playwright cookie objects, for pages behind a login. Stored encrypted and kept out of the run log.

## `basicAuthUsername` (type: `string`):

Username for HTTP Basic authentication, for staging sites behind a password. Stored encrypted and kept out of the run log.

## `basicAuthPassword` (type: `string`):

Password for HTTP Basic authentication.

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

Use a proxy for geo-specific or blocked pages.

## Actor input object example

```json
{
  "urls": [
    "https://apify.com"
  ],
  "html": "<h1>Hello from HTML</h1>\n<p>Rendered by the Actor, no URL needed.</p>",
  "device": "desktop",
  "width": 1920,
  "height": 1080,
  "scaleFactor": 1,
  "fullPage": true,
  "format": "png",
  "quality": 85,
  "transparentBackground": false,
  "dismissCookieBanners": "hide",
  "blockAds": true,
  "darkMode": false,
  "locale": "en-US",
  "waitUntil": "load",
  "delayMs": 0,
  "scrollToBottom": true,
  "timeoutSecs": 60,
  "concurrency": 4,
  "retries": 2,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

No description

## `files` (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"
    ],
    "html": `<h1>Hello from HTML</h1>
<p>Rendered by the Actor, no URL needed.</p>`
};

// Run the Actor and wait for it to finish
const run = await client.actor("power_on/screenshot-url-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://apify.com"],
    "html": """<h1>Hello from HTML</h1>
<p>Rendered by the Actor, no URL needed.</p>""",
}

# Run the Actor and wait for it to finish
run = client.actor("power_on/screenshot-url-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://apify.com"
  ],
  "html": "<h1>Hello from HTML</h1>\\n<p>Rendered by the Actor, no URL needed.</p>"
}' |
apify call power_on/screenshot-url-pdf --silent --output-dataset

```

## MCP server setup

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