Website Screenshot API: URL to PNG and PDF avatar

Website Screenshot API: URL to PNG and PDF

Pricing

Pay per event

Go to Apify Store
Website Screenshot API: URL to PNG and PDF

Website Screenshot API: URL to PNG and PDF

Capture full-page screenshots (PNG/JPEG) and PDFs of any public web page, built for AI agents and automations. Set viewport and device scale, capture one element, hide cookie banners, block ads and set PDF margins. Files land in the key-value store with direct URLs.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Michael Lip

Michael Lip

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

12 hours ago

Last modified

Security

Limited permissions

Share

Website Screenshot & PDF API for AI agents

Send a list of URLs and get back a PNG, JPEG or PDF of each page, with a direct file link, for $0.001 per saved file.

You don't write Playwright code, run a browser or set up storage. Each URL gives one dataset row with the HTTP status, image size, byte size and a fileUrl you can open or pass to another tool. Cookie banners are hidden by CSS selector and common ad and tracker hosts are blocked. Failed URLs are free.

What you get

One row per input URL. Duplicate URLs are dropped, and URLs left out at the charge cap get no row (they are counted in SUMMARY). The file itself sits in the run's key-value store.

FieldTypeMeaning
urlstringThe URL as you sent it (trimmed)
statusstringsuccess when a file was saved, failed when the URL was refused or no file could be made
httpStatusinteger or nullHTTP status of the main document after redirects, such as 200, 404 or 403. null if no response came back
filestring or nullKey-value store key, for example screenshot-0001-apify-com.png
fileUrlstring or nullSigned link to the file. A plain GET returns the bytes, no token needed
widthnumber or nullImage width in pixels (PNG, JPEG) or paper width in PDF points
heightnumber or nullImage height in pixels (PNG, JPEG) or paper height in PDF points
bytesinteger or nullFile size in bytes
clippedbooleantrue when the page was taller than 16384 or wider than 7680 device pixels and the image was cut
errorstring or nullWhy the row failed (SSRF guard, DNS, timeout, browser error), null on success

The run also writes a SUMMARY record to the key-value store with counts of succeeded, failed and skipped URLs, total bytes and the events charged.

Example output

These two rows come from a real run of the prefilled input on 2026-11-05 (run lPNsxG6uGnLcSJa55, finished in about 7 seconds).

[
{
"url": "https://news.ycombinator.com",
"status": "success",
"httpStatus": 200,
"file": "screenshot-0002-news-ycombinator-com.png",
"fileUrl": "https://api.apify.com/v2/key-value-stores/REDGccWd0hMLbVgSy/records/screenshot-0002-news-ycombinator-com.png?signature=1jrmKQ809iHohQCvx5rFN",
"width": 1280,
"height": 1185,
"bytes": 214618,
"clipped": false,
"error": null
},
{
"url": "https://apify.com",
"status": "success",
"httpStatus": 200,
"file": "screenshot-0001-apify-com.png",
"fileUrl": "https://api.apify.com/v2/key-value-stores/REDGccWd0hMLbVgSy/records/screenshot-0001-apify-com.png?signature=hCz8LgEGzXNBpAiQWIYn",
"width": 1280,
"height": 10052,
"bytes": 820825,
"clipped": false,
"error": null
}
]

A refused URL looks like this one, from an earlier test run that tried the cloud metadata address.

{
"url": "http://169.254.169.254/latest/meta-data",
"status": "failed",
"httpStatus": null,
"file": null,
"fileUrl": null,
"width": null,
"height": null,
"bytes": null,
"clipped": false,
"error": "IP address 169.254.169.254 is private or reserved"
}

Pricing

You pay per saved file. Nothing else is listed in this Actor's pricing.

EventPriceCharged when
screenshot$0.001a PNG or JPEG file is saved to the key-value store
pdf$0.001a PDF file is saved to the key-value store

Some worked examples.

JobFilesCost
The prefilled sample run2 PNG2 x $0.001
One full run of 50 URLs50 PNG$0.05
1,000 product pages a month1,000 PNG$1.00
500 pages as both PNG and PDF (20 runs of 50 URLs: 10 PNG, 10 PDF)1,000 files$1.00

Refused URLs, timeouts and browser errors aren't charged. A saved error page is charged, though. A 404 or a bot-challenge page is captured like any page, so check httpStatus if you only want 2xx pages.

To cap spend, set the maximum cost per run in Console or pass maxTotalChargeUsd through the API. When the cap is reached, the Actor stops rendering, leaves the rest out of the dataset and reports them as skippedForChargeLimit in SUMMARY.

Quick start in Console

  1. Open the Actor and click Start. The input is prefilled with two sample pages.
  2. After about 10 seconds, open the Output tab and click a fileUrl to see the image.
  3. Replace the sample URLs with your own and pick PNG, JPEG or PDF.

Use from the API

run-sync-get-dataset-items starts a run, waits for it and returns the rows in one request.

curl -s "https://api.apify.com/v2/acts/zovo-one~website-screenshot-pdf-api/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-X POST -H 'Content-Type: application/json' \
-d '{"urls":["https://example.com"],"output":"pdf","pdfFormat":"A4"}'

A sync call waits at most 300 seconds for the run. After that the HTTP request ends, but the run keeps going. With the defaults (30 s per page, 2 pages in parallel), about 20 URLs fit in 300 seconds in the worst case. For bigger batches, start the run with POST /v2/acts/zovo-one~website-screenshot-pdf-api/runs, wait for it to finish, then read /v2/datasets/<defaultDatasetId>/items.

Use from Python

from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("zovo-one/website-screenshot-pdf-api").call(run_input={
"urls": ["https://example.com", "https://apify.com/pricing"],
"output": "png",
"viewportWidth": 390,
"deviceScaleFactor": 2,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
print(row["status"], row["httpStatus"], row["fileUrl"])

Use from Node.js

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('zovo-one/website-screenshot-pdf-api').call({
urls: ['https://example.com'],
output: 'jpeg',
quality: 80,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items.map((row) => row.fileUrl));

Use from AI agents (MCP)

Agents that speak the Model Context Protocol can call this Actor as a tool through the Apify MCP server. Each client adds servers in its own way. VS Code and Claude Desktop, for example, don't take the JSON below as written. For your client, open the MCP configurator, pick this Actor and copy the setup it gives you. A client that reads an mcpServers block with a url key, such as Cursor, can use this config.

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=zovo-one/website-screenshot-pdf-api"
}
}
}

The first connection opens a browser so you can sign in to Apify. If your client can't do that, add an Authorization: Bearer <APIFY_TOKEN> header instead. Without the tools parameter, an agent can still find the Actor with search-actors, run it with call-actor and read the rows with get-dataset-items.

A prompt that works well with it:

Take a full-page screenshot of https://example.com/pricing at phone width (390 px) and give me the file link and the HTTP status.

The agent gets back fileUrl, so it can open the image or hand the link to a vision model. You can also pass maxTotalChargeUsd in the call options to cap what one agent call may spend.

Input reference

Only urls is required. Everything else has a default.

FieldTypeDefaultNotes
urlsarray of strings["https://apify.com", "https://news.ycombinator.com"]1 to 50 public http(s) URLs. If you leave urls out of an API call, these two sample URLs are rendered and charged, so always send your own list
outputpng, jpeg or pdfpngOne format per run
qualityinteger 1–10090JPEG only
fullPagebooleantrueWhole page or viewport only. Ignored for PDF and when selector is set
viewportWidthinteger 320 to 38401280390 is a phone, 1920 a desktop
viewportHeightinteger 240 to 2160800Matters only when fullPage is off
deviceScaleFactorinteger 1 to 312 gives retina images at double the pixel size
waitUntilload, domcontentloaded, networkidle, commitnetworkidleUse domcontentloaded for pages that never go idle
delayinteger 0 to 30000 ms0Extra wait before capture, counted in the page timeout
selectorCSS selectornoneCapture only the first matching element (PNG and JPEG)
hideSelectorsarray of CSS selectors["#onetrust-banner-sdk", ".cookie-banner"]Hidden before capture, up to 20. Send [] to hide nothing
blockAdsbooleantrueBlocks common ad and tracker hosts, never the page you asked for
pdfFormatA4, Letter, Legal, A3, TabloidA4PDF only
pdfLandscapebooleanfalsePDF only
pdfPrintBackgroundbooleantruePDF only
pdfMarginTop, pdfMarginRight, pdfMarginBottom, pdfMarginLeftstring1cm0 or a length in mm, cm, in, px or pt
maxUrlsinteger 1 to 5050Refuses longer lists before the browser starts
pageTimeoutSecsinteger 5 to 12030Per URL, covering load, delay and capture
maxConcurrencyinteger 1 to 42Lowered automatically when memory is tight

Bad input fails fast with a message that names the field.

Limits

  • 50 URLs per run at most, in one output format. Split longer lists into several runs.
  • Each page gets 30 seconds by default (5 to 120). A page that runs out of time becomes a free failed row.
  • Width times device scale factor may be at most 7680. Full-page and element images are cut at 7680 device pixels wide and 16384 high, and the row says clipped: true.
  • selector and fullPage: false are ignored for PDF. Playwright always prints the whole page.
  • It doesn't log in, solve captchas or get past bot protection. You get an image of whatever the site served.

Only public pages are rendered. The Actor refuses localhost, *.local, *.internal, private and reserved IPv4 and IPv6 ranges and cloud metadata addresses. It checks every URL before the browser starts, after DNS lookup, and again for every request and redirect the page makes. Service workers are blocked.

The guard has known gaps. A DNS-rebinding host with a 0 s TTL that answers two lookups differently isn't caught. A redirect to a private address is detected only after the request was sent, and WebRTC traffic is cut by browser settings that weren't tested in a real browser. Don't use this Actor as your only network boundary.

Default memory is 2048 MB (1024 to 4096). Near the run's own timeout, the Actor stops starting new pages and writes the rest as free failed rows. Plan about URLs x pageTimeoutSecs / maxConcurrency seconds for the worst case.

FAQ

Why did a row say success when the page was an error?

The Actor captures whatever the server returns, including 404 pages and bot challenges, and the file is charged. Check httpStatus in the row.

Do I need a token to download the file?

No. fileUrl carries a signature, so a plain GET returns the file. Files live in the run's default key-value store and follow your Apify data retention.

How do I get a mobile screenshot?

Set viewportWidth to 390 and deviceScaleFactor to 2 or 3. The image is then 780 or 1170 pixels wide.

Can I capture only one part of the page?

Yes. Put a CSS selector such as #pricing in selector. The first matching element is captured, for PNG and JPEG.

Find the banner's id or class in your browser's inspector and add it to hideSelectors. The two defaults cover OneTrust and sites that use a .cookie-banner class.

Can I get a PNG and a PDF of the same page?

Run the Actor twice, once with output set to png and once with pdf. Each saved file is one $0.001 event.