# Website Screenshot API: Screenshot URL to PNG, JPEG or PDF (`hiver/bulk-website-screenshots`) Actor

Website screenshot API: screenshot a URL list to PNG, JPEG or PDF (URL to image), full page up to 4,000 CSS px or exact size, with final URL, HTTP status, title. HTML strings to PDF too: A4/Letter, landscape, margins. Bench 10-10: 17 of 18 checks passed. $0.80 per 1,000; dead links free.

- **URL**: https://apify.com/hiver/bulk-website-screenshots.md
- **Developed by:** [hiver](https://apify.com/hiver) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.68 / 1,000 screenshot takens

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 API: screenshot any URL to PNG, JPEG or PDF

**Paste a list of URLs and get a screenshot of every URL in one run (PNG, JPEG or PDF), or call it as a URL screenshot API from your code.** Or send HTML strings (invoices, reports) and get one PDF per item on A4, Letter or Legal paper, portrait or landscape, with your margins: the `pdfkit.from_string` job without the wkhtmltopdf binary. You get one PNG, JPEG or PDF per page, each with a public link, plus a table with the final URL after redirects, the HTTP status, the page title and the file size. Dead links and error pages show up in the table and cost nothing.

**$0.80 per 1,000 screenshots** on the free plan ($0.68 on Business). Apify platform usage is included in that price.

**Current full-page PNG/JPEG limit: 4,000 CSS pixels.** Longer pages are intentionally clipped; each row reports `pageHeight` and `truncatedAt: 4000`. Viewport resolution and PDF output are unchanged. The historical measurements below used the older 16,000 px cap, not this safeguard.

![Real output from the board test run on 2026-10-11 (build 0.1.15): 4 long pages in, 4 full-page JPEGs and one table with final URL, HTTP status, title, page height and image link](https://raw.githubusercontent.com/hiver-data/website-screenshot-monitor/main/media/screenshots/readme.gif)

[Watch the 2-minute walkthrough (MP4)](https://github.com/hiver-data/website-screenshot-monitor/blob/main/media/screenshots/walkthrough.mp4) · [Free example on GitHub: weekly screenshots of your pages, changes posted to Slack or Discord](https://github.com/hiver-data/website-screenshot-monitor?utm_source=apify\&utm_campaign=website-screenshot-monitor)

### Measured against the alternatives

Our board's Lab bench ran this Actor (build 0.1.10) and three other Store Actors that take a list of URLs on 2026-10-10 11:23 UTC: Apify's own screenshot Actor (apify/screenshot-url), fourwake/screenshot-api and fetch_cat/website-screenshot-generator. Each got the same 9 URLs in three runs: (1) a plain page, an http:// link that redirects to https, the BBC home page with its cookie banner and a small business site; (2) two normal sites and a domain that does not exist; (3) two long Wikipedia articles captured full page (the English "Cat" article, 34,232 px tall with dozens of lazy-loaded photos, and the Chinese "Emoji" article). Each answer was checked against the real pages: page title, final URL, image saved, and for the long pages the real page height and no images still loading.

| | This Actor | apify/screenshot-url | fourwake/screenshot-api | fetch_cat/website-screenshot-generator |
|---|---|---|---|---|
| Runs that succeeded | **3 of 3** | 1 of 2 measured | 2 of 3 | 2 of 3 |
| Fields filled (URL, final URL, HTTP status, title, image link, width, height) | **100%** | 43% (2 runs) | 43% | 67% |
| Checks passed against the real pages | **17 of 18** | 4 of 11 (2 runs) | 6 of 18 | 12 of 18 |
| Long pages full page: checks passed | **7 of 7** | not measured (see below) | 1 of 7 (run hit the 5-minute limit) | 6 of 7 |
| Price per 1,000 screenshots in these runs (dead links free) | **$0.68** | no per-screenshot price | $3.00 | $9.17 |
| Run time per run | 49 s average | 300 s (hit the limit) and 10 s | 109 s average | **17 s average** |

- **Read it fairly:** 9 URLs are a small sample. fetch_cat/website-screenshot-generator is much faster (17 s vs 49 s a run) and filled every field on the long pages too; its run on the list with the dead domain failed. Apify's own Actor did not start on the long-page run because the bench asked it for JPEG, a format it does not offer, so that run is left out of its column (not measured, not a failure of that Actor). The bench also caps every Actor at 1,024 MB, half the 2,048 MB Apify's Actor uses by default, so its time-out on run 1 may come from that cap. Later builds have not been benched again yet.
- **Tested example (Apify run, build 0.1.10, 2026-10-10):** the 34,232 px Cat article came back as a 1920x16,000 JPEG (the 16,000 px cap) with text and photos all the way down and `imagesPending: 0`; the Chinese Emoji article came back full height (4,442 px) with Chinese text and the inline emoji rendered, not empty boxes. We looked at both images.
- **Historical test and reliability caveat (build 0.1.14, 2026-10-10):** run `lyaywgV4p0bghNCac` saved all four full-page images, but recheck `hmNdzt27v0kh243rp` saved only two of four within 299 seconds. This build uses an explicit 4,000 CSS px safeguard instead of the historical 16,000 px limit. The old recheck did not pass; a new board health verdict is pending.

### Common screenshot problems, and the setting that fixes each

| Problem | Setting |
|---|---|
| A full-page screenshot of a long page is blank or has empty boxes below the first screen | `fullPage: true` captures the page screen by screen and waits up to 5 seconds for the images in view. Every row reports `pageHeight` and `imagesPending`, so a page that was still loading shows up in the data instead of in your report |
| You need every image at one exact size, for example 1200x630 for link previews | `device: custom` with `viewportWidth` and `viewportHeight`, `fullPage` off: each image is exactly that size (times `deviceScaleFactor`) |
| You only want the top of the page, not the whole scroll | `fullPage` off (the default) captures the first screen only |
| The screenshot shows a spinner or half-loaded page | `waitUntil: networkidle`, `waitForSelector` with a CSS selector that appears when the page is ready, or `delayMs` |
| A cookie banner or chat widget covers the page | `hideCookieBanners` (on by default), plus your own CSS selectors in `hideSelectors` |
| You paid for URLs that did not load | Nothing to set: failed URLs and 4xx/5xx error pages are listed with the reason and not charged |
| You need to know where each URL ended up | Every row has the final URL after redirects, the HTTP status and the page title |

### HTML to PDF (invoices, reports, wkhtmltopdf and pdfkit jobs)

Put HTML in `html` instead of (or next to) `urls`, set `format: pdf`, and you get one PDF per item, rendered in headless Chromium.

- `html`: one HTML string, a list of strings, or `{"name": "invoice-1001", "html": "..."}` objects (the name goes into the file name and the `name` column). Up to 2 MiB per item. HTML and URLs can go in the same run.
- Paper: `pageSize` A4 (default), Letter, Legal, A3, A5 or Tabloid; `landscape: true`; `margin` as one CSS length (`"0.5in"`, `"10mm"`) or an object with `top`, `right`, `bottom`, `left`; `printBackground` (on by default).
- JavaScript runs in a current Chromium, so `Promise`, `fetch` and arrow functions work.
- Empty, non-string or over-2-MiB items come back as rows with the reason and are not charged.
- **Tested example (board run `xCcB7tEdn6snKVWj8`, build 0.1.19, 2026-10-14):** 3 HTML strings and 1 URL (the Wikipedia "Cat" article) in, 4 PDFs out, every page 792 x 612 pt (US Letter landscape), 0 error rows, 4 `screenshot-taken` charges, 37 s for the run. One of the HTML strings writes its text with `Promise` and `fetch`, and that text is in its PDF.
- Price: the same $0.80 per 1,000 as screenshots (free plan, lower on paid plans), one charge per saved PDF however many pages it has.

| wkhtmltopdf / pdfkit | This Actor |
|---|---|
| `pdfkit.from_string(html, ...)` | `html` |
| `pdfkit.from_url(url, ...)` | `urls` |
| `--page-size Letter` | `pageSize: "Letter"` |
| `-O Landscape` | `landscape: true` |
| `--margin-top 0.5in` (and right, bottom, left) | `margin: {"top": "0.5in"}`, or `margin: "0.5in"` for all four sides |
| `--no-background` | `printBackground: false` |

Margins default to 0 here, not to wkhtmltopdf's defaults, so set `margin` if your layout relied on them. Chromium's layout and page breaks will not match wkhtmltopdf byte for byte.

Ready-made input: [HTML strings to PDF, Letter landscape](https://apify.com/hiver/bulk-website-screenshots/examples/html-to-pdf-letter-landscape).

Calling pdfkit from Python? The free [pdfkit-alternative](https://github.com/hiver-data/pdfkit-alternative?utm_source=apify\&utm_campaign=pdfkit-alternative) module (MIT) keeps your `pdfkit.from_string` and `pdfkit.from_url` calls: change `import pdfkit` to `import pdfkit_compat as pdfkit` and each call runs this Actor once with your own Apify token and a charge cap. It maps `page-size`, `orientation`, the `margin-*` options and `print-media-type`; any other option stops with an error before a run starts.

### What you can do with it

- Thumbnails for directories, link previews, reports and client decks
- A visual archive of what a list of pages looked like on a given day
- Checks of how pages render at desktop, laptop, tablet and mobile sizes
- Before and after a site launch or redesign: run it twice and compare
- A weekly screenshot of your own pages with a changed / unchanged / failed report in Slack or Discord: free GitHub Actions template (see the FAQ)
- Broken links in a list: every row says `ok`, `http_error` or `failed`, with the reason
- HTML or URL to PDF where wkhtmltopdf / pdfkit no longer installs: one PDF per HTML string or URL, A4, Letter or Legal, portrait or landscape, rendered in Chromium (see HTML to PDF above)

### Input

Supply `urls`, `html`, or both. Inline HTML supports PNG, JPEG and PDF; no wkhtmltopdf binary is needed. For HTML, `html` accepts a single string, a list of strings, or `{ "name": "invoice", "html": "<h1>Invoice</h1>" }` objects. `htmlNames` optionally names list entries. Each HTML item gets its own file even when two entries contain the same markup. Names are sanitized and the item position is included to prevent collisions.

```json
{
  "html": ["<!doctype html><html><body><h1>Invoice #1001</h1><p>Total: $49.00</p></body></html>"],
  "htmlNames": ["invoice-1001"],
  "urls": ["https://example.com"],
  "format": "pdf",
  "pageSize": "Letter",
  "landscape": true,
  "margin": "0.5in",
  "printBackground": true
}
```

HTML renders on `about:blank`, with local file/internal browser access blocked and non-HTTP(S) embedded-frame navigations disallowed. Use absolute HTTPS URLs for external CSS, images and scripts; relative URLs have no website base unless you supply an HTTPS `<base>` tag. JavaScript runs in Chromium; use `waitForSelector` or `delayMs` when your application needs extra time. Empty HTML, non-string entries, entries over 2 MiB (UTF-8 bytes), and render failures produce free error rows. Do not put secrets in HTML input.

PDF defaults remain A4, portrait, zero margins, backgrounds on. Paper sizes: A4, Letter, Legal, A3, A5, Tabloid. `margin` accepts a CSS length (`px`, `pt`, `in`, `cm`, `mm`, or zero), or an object with `top`, `right`, `bottom`, `left`; omitted sides are zero. PDF pagination follows Chromium's print layout. The page options do not change PNG/JPEG output.

| Field | What it does |
|---|---|
| `urls` | One URL per line. `example.com` is fine, `https://` is added. Duplicates are skipped. |
| `html`, `htmlNames` | Inline HTML string/list or named objects, optionally mixed with URLs |
| `format` | `png`, `jpeg` or `pdf` |
| `pageSize`, `landscape`, `margin`, `printBackground` | PDF paper, orientation, margins and backgrounds |
| `fullPage` | Capture the scrollable page up to 4,000 CSS px; longer pages report `truncatedAt: 4000` |
| `device` | `desktop` 1920x1080, `laptop` 1366x768, `tablet` 820x1180, `mobile` 390x844 @2x, or `custom` width/height |
| `deviceScaleFactor` | 1 to 3, for sharper images |
| `waitUntil`, `waitForSelector`, `delayMs` | Wait for load, network idle, a CSS selector, and/or a pause before capture |
| `hideCookieBanners` | Clicks common "accept" buttons and hides known consent pop-ups |
| `hideSelectors` | Your own CSS selectors to hide (chat widgets, sticky bars) |
| `darkMode`, `blockAds`, `userAgent`, `timeoutSecs`, `maxConcurrency` | Fine control |
| `proxyConfiguration` | Off by default. Turn on for sites that block data-centre IPs. |

```json
{ "urls": ["https://example.com", "https://news.ycombinator.com"], "format": "png", "device": "desktop", "fullPage": false }
```

### Output

One dataset row per URL or HTML item. HTML rows have `source: "html"`, `url: null`, zero-based `htmlIndex`, and `name`; URL rows retain their existing fields. PDF rows add `pageSize`, `landscape`, `pages` (page count); `width`/`height` are the first page's actual MediaBox dimensions in points (72 pt/in), not viewport pixels.

```json
{
  "url": "https://example.com",
  "finalUrl": "https://example.com/",
  "status": "ok",
  "httpStatus": 200,
  "title": "Example Domain",
  "imageUrl": "https://api.apify.com/v2/key-value-stores/<id>/records/example.com-327c3fda.png",
  "format": "png", "bytes": 79492, "width": 1920, "height": 1080, "seconds": 2.6, "error": null
}
```

`status` is `ok`, `http_error` (the server answered 4xx/5xx: the error page is still saved, but not charged) or `failed` (DNS error, timeout, bad URL; `error` says why). Images are in the run's key-value store, so the links work without logging in.

`width` and `height` are the actual PNG/JPEG pixel dimensions, including device scaling. `pageHeight` is the scrollable page height in CSS pixels before capture; full-page images stop at 4,000 CSS pixels even if the page is longer. `imagesPending` counts visible `<img>` elements inside the capture that are still loading after a wait of up to five seconds. Zero means none are pending, not that every image loaded successfully (broken images can be complete). `truncatedAt` is the CSS pixel cutoff when a full-page image is capped, otherwise null. Full-page PNG/JPEG captures use viewport-sized tiles stitched into one image, without resizing the viewport. Sticky elements keep their document position; fixed overlays are hidden after the first tile to avoid repetition. Raster capture has a total budget of 10 seconds plus 5 seconds per tile (30 seconds for 4 desktop tiles), capped at 90 seconds, and a 10-second budget per tile; both are bounded by the run deadline with five seconds reserved for saving/reporting. Pages are asked for reduced motion; Finite CSS reveal animations are fast-forwarded; infinite CSS animations, JavaScript animation-frame loops and playing videos are paused during tiled raster capture, then restored afterward. Each full-page URL uses a fresh browser to reclaim decoded-image and compositor memory. Navigation waits up to ten additional seconds for main-frame redirects to settle; current-document readiness probes allow up to five seconds for a CPU-busy renderer, within that settlement limit. A document change or destroyed execution context during redirect settlement or preparation/capture retries the complete capture once, never stitches different documents together. When `waitUntil: load` times out after the current document reached DOMContentLoaded, capture continues and reports `loadState: domcontentloaded`; explicit `networkidle` and selector timeouts still fail. `loadState` records the reached navigation state and `navigationRetries` counts the one permitted redirect retry. A capture timeout returns a failed, uncharged row rather than a partial image. `captureTiles` and `captureSeconds` expose tile count and raster/stitching time; a failed raster still reports elapsed capture time and its timeout stage in `error`. Full-page raster URLs run serially to bound CPU/memory contention; viewport captures keep `maxConcurrency`. These fields also appear in the Results dataset view. PDF uses Chromium's print layout on the `pageSize` paper (A4 by default).

### Pricing

Pay per event: you pay per screenshot that loads (HTTP 2xx/3xx). Failed URLs and error pages are free. Apify platform usage is included in the price. Higher Apify plans get a discount automatically:

| Event, per 1,000 | Free | Starter (Bronze) | Scale (Silver) | Business (Gold) |
|---|---|---|---|---|
| Screenshot taken (`screenshot-taken`) | $0.80 | $0.76 | $0.72 | $0.68 |

**Worked example:** 5,000 screenshots on the Free plan cost 5,000 × $0.0008 = **$4.00**; on Gold, 5,000 × $0.00068 = $3.40. Dead links and error pages in the list cost nothing.

### Known limits

- Pages behind a login are not supported.
- Some sites block data-centre IPs or show a bot check; try the proxy option.
- Cookie-banner removal covers the common consent tools, not every site's custom one; add its selector in `hideSelectors`.
- Full-page PNG/JPEG capture stops at 4,000 CSS px tall. Longer pages and endless-scroll feeds are cut there with `truncatedAt: 4000`. PDF is unaffected.
- PDF output ignores `fullPage` and device size; paper size, orientation and margins follow `pageSize`, `landscape`, and `margin` (defaults: A4, portrait, zero). Large documents can have multiple pages but are charged once per saved PDF.
- Capture only pages you are allowed to capture.

### Input example

```json
{
  "urls": [
    "https://example.com",
    "https://news.ycombinator.com",
    "https://apify.com"
  ],
  "format": "png",
  "fullPage": false,
  "device": "desktop",
  "hideCookieBanners": true
}
```

### FAQ

**How many URLs per run?**\
As many as you list; runs are bounded by the Actor timeout you set. Start with a few dozen and scale up.

**Which pages work?**\
Public pages that load in a browser without login. Pages behind logins, captchas or bot blocks return an error row and are not charged.

**Are cookie banners always removed?**\
The option hides the common consent banners; unusual ones can remain visible.

**Can I get a weekly screenshot of my pages and an alert when one changes?**\
Yes, with the free [website-screenshot-monitor](https://github.com/hiver-data/website-screenshot-monitor?utm_source=apify\&utm_campaign=website-screenshot-monitor) repo (MIT): a GitHub Actions template runs this Actor weekly with your own Apify token, compares each image with the last good capture, and posts a changed / unchanged / failed report to Slack or Discord. Failed URLs stay in the report and never replace a good baseline.

**Coming from wkhtmltopdf, pdfkit or imgkit?**\
wkhtmltopdf's GitHub repo is archived (last push November 2022) and Debian removed its package from testing on 2025-02-05. Debian 13, the base of today's `python:3.13-slim` image, has no wkhtmltopdf package, so `apt-get install wkhtmltopdf` finds nothing and `pdfkit.from_url()` stops at "No wkhtmltopdf executable found" (imgkit: "No wkhtmltoimage executable found"; checked on Debian 13 on 2026-10-12, pdfkit 1.0.0, imgkit 1.2.3). Its engine is a 2012 QtWebKit. The last upstream `.deb` (0.12.6.1, built for Debian 12) still runs on Debian 13 if you install it by hand, but in the same check it had no `Promise`, `fetch` or arrow functions and crashed on 2 of 6 public pages (react.dev and a Wikipedia article: floating point exception and segmentation fault), where headless Chromium printed all 6. This Actor renders in headless Chromium (Playwright) instead: send HTML strings in `html` (the `pdfkit.from_string` case) or URLs in `urls`, with `pageSize`, `landscape` and `margin` in place of `--page-size`, `-O` and the `--margin-*` options (table in HTML to PDF above); `png` or `jpeg` gives images (the imgkit case). Ready-made inputs: [HTML strings to PDF, Letter landscape](https://apify.com/hiver/bulk-website-screenshots/examples/html-to-pdf-letter-landscape) and [wkhtmltopdf alternative, URL list to PDF](https://apify.com/hiver/bulk-website-screenshots/examples/wkhtmltopdf-alternative-url-to-pdf).

**Is this official?**\
No. Unofficial and not affiliated with, endorsed by or sponsored by any company or site named in the results or this README.

**Something looks wrong or you need a field added?**\
Open an issue from the Actor's Issues tab with the input and run link.

# Actor input Schema

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

Pages to capture, one per line. `example.com` works as well as `https://example.com`. One result row per URL.

## `html` (type: `string,array,object`):

A string, a list of strings, or {name, html} objects. Either html or urls is needed; both may be mixed. Each item renders on about:blank; use absolute https URLs for remote assets. Empty, non-string and over-2-MiB UTF-8 items return free error rows.

## `htmlNames` (type: `array`):

Optional names matched by zero-based HTML position; object name wins. File names are sanitized and include the position to avoid overwrites.

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

PNG is lossless, JPEG is much smaller, PDF is a printable page.

## `pageSize` (type: `string`):

PDF paper size. Default A4 preserves previous URL output. PNG/JPEG unaffected.

## `landscape` (type: `boolean`):

Print PDFs in landscape orientation instead of portrait.

## `margin` (type: `string,object,number`):

One nonnegative CSS length (e.g. "0.5in"), zero, or {top,right,bottom,left}. Units: px, pt, in, cm, mm. Missing sides are zero. Only used for PDF.

## `printBackground` (type: `boolean`):

Include background colors and images in PDFs.

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

Capture the scrollable page up to 4,000 CSS pixels instead of just the first screen. Longer PNG/JPEG pages are clipped and report pageHeight plus truncatedAt: 4000. PDF is unaffected.

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

desktop 1920x1080, laptop 1366x768, tablet 820x1180, mobile 390x844 (touch, mobile user agent). `custom` uses the width and height below.

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

Viewport width in pixels when device is custom.

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

Viewport height in pixels when device is custom.

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

2 gives sharper retina images (4x the pixels, larger files).

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

JPEG quality, 30 to 100.

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

When the page counts as loaded. `networkidle` waits until requests stop; slower, good for heavy single-page apps.

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

Optional CSS selector that must appear before capture, for example `#app .loaded`.

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

Pause after load, before capture, so animations and lazy images settle.

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

Clicks common 'accept' buttons and hides known consent pop-ups.

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

CSS selectors to hide before capture (chat widgets, sticky bars, ads).

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

Ask pages for their dark colour scheme.

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

Skips well-known ad and tracking requests: faster, cleaner images.

## `userAgent` (type: `string`):

Override the browser user agent. Leave empty for the default.

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

Navigation and selector timeout. Load timeouts can fall back only after DOMContentLoaded (reported in loadState); explicit networkidle and selector timeouts still fail. Redirect settlement adds at most 10 seconds per attempt, with one retry on a document change. Full-page raster budget: 10 seconds + 5 seconds per tile, capped at 90 seconds, plus a 10-second per-tile cap; bounded by the run deadline.

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

How many viewport/PDF pages are captured at the same time (1-10). Full-page PNG/JPEG URLs run one at a time to bound CPU and memory usage.

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

Optional. Off by default; turn on for sites that block data-centre IPs.

## Actor input object example

```json
{
  "urls": [
    "https://example.com"
  ],
  "html": [
    "<!doctype html><html><head><title>Invoice</title></head><body><h1>Invoice #1001</h1><p>Total: $49.00</p></body></html>"
  ],
  "format": "png",
  "pageSize": "A4",
  "landscape": false,
  "margin": 0,
  "printBackground": true,
  "fullPage": false,
  "device": "desktop",
  "viewportWidth": 1280,
  "viewportHeight": 800,
  "deviceScaleFactor": 1,
  "jpegQuality": 80,
  "waitUntil": "load",
  "delayMs": 1000,
  "hideCookieBanners": true,
  "hideSelectors": [],
  "darkMode": false,
  "blockAds": true,
  "timeoutSecs": 30,
  "maxConcurrency": 4
}
```

# Actor output Schema

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

One stored PNG/JPEG/PDF per URL or HTML item. HTML rows: source, htmlIndex, name and null url. PDF rows: pageSize, landscape, pages and MediaBox width/height in points. Raster dimensions remain pixels; failures are free.

# 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",
        "https://news.ycombinator.com"
    ],
    "html": [
        "<!doctype html><html><head><title>Invoice</title></head><body><h1>Invoice #1001</h1><p>Total: $49.00</p></body></html>"
    ],
    "hideSelectors": []
};

// Run the Actor and wait for it to finish
const run = await client.actor("hiver/bulk-website-screenshots").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",
        "https://news.ycombinator.com",
    ],
    "html": ["<!doctype html><html><head><title>Invoice</title></head><body><h1>Invoice #1001</h1><p>Total: $49.00</p></body></html>"],
    "hideSelectors": [],
}

# Run the Actor and wait for it to finish
run = client.actor("hiver/bulk-website-screenshots").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",
    "https://news.ycombinator.com"
  ],
  "html": [
    "<!doctype html><html><head><title>Invoice</title></head><body><h1>Invoice #1001</h1><p>Total: $49.00</p></body></html>"
  ],
  "hideSelectors": []
}' |
apify call hiver/bulk-website-screenshots --silent --output-dataset

```

## MCP server setup

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

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/JDbU5UJNmWodw09wJ/builds/VsXdsQbeBh8pChOl3/openapi.json
