# PDF Toolkit – Merge, Split, Compress, Rotate & Unlock PDFs (`gazidev/pdf-toolkit`) Actor

Merge PDFs, split by page, ranges, every N pages or bookmarks, extract and reorder pages, rotate, compress (image recompression, size before/after), add or remove a password, strip metadata, get page count and info. URLs or uploads. $3 per 1,000 files + $0.20 per 1,000 pages.

- **URL**: https://apify.com/gazidev/pdf-toolkit.md
- **Developed by:** [Cemal Atakli](https://apify.com/gazidev) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 pdf outputs

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

## PDF Toolkit – Merge, Split, Compress, Rotate & Unlock PDFs

**Merge PDF files, split PDFs, extract or reorder pages, rotate, compress, add or remove a password, strip metadata and get the page count, all in one Apify Actor.** Paste PDF links or upload files from your computer. Each output PDF is saved with a public download link, and you get one clean dataset row per file. No Ghostscript, no browser, no sign-up on a third-party site. It runs on open-source qpdf (pikepdf) and Pillow.

**Price: $3 per 1,000 output files + $0.20 per 1,000 pages.** Merging two PDFs into a 7-page file costs $0.0044. Errors are free.

### What it does

| Operation | What you get |
|---|---|
| **Merge** (`merge`) | Many PDFs into one, in the order you list them, with an optional bookmark per source file. Form fields and internal links are kept |
| **Split** (`split`) | One PDF per page, per custom range (`1-3, 4-6, 7-`), every N pages, or **per top-level bookmark/chapter**. Optional ZIP of all parts |
| **Extract pages** (`extract`) | A new PDF with the pages you choose, in your order (`1-3, 5`, `-1` = last page, `odd`, `even`) |
| **Rotate** (`rotate`) | Rotate all pages or selected ones by 90°, 180° or 270° |
| **Reorder** (`reorder`) | Any new order, `reverse`, or duplicates (`3, 1, 2, 4-`) |
| **Compress** (`compress`) | Re-encodes large images as JPEG at the quality you pick, downscales oversized images, and compresses the structure (object streams, Flate). Text and vector graphics stay sharp. Reports size before and after. If the result is not smaller, you get the original back |
| **Add password** (`encrypt`) | AES-256 encryption with an open password and/or owner password, and print/copy/edit permissions |
| **Remove password** (`decrypt`) | Removes the open password (you supply it) or permission restrictions from **your own** PDFs |
| **Remove metadata** (`remove-metadata`) | Removes author, title, creator, producer and dates, XMP metadata and app private data |
| **Info** (`info`) | Page count, page size (A4/Letter…), PDF version, encryption and permissions, image count, bookmarks, forms, metadata. No output file |

### Use cases

- **Automation and no-code**: merge invoices or reports in Make, Zapier or n8n, then email the merged link.
- **OCR and AI pipelines**: split long PDFs into chapters or N-page chunks before OCR or LLM processing. Get page counts first with `info`.
- **Back office**: compress scanned PDFs for email limits (often 90%+ smaller), protect contracts with a password, strip author metadata before publishing.
- **Bulk cleanup**: rotate sideways scans, reorder pages, extract the signed page.

### Input example

```json
{
  "operation": "split",
  "pdfUrls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"],
  "splitMode": "ranges",
  "splitRanges": "1-3, 4-6",
  "createZip": true
}
```

More examples:

- Merge: `{"operation": "merge", "pdfUrls": ["https://…/a.pdf", "https://…/b.pdf"], "outputFileName": "report"}`
- Compress: `{"operation": "compress", "pdfUrls": ["https://…/scan.pdf"], "compressionLevel": "high"}`
- Password: `{"operation": "encrypt", "pdfUrls": ["https://…/contract.pdf"], "userPassword": "…", "allowCopying": false}`

Files can be **direct PDF URLs** (Google Drive, Dropbox and GitHub share links are converted automatically), **uploaded files** ("Or upload PDFs"), or both. For merge, the order of the list is the page order (URLs first, then uploads).

### Output example

One row per output file. The PDF itself is in the run's key-value store, and `pdfUrl` is its download link.

```json
{
  "type": "pdf",
  "status": "ok",
  "operation": "compress",
  "fileName": "scan-compressed.pdf",
  "pdfUrl": "https://api.apify.com/v2/key-value-stores/…/records/scan-compressed.pdf",
  "pages": 3,
  "bytes": 210086,
  "sourceUrl": "https://example.com/scan.pdf",
  "sourceFileName": "scan.pdf",
  "sourcePages": 3,
  "compressionLevel": "medium",
  "imagesRecompressed": 3,
  "sizeBefore": 20457108,
  "sizeAfter": 210086,
  "reductionPct": 99.0
}
```

Merge rows also list `sources` (file, first page, pages). Split rows have `pageRange`, `part`, `partsTotal` and `zipUrl`. `info` rows have `pages`, `pdfVersion`, `encrypted`, `permissions`, `pageSize`, `images`, `bookmarks`, `hasForm` and `metadata`. Errors are rows with `type: "error"`, an `errorCode` and a message, and they are **not charged**. A `SUMMARY` record in the key-value store has the run totals.

### Pricing

Pay per event: **$0.003 per output file** + **$0.0002 per page** in the output. An `info` report is $0.003 with no page charge. Errors, ZIPs and skipped files are free. Set a maximum charge per run and the Actor stops cleanly when it is reached.

| Task | This Actor | Alternatives on Apify Store |
|---|---|---|
| Merge 2 PDFs (7 pages) | $0.0044 | autofacts/universal-web-printer: $0.01 per merge |
| Split a 10-page PDF into 10 files | $0.032 | – |
| Compress a 20-page PDF | $0.007 | – |
| Page count of 1,000 PDFs | $3 | – |

Typical desktop web tools limit free use and need an upload to their servers. Here everything runs in your own Apify account, and output files stay in your run storage, following your data-retention settings.

### FAQ

**Is compression lossy?** Only for raster images. They are re-encoded as JPEG only when that makes them smaller. Choose `lossless` to optimise the structure only. Text, fonts and vector graphics are never rasterised.

**Why did compress return almost the same size?** Text-only or already optimised PDFs have little to remove. The row says so, and you get the original back if the result was not smaller.

**Can it unlock any PDF?** No. To remove an **open password** you must know it and enter it in "Password of the input PDF(s)". PDFs that open without a password but block printing or copying can have those restrictions removed. Use this only for files you own or are allowed to modify.

**Are my files private?** Input files are downloaded into the run and output PDFs are stored in your run's key-value store. Passwords are secret inputs (encrypted by Apify) and are never written to the dataset or logs.

**What is the maximum file size?** 100 MB by default (`maxFileSizeMb`, up to 500). The limit is also capped at a third of the run memory (170 MB at 512 MB), so give the run more memory for very large files. Too-large files get a free error row.

**Does merge keep fillable forms and links?** Yes. Form fields are carried over (renamed if two files use the same field names), and so are named destinations.

**Split by bookmarks?** `splitMode: "bookmarks"` cuts at each top-level bookmark (chapter). Pages before the first bookmark become `front-matter`. PDFs without bookmarks get a free `no-bookmarks` error.

**Which URLs are refused?** Local or private network addresses (SSRF protection), web pages that are not PDFs, and non-PDF files. Each gets a free error row.

### Use with AI agents / Apify MCP

Add this Actor to Claude, Cursor or any MCP client through the [Apify MCP server](https://mcp.apify.com) (`gazidev/pdf-toolkit`). An agent can then call, for example, "merge these three PDF links", "split this PDF into 20-page chunks" or "how many pages does this PDF have", and get public download links back. With the Apify API, start a run with `POST /v2/acts/gazidev~pdf-toolkit/run-sync-get-dataset-items` and the input JSON above, and read `pdfUrl` from each row.

### Related Actors

- [Image to PDF & PDF to Image Converter](https://apify.com/gazidev/pdf-image-converter): JPG/PNG/WebP/HEIC to PDF, and PDF pages to images.
- [Media Metadata Remover](https://apify.com/gazidev/media-metadata-remover): remove EXIF/GPS from photos and videos.

# Actor input Schema

## `operation` (type: `string`):

Every operation except merge runs on each input PDF separately.

## `pdfUrls` (type: `array`):

Direct links to PDF files (one per line). For merge this is the page order. Google Drive, Dropbox and GitHub share links are converted to direct downloads. Local/private network addresses are refused.

## `uploadedPdfs` (type: `array`):

Upload PDFs from your computer. They are processed after the URLs above (merge order: URLs first, then uploads).

## `maxFileSizeMb` (type: `integer`):

Larger files are skipped with a free error. The effective limit is also capped at one third of the run memory (512 MB run = 170 MB files).

## `password` (type: `string`):

Needed to open password-protected inputs (and for "Remove password"). Only use this for PDFs you own or are allowed to unlock.

## `userPassword` (type: `string`):

Password needed to open the protected PDF. AES-256 encryption.

## `ownerPassword` (type: `string`):

Password that lifts the permission restrictions below. Defaults to the open password. Set only this (no open password) to make a PDF that opens freely but is restricted.

## `allowPrinting` (type: `boolean`):

For "Add password".

## `allowCopying` (type: `boolean`):

For "Add password".

## `allowEditing` (type: `boolean`):

For "Add password".

## `pageRange` (type: `string`):

For extract: the pages to keep, in this order, e.g. `1-3, 5, 10-` (10 to the end), `-1` (last page), `-3--1` (last three), `odd`, `even`. For rotate: pages to rotate (empty = all).

## `splitMode` (type: `string`):

How "split" cuts each PDF.

## `splitRanges` (type: `string`):

For split mode "ranges": one output per comma-separated part, e.g. `1-3, 4-6, 7-` gives 3 PDFs; `1-3,5` gives pages 1-3 and page 5.

## `splitEveryN` (type: `integer`):

For split mode "every-n".

## `createZip` (type: `boolean`):

One ZIP per split input PDF (free). Its link is in `zipUrl`.

## `rotation` (type: `string`):

For rotate. Added to each page's current rotation.

## `pageOrder` (type: `string`):

E.g. `3, 1, 2, 4-` or `reverse` or `5-1`. Pages can be repeated; pages left out are dropped (noted in the row).

## `compressionLevel` (type: `string`):

Images are re-encoded only when that makes them smaller. Text and vector graphics stay sharp. If the result is not smaller, the original is returned.

## `imageQuality` (type: `integer`):

Optional. Overrides the level's JPEG quality.

## `maxImageSize` (type: `integer`):

Optional. Images with a longer side are downscaled. 0 = never downscale.

## `removeMetadataOnCompress` (type: `boolean`):

Strip author/title/XMP metadata from the compressed PDF too.

## `outputFileName` (type: `string`):

For merge: name of the merged file (default `merged`). For other operations: prefix instead of the input file name.

## `addBookmarks` (type: `boolean`):

Adds one bookmark (outline entry) per source file, named after the file.

## `linearize` (type: `boolean`):

Optimise output PDFs for page-by-page loading in browsers.

## `maxFiles` (type: `integer`):

0 = no limit.

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

How many PDFs are downloaded and processed at the same time.

## Actor input object example

```json
{
  "operation": "merge",
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
  ],
  "maxFileSizeMb": 100,
  "allowPrinting": true,
  "allowCopying": true,
  "allowEditing": false,
  "splitMode": "every-page",
  "splitEveryN": 2,
  "createZip": false,
  "rotation": "90",
  "compressionLevel": "medium",
  "removeMetadataOnCompress": false,
  "addBookmarks": true,
  "linearize": false,
  "maxFiles": 0,
  "maxConcurrency": 4
}
```

# Actor output Schema

## `files` (type: `string`):

No description

## `info` (type: `string`):

No description

## `errors` (type: `string`):

No description

## `store` (type: `string`):

No description

## `summary` (type: `string`):

No description

## `results` (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 = {
    "operation": "merge",
    "pdfUrls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gazidev/pdf-toolkit").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 = {
    "operation": "merge",
    "pdfUrls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    ],
}

# Run the Actor and wait for it to finish
run = client.actor("gazidev/pdf-toolkit").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 '{
  "operation": "merge",
  "pdfUrls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
  ]
}' |
apify call gazidev/pdf-toolkit --silent --output-dataset

```

## MCP server setup

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

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/5Gcg88Mx2zCdwqliS/builds/Wg0cVTDddHSoXftyZ/openapi.json
