# PDF Text & Metadata Extractor (pay per PDF) (`pennydata/pdf-text-extractor-pay-per-pdf`) Actor

Give a list of PDF URLs, get per-page text, page count, document metadata and table-like line grouping. Failed URLs return an error item and are not charged.

- **URL**: https://apify.com/pennydata/pdf-text-extractor-pay-per-pdf.md
- **Developed by:** [Penny Data](https://apify.com/pennydata) (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 extracteds

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 Text & Metadata Extractor (pay per PDF)

**PDF to Markdown and RAG chunks for LLMs.** Set `outputFormat` to `markdown` for headings, lists and tables preserved, and `chunkSize` (for example 800 tokens, `chunkOverlap` 100) to get one dataset item per chunk with `pageStart`/`pageEnd`, ready for a vector database. Still one flat charge per PDF, however many chunks it yields. Defaults stay plain text, one item per PDF.

Give it a list of public PDF URLs. You are charged only for PDFs that yield extractable text; failed, non-PDF and scanned/image-only PDFs (no text layer) are free. For each one you get a dataset item with:

- `text` (whole document) and `pages[]` (`page`, `text`, `charCount`, `tableLikeLineCount`, optional `lines[]`)
- `pageCount` and `metadata` (title, author, subject, keywords, creator, producer, creation/modification date, PDF version)
- `lines[]`: text grouped by visual row, split into `cells` at wide horizontal gaps; rows with 3+ cells are flagged `tableLike` (a lightweight way to recover simple tables, not a full table-structure parser)

Broken URLs, non-PDF responses, oversized files and timeouts produce an item with `status: "error"` and an `error` message. The run continues with the next URL.

### Input

| Field | Description |
|---|---|
| `urls` | PDF URLs (required) |
| `maxFileSizeMB` | Skip files larger than this (default 25) |
| `maxPages` | Extract only the first N pages, 0 = all |
| `includeLines` | Include per-page line/cell grouping (default true) |
| `outputFormat` | `text` (default) or `markdown` (headings from font size, bullets, pipe tables) |
| `chunkSize` | 0 = one item per PDF (default). Above 0 = one item per RAG chunk of about this many tokens, with `chunkIndex`, `pageStart`, `pageEnd`, `pages` |
| `chunkOverlap` | Tokens repeated between consecutive chunks (default 0) |
| `maxPdfs` | Process at most this many URLs per run (default 100) |
| `timeoutSecs` | Download timeout per PDF (default 60) |

### Switching from another PDF text extractor

Saved tasks, API calls and integrations written for other Store PDF text extractors (for example gochujang/pdf-text-extractor and automation-lab/pdf-text-extractor) run here after you change only the actor id. These input fields are accepted:

| Field you already use | What happens here |
|---|---|
| `urls` (strings or `{url}` objects) | used as is |
| `url`, `pdfUrls`, `startUrls`, `sources` | merged into `urls` |
| `urlsText` (one URL per line) | split and merged into `urls` |
| `timeoutPerPdfSecs` | same as `timeoutSecs` |
| `maxPdfMb` | same as `maxFileSizeMB` |
| `endPage` | same as `maxPages` (first N pages) |
| `includePages: false` | drops `pages[]`, keeps `text` |
| `maxConcurrency`, `password`, `chunkSize`, `chunkOverlap`, `extractImages` | ignored |

Not supported (a note is written to the run log and the run continues): `base64Pdfs` (URLs only), `startPage` above 1, `extractTables`, `outputFormat` other than the default (every item carries `text`, `pages[]` and `metadata`), and password-protected PDFs. Output field names are this actor's own (`text`, `pages`, `pageCount`, `metadata`), so check your downstream mapping. Not affiliated with those actors or their authors.

### Pricing

Pay per event: one `pdf-extracted` event (USD 0.003) per PDF that is parsed successfully. Errors and PDFs with no extractable text (`status: "no_text"`) are not charged. At most `maxPdfs` (default 100) URLs are processed per run.

#### Price per PDF

This actor charges USD 0.003 per PDF parsed successfully, flat for every plan, with no per-run start fee. A comparable Store actor (automation-lab/pdf-text-extractor, prices read from its public Store page on 2026-10-21) lists a USD 0.005 per-run start fee plus USD 0.00345 per PDF on the free plan. Example: 10 PDFs in one run cost USD 0.03 here versus about USD 0.0395 there. Check each actor's current pricing before you compare; prices change.

### Limits

- Text-based PDFs only. Scanned/image PDFs contain no text layer, so they return empty text (no OCR).
- Password-protected PDFs return an error item.
- Only public http(s) URLs; localhost/private network addresses are refused.

Built with [pdfjs-dist](https://github.com/mozilla/pdf.js) (Apache-2.0) and the Apify SDK.

### Local run

```
mkdir -p storage/key_value_stores/default
echo '{"urls":[{"url":"https://example.com/a.pdf"}]}' > storage/key_value_stores/default/INPUT.json
npm install && npm start
```

### Non-affiliation

Not affiliated with or endorsed by Adobe or any PDF publisher whose files you supply.

### AI disclosure

Built with AI assistance (Claude); maintained by the publisher.

### Related PennyData actors

- [Website Screenshot & URL to PDF](https://apify.com/pennydata/shot-pdf): full-page screenshots and URL/HTML to PDF with headless Chromium.
- [Website Contact Extractor](https://apify.com/pennydata/website-contact-extractor): public emails, phones and social links from website pages.

I built this and earn from its runs. Written with AI assistance (Claude).

# Actor input Schema

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

Public http(s) URLs of PDF files. Each successfully processed PDF is one billable event; failed URLs yield an error item and are free. Also accepted as aliases: pdfUrls, startUrls, sources, url, urlsText (see Switching from another PDF extractor in the README). Also accepted as aliases: pdfUrls, startUrls, sources, url, urlsText (see Switching from another PDF extractor in the README).

## `maxFileSizeMB` (type: `integer`):

PDFs larger than this are skipped with an error item (checked via Content-Length and while downloading).

## `maxPages` (type: `integer`):

Only extract the first N pages. 0 means all pages. Page count in the output always reflects the full document.

## `includeLines` (type: `boolean`):

Add per-page 'lines' with text grouped by visual row and split into cells where wide horizontal gaps occur (rows with 3+ cells are flagged tableLike). Turn off for smaller output.

## `outputFormat` (type: `string`):

'text' (default) keeps the plain text output. 'markdown' converts a PDF to Markdown: headings from font size, bullet lists and pipe tables, in 'text' and each page's 'markdown' field. Use markdown for LLM prompts and RAG.

## `chunkSize` (type: `integer`):

0 (default) returns one item per PDF. A value above 0 returns one dataset item per RAG chunk of about this many tokens (4 characters per token), split on paragraph boundaries, each with pageStart, pageEnd, pages and chunkIndex, ready for a vector database. Still billed once per PDF. Try 500 to 1000.

## `chunkOverlap` (type: `integer`):

Tokens of trailing text repeated at the start of the next chunk so context is not lost at boundaries (only used when chunkSize is above 0). Capped at half the chunk size.

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

Maximum time to wait for each PDF download before giving up with an error item.

## `maxPdfs` (type: `integer`):

Process at most this many URLs per run (cost guard).

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

Compatibility alias: one PDF URL, merged with urls.

## `urlsText` (type: `string`):

Compatibility alias: PDF URLs pasted one per line, merged with urls.

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

Compatibility alias for urls (strings or {url} objects).

## `startUrls` (type: `array`):

Compatibility alias for urls (strings or {url} objects).

## `sources` (type: `array`):

Compatibility alias for urls (strings or {url} objects).

## `timeoutPerPdfSecs` (type: `integer`):

Compatibility alias for timeoutSecs.

## `maxPdfMb` (type: `integer`):

Compatibility alias for maxFileSizeMB.

## `endPage` (type: `integer`):

Compatibility alias: extract the first N pages (same as maxPages).

## `includePages` (type: `boolean`):

Compatibility: false drops the pages\[] array from each item (text is kept).

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://www.w3.org/WAI/WCAG21/working-examples/pdf-table/table.pdf"
    }
  ],
  "maxFileSizeMB": 25,
  "maxPages": 0,
  "includeLines": true,
  "outputFormat": "text",
  "chunkSize": 0,
  "chunkOverlap": 0,
  "timeoutSecs": 60,
  "maxPdfs": 100
}
```

# Actor output Schema

## `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 = {
    "urls": [
        {
            "url": "https://www.w3.org/WAI/WCAG21/working-examples/pdf-table/table.pdf"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("pennydata/pdf-text-extractor-pay-per-pdf").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": [{ "url": "https://www.w3.org/WAI/WCAG21/working-examples/pdf-table/table.pdf" }] }

# Run the Actor and wait for it to finish
run = client.actor("pennydata/pdf-text-extractor-pay-per-pdf").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": [
    {
      "url": "https://www.w3.org/WAI/WCAG21/working-examples/pdf-table/table.pdf"
    }
  ]
}' |
apify call pennydata/pdf-text-extractor-pay-per-pdf --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,pennydata/pdf-text-extractor-pay-per-pdf"
        }
    }
}
```

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/JzoscX8Mgn2kfiuxd/builds/qgIIv05kLVPbGzpEN/openapi.json
