# Bulk Webpage to PDF Converter – Batch URL to PDF (`graphitactlabs/webpage-to-pdf-batch`) Actor

Convert one URL, a batch of URLs, or a CSV list to PDFs in a single run. Per-URL success/failure JSON manifest, deterministic filenames, retries, webhooks and optional ZIP bundle. Designed for agents and automation pipelines.

- **URL**: https://apify.com/graphitactlabs/webpage-to-pdf-batch.md
- **Developed by:** [Graham Harris](https://apify.com/graphitactlabs) (community)
- **Stats:** 2 total users, 1 monthly users, 6.3% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$9.90 / 1,000 pdf generateds

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

## Bulk Webpage to PDF Converter — Batch URL to PDF

Convert **one URL, hundreds of URLs, or a CSV list** into PDFs in a single run — and get back an exact machine-readable answer to *"what succeeded and what failed?"*.

Built for automation pipelines and AI agents, not for clicking through a UI one page at a time. Feed it an array of 300 URLs; every URL comes back with its own status, filename, HTTP code, retry count, timing and charge state.

- **Batch-first**: single URL, URL array, pasted CSV/list, or a remote CSV file — merged in one run.
- **Exact per-URL manifest**: success / error / skipped, HTTP status, error kind, attempts, duration, deterministic filename.
- **Charges only for successes**: each *successfully produced PDF* costs one `pdf-generated` event at **$0.0099**. 404s, timeouts and blocked pages are never charged.
- **Workflow features**: webhook callback on completion, optional single ZIP of all PDFs, retries with backoff on 5xx/network/timeout, per-URL timeout, concurrency control, optional image blocking.
- **Deterministic output**: fixed viewport (1280×800), `en-US` locale, UTC timezone, animations/transitions disabled, stable settle time → repeatable renders of the same page.
- **Agent-friendly schema**: flat JSON rows, predictable filenames (`0001-example-com-page.pdf`), summary row, plain-English errors.

### Input

Provide URLs via any combination of these fields (merged in order, duplicates converted once):

| Field | Type | Description |
|---|---|---|
| `url` | string | Single page. Scheme added automatically if missing. |
| `urls` | string array | Batch array — the core use case. |
| `csv_text` | string | Pasted CSV (`url` column or first column) or plain URL list, one per line. |
| `csv_url` | string | URL of a CSV file to fetch at run start (max 10 MB). |
| `pageSize` | select | A0–A5, Letter, Legal, Ledger, Tabloid (default A4). |
| `landscape` | bool | Landscape orientation. |
| `scale` | number | 0.1–2 (default 1). |
| `margin` | string | CSS-style: `"0"`, `"10mm"`, `"10mm 15mm"`. |
| `waitAfterLoadMs` | int | Settle time after load (default 300 ms). |
| `timeoutMs` | int | Per-URL navigation timeout, 5–120 s (default 30 s). |
| `retries` | int | Retries for timeout/network/5xx (default 1). 4xx is never retried. |
| `blockImages` | bool | Skip images/media — faster, cheaper, smaller PDFs. |
| `concurrency` | int | Parallel pages, 1–6 (default 3). |
| `bundleZip` | bool | Additionally store all successful PDFs as `batch-output.zip` (limit 100 MB). |
| `webhookUrl` | string | POST the full result when the batch finishes (3 retries with backoff). |

### Output

#### Dataset — one row per URL (`type: "result"`)

```json
{
  "type": "result",
  "index": 1,
  "url": "https://example.com/pricing",
  "status": "success",
  "http_status": 200,
  "filename": "0001-example-com-pricing.pdf",
  "size_bytes": 84211,
  "attempts": 1,
  "duration_ms": 1234,
  "charged": true,
  "error": null,
  "detail": null
}
```

`status` is `success` | `error` | `skipped`. `error` kinds: `http_4xx`, `http_5xx`, `timeout`, `network`, `ssl`, `aborted`, `pdf_error`, `invalid_input`, `charge_limit_reached`, `run_aborted`. Invalid input entries appear as rows with `index: null` and are never charged.

#### Dataset — final summary row (`type: "summary"`)

`urls_total`, `success`, `failed`, `skipped_limit`, `charged_count`, `charge_event`, `stop_reason`, `bundle_record`, `bundle_bytes`, `bundle_note`, `invalid_count`, `webhook`, `duration_ms`, `started_at`, `finished_at`, `status`.

#### Downloads (key-value store)

- One record per successful PDF, key = `filename` (e.g. `0001-example-com-pricing.pdf`), content type `application/pdf`.
- Optional `batch-output.zip` containing all successful PDFs (`bundleZip: true`).

#### Webhook payload

```json
{
  "event": "batch_completed",
  "summary": { "status": "complete", "success": 81, "failed": 19, "...": "..." },
  "results": [ { "type": "result", "...": "..." } ]
}
```

### Pricing

**$0.0099 per successfully generated PDF** (`pdf-generated` pay-per-event). Failures, skips and invalid entries cost nothing. Example: 300 URLs, 285 rendered → 300 × $0.0099 would be $2.97; you pay **285 × $0.0099 = $2.82**.

Apify platform usage (compute, data transfer) for the run is billed by Apify per your plan's usual usage terms.

### Run via API (agents, scripts, CI)

```bash
## 1. Start a run
curl -X POST "https://api.apify.com/v2/acts/your-user~webpage-to-pdf-batch/runs?token=APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://example.com", "https://example.org"], "bundleZip": true}'

## 2. Poll until status is SUCCEEDED, then fetch the manifest
curl "https://api.apify.com/v2/datasets/~/items?token=APIFY_TOKEN&format=json"
```

Or trigger with a CSV:

```json
{ "csv_url": "https://your-app.example/exports/urls.csv", "retries": 1, "timeoutMs": 20000 }
```

### Limits & good citizenship

- Max 10,000 URLs per run; CSV input max 10 MB; ZIP bundle max 100 MB.
- Pages behind logins, CAPTCHAs or aggressive bot walls will return `http_4xx` errors — those runs are not charged for those URLs.
- Renders a standard headless Chromium visit (robots-respecting sites). It does not bypass CAPTCHAs, logins or paywalls.
- Output size: PDF text/fonts render fully; use `blockImages: true` when you don't need images.

### Notes

- Filenames are deterministic: `{index:04d}-{host}-{path-slug}.pdf` — stable across runs for the same input order.
- Default memory 2048 MB; browser restarts every 8 URLs to keep long batches stable.
- Retries apply to `timeout`, `network`, `pdf_error` and HTTP 5xx only.

*Not affiliated with or endorsed by the websites you convert.*

# Actor input Schema

## `url` (type: `string`):

One webpage to convert. The https:// scheme is added automatically when missing.

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

Array of webpage URLs to convert in one batch.

## `csv_text` (type: `string`):

Paste a CSV with a 'url' column (or first column = URLs), or a plain list of URLs, one per line.

## `csv_url` (type: `string`):

URL of a CSV file to fetch at run start; the 'url' column (or first column) is used. Max 10 MB.

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

Paper format of every PDF.

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

Rotate pages to landscape orientation.

## `scale` (type: `number`):

Render scale from 0.1 to 2 (1 = 100%).

## `margin` (type: `string`):

CSS-style page margin: "0", "10mm", or "10mm 15mm" (top right bottom left).

## `waitAfterLoadMs` (type: `integer`):

Extra wait after page load so late content renders, in milliseconds.

## `timeoutMs` (type: `integer`):

Navigation timeout for each URL, in milliseconds.

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

Retry attempts for timeouts, network errors and HTTP 5xx. HTTP 4xx is never retried.

## `blockImages` (type: `boolean`):

Skip loading images and media: smaller PDFs, faster and cheaper runs.

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

How many pages render in parallel (1-6). Higher is faster but needs more memory.

## `bundleZip` (type: `boolean`):

Also store all successful PDFs as one batch-output.zip record in the key-value store (skipped if over 100 MB).

## `webhookUrl` (type: `string`):

POST {event:'batch_completed', summary, results\[]} when the batch finishes (up to 3 retries with backoff).

## Actor input object example

```json
{
  "pageSize": "A4",
  "landscape": false,
  "scale": 1,
  "margin": "0",
  "waitAfterLoadMs": 300,
  "timeoutMs": 30000,
  "retries": 1,
  "blockImages": false,
  "concurrency": 3,
  "bundleZip": false
}
```

# Actor output Schema

## `manifest` (type: `string`):

JSON dataset items: one row per input URL with status (ok|failed), deterministic filename, size, duration, attempts and charged flag; plus invalid-input rows and a final summary row (event=summary) with totals and bundle record.

## `pdfFiles` (type: `string`):

Generated PDFs stored under deterministic names (0001-host-slug.pdf) and, when enabled, the batch-output.zip archive containing all successful PDFs.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("graphitactlabs/webpage-to-pdf-batch").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("graphitactlabs/webpage-to-pdf-batch").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 '{}' |
apify call graphitactlabs/webpage-to-pdf-batch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,graphitactlabs/webpage-to-pdf-batch"
        }
    }
}
```

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/AgFgXVIAbn98dRDGd/builds/o0qbh8vEMcdtDtOkU/openapi.json
