DEV Community

Batulzii Ulziikhuu
Batulzii Ulziikhuu

Posted on Originally published at apify.com

How to Take Full-Page Website Screenshots and PDFs via API (No Puppeteer Setup)

Taking one screenshot with Puppeteer or Playwright is easy. Taking thousands reliably is not. You end up managing Chromium versions in Docker, memory limits, lazy-loaded images that never appear, cookie banners covering half the page, timeouts, retries and a queue.

This guide shows how to skip that and call a hosted screenshot API instead: send a list of URLs, get back PNG, JPEG, WebP or PDF files plus a JSON record per URL. I'll show plain HTTP, JavaScript and Python, and I'll be upfront about the limits.

When a screenshot API makes sense

  • Visual monitoring: a daily capture of your landing pages to spot broken layouts.
  • SEO and client reports: desktop and mobile above-the-fold captures side by side.
  • Archiving: save articles, documentation or your own pages as PDF.
  • Link previews and thumbnails for dashboards, directories or internal tools.
  • QA: check how a release looks on a phone, a tablet and a large screen.

If you only need a single screenshot once a month, a local Playwright script is fine. Once you're dealing with batches, schedules or several devices, a hosted service saves a lot of maintenance.

The tool: Website Screenshot & PDF API on Apify

Website Screenshot & PDF API is an Apify Actor, a hosted program you can call through the Apify API. What it does:

  • Full-page or viewport captures (full page up to a configurable height, default 10,000 px)
  • Device presets: desktop 1366×768, laptop 1920×1080, tablet (iPad, 2×), mobile (iPhone 390×844, 2×) or a custom size
  • Formats: PNG, JPEG, WebP and PDF (A4, Letter, Legal or A3, portrait or landscape, with backgrounds)
  • Cookie banners hidden with CSS for about 45 common consent managers. Nothing is clicked or accepted.
  • Lazy-image scrolling, wait for a selector, extra delay, or network-idle waiting
  • Element capture: only header, #pricing and so on
  • Dark mode emulation
  • Batch: up to 5,000 URLs per run, up to 10 pages in parallel

Respectful by default: it checks each site's robots.txt, including the targets of redirects, and skips disallowed URLs. It only captures public websites (localhost and private-network addresses are blocked), and it doesn't try to get around logins, paywalls, CAPTCHAs or bot protection.

Pricing

You pay per successful capture:

Event Free plan Higher Apify plans
Screenshot (PNG/JPEG/WebP) $0.002 down to $0.0008
PDF $0.003 down to $0.0015

There's also a tiny start fee per run ($0.00005 per GB of memory). Failed, timed-out, robots-blocked and invalid URLs are free. Each one gets an error record in the dataset instead of a charge. Pages that answer with an HTTP error (4xx/5xx) are free error records too, unless you explicitly turn on captureErrorPages. Apify's free plan includes monthly credits, which covers a lot of test screenshots.

Apify also has its own Screenshot URL Actor with URL lists, PDF output, scrolling and selectors to hide. Both work well, so pick whichever fits your workflow. This one focuses on device presets, WebP, cookie-banner hiding, element capture and per-URL error codes with pay-per-success pricing.

Step 1: Get an Apify API token

  1. Create a free account at apify.com.
  2. Open Settings → API & Integrations and copy your API token.
  3. Store it as an environment variable: export APIFY_TOKEN=...

Step 2: One request with curl (any language)

The synchronous endpoint runs the Actor and returns the dataset in a single HTTP call:

curl -X POST \
  "https://api.apify.com/v2/acts/batuweb~website-screenshot/run-sync-get-dataset-items" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "urls": [{ "url": "https://crawlee.dev" }],
    "format": "png",
    "fullPage": true,
    "device": "desktop"
  }'
Enter fullscreen mode Exit fullscreen mode

The response is a JSON array with one item per URL. imageUrl is a direct link to the file:

[
  {
    "success": true,
    "url": "https://crawlee.dev",
    "finalUrl": "https://crawlee.dev/",
    "status": 200,
    "title": "Crawlee · Build reliable crawlers. Fast.",
    "format": "png",
    "device": "desktop",
    "viewportWidth": 1366,
    "viewportHeight": 768,
    "fullPage": true,
    "width": 1366,
    "height": 2874,
    "truncated": false,
    "pageCount": null,
    "fileSize": 330185,
    "imageUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/0001-crawlee-dev-0d35627b.png?signature=...",
    "key": "0001-crawlee-dev-0d35627b.png",
    "errorCode": null,
    "error": null,
    "capturedAt": "2026-10-19T08:11:52.257Z"
  }
]
Enter fullscreen mode Exit fullscreen mode

This works from n8n, Make, Zapier, Google Apps Script or a cron job.

Step 3: Full-page and mobile screenshots with JavaScript

npm install apify-client
Enter fullscreen mode Exit fullscreen mode
import { ApifyClient } from 'apify-client';
import { writeFile } from 'node:fs/promises';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });

const run = await client.actor('batuweb/website-screenshot').call({
  urls: [
    { url: 'https://crawlee.dev' },
    { url: 'https://lightertorch.com' },
  ],
  format: 'webp',          // png | jpeg | webp | pdf
  quality: 80,             // JPEG/WebP only
  device: 'mobile',        // desktop | laptop | tablet | mobile | custom
  fullPage: false,         // only the visible viewport ("above the fold")
  hideCookieBanners: true,
  maxConcurrency: 3,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
for (const item of items) {
  if (!item.success) {
    console.log('skipped (free):', item.url, item.errorCode);
    continue;
  }
  const res = await fetch(item.imageUrl);
  await writeFile(item.key, Buffer.from(await res.arrayBuffer()));
  console.log('saved', item.key, item.width + 'x' + item.height);
}
Enter fullscreen mode Exit fullscreen mode

Want only one section of a page? Add selector: '#pricing' (image formats only). For JavaScript-heavy pages, use waitUntil: 'networkidle' or waitForSelector: '.chart' together with delayMs: 1000.

Step 4: Website to PDF with Python

pip install apify-client requests
Enter fullscreen mode Exit fullscreen mode
import os
import requests
from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])

run = client.actor("batuweb/website-screenshot").call(run_input={
    "urls": [
        {"url": "https://docs.apify.com/platform/actors"},
        {"url": "https://crawlee.dev/js/docs/quick-start"},
    ],
    "format": "pdf",
    "pdfPaper": "a4",            # a4 | letter | legal | a3
    "pdfLandscape": False,
    "pdfPrintBackground": True,
    "scrollToBottom": True,      # load lazy images before printing
    "maxFullPageHeight": 10000,  # long pages are cut here (truncated: true)
})

for item in client.dataset(run.default_dataset_id).iterate_items():
    if not item["success"]:
        print("free error record:", item["url"], item["errorCode"])
        continue
    with open(item["key"], "wb") as f:
        f.write(requests.get(item["imageUrl"], timeout=60).content)
    print("saved", item["key"], item.get("pageCount"), "pages")
Enter fullscreen mode Exit fullscreen mode

The files also stay in the run's key-value store, under the key shown in each record, for the store's normal retention period. So you can fetch them later with client.key_value_store(run.default_key_value_store_id).get_record(key).

(This uses the current apify-client 3.x for Python, where call() returns a Run object. With the older 1.x client, use run["defaultDatasetId"] instead.)

Step 5: Batch screenshots on a schedule

For visual monitoring, save the input as an Apify task and add a schedule (for example every morning at 07:00). Each run creates a dataset you can export as JSON, CSV or Excel, or push to a webhook, Google Drive or Slack through Apify integrations.

Two settings help control cost in big batches:

  • maxScreenshots: stop after N successful captures.
  • The run's max total charge: the Actor stops cleanly once your budget is reached.

You can try ready-made examples without writing any code:

Error codes you may see (all free)

errorCode Meaning
ROBOTS_DISALLOWED The URL or its redirect target is disallowed by robots.txt, or robots.txt couldn't be fetched (treated as "disallow all")
HTTP_403, HTTP_404, ... The page answered with an HTTP error
TIMEOUT, DNS_ERROR, TLS_ERROR, CONNECTION_ERROR The page couldn't be loaded within navigationTimeoutSecs (after retries)
BLOCKED_PRIVATE_ADDRESS The URL or a redirect points to localhost, a private network or a cloud metadata address
SELECTOR_NOT_FOUND Your selector or waitForSelector didn't match a visible element
INVALID_URL, TOO_MANY_REDIRECTS Exactly what it says

Honest limitations

  • robots.txt is a courtesy signal, not permission. It doesn't replace a site's terms of use, copyright or privacy law. Only capture pages you're allowed to: your own sites, clients' sites with their OK, or public pages whose terms allow it. Using this tool doesn't give you legal clearance for any particular use.
  • No login, paywall or CAPTCHA bypass. Sites that block automated browsers return an error (free). A block page served with HTTP 200 looks like a normal page and is captured as rendered.
  • Height limits: full-page captures and PDFs are cut at maxFullPageHeight (max 16,000 px) and marked truncated: true. WebP is also limited to 16,383 pixels per side.
  • Cookie banners from less common consent tools may still show. Add their selectors to selectorsToHide.
  • If a site's robots.txt is down or rate-limited, that site is skipped for the run. Retry later.

FAQ

Do I need to install Chromium or Puppeteer? No. The browser runs on Apify, so you only send HTTP requests.

How much do 1,000 screenshots cost? About $2 on the Apify Free plan, and down to $0.80 on higher plans, plus a negligible start fee. Failed URLs cost nothing.

Can it capture pages behind a login? No, only publicly reachable pages.

Can AI agents use it? Yes. Like any Apify Actor, it can be called through the Apify MCP server or the REST API.

Related tools

Disclosure: I built the Website Screenshot & PDF API Actor.

Top comments (0)