# PDF to Text and JSON (`comradecoding/pdf-to-text`) Actor

Give it PDF links, get the text of every page plus title, author, dates and page count as JSON. No OCR. $0.005 per PDF.

- **URL**: https://apify.com/comradecoding/pdf-to-text.md
- **Developed by:** [Comrade Coding](https://apify.com/comradecoding) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 converted pdfs

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 to Text and JSON

Give it a list of PDF links. For each PDF you get back the text of every page, the whole text in one field, and the document's metadata: title, author, subject, keywords, the software that made it, creation and modification dates, PDF version, page count, and file size.

Use it to feed reports, papers, forms, or manuals into a search index or a language model, to check what a batch of PDFs contains without opening them, or to pull dates and authors out of a document archive.

### What it does to each URL

1. Checks the site's `robots.txt`. If the file is disallowed, it is skipped and never downloaded. If `robots.txt` sets a `Crawl-delay`, the actor waits that long between requests to that site.
2. Downloads the file, up to the size limit you set (50 MB by default). If the first bytes show it is not a PDF (an HTML page, an image, a login screen), the download stops there.
3. Reads the PDF's text layer page by page with pdf.js, the PDF engine used in Firefox. Text items on the same line are joined, and each new line in the PDF starts a new line in the output.
4. Reads the document info and XMP metadata.

### Input

| Field | Type | Default | What it does |
| --- | --- | --- | --- |
| `startUrls` | array | required, no default | PDF links. Plain strings or `{ "url": "..." }` objects. Duplicates are dropped. |
| `maxItems` | integer | `100` | Stop after this many PDFs. Caps what a run costs you. |
| `maxPagesPerPdf` | integer | `0` | Read only the first N pages of each PDF. `0` reads all pages. |
| `maxFileSizeMb` | integer | `50` | Skip files larger than this (up to 100). |
| `includePages` | boolean | `true` | Add the `pages` array, one entry per page. |
| `includeFullText` | boolean | `true` | Add the `text` field with all pages joined by a blank line. |

Example input:

```json
{
    "startUrls": [
        { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" },
        { "url": "https://arxiv.org/pdf/1706.03762" }
    ],
    "maxItems": 20,
    "maxPagesPerPdf": 0,
    "includePages": true,
    "includeFullText": true
}
```

### Output

One dataset item per PDF. This is a real result from a test run. The text fields are cut short here with `...`, and only the first two of the six pages are shown.

```json
{
    "source_url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "final_url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "fetched_at": "2026-10-28T06:23:38.789Z",
    "status_code": 200,
    "file_size_bytes": 140815,
    "pdf_version": "1.7",
    "page_count": 6,
    "pages_extracted": 6,
    "truncated": false,
    "truncated_reason": null,
    "title": "Form W-9 (Rev. March 2024)",
    "author": "SE:W:CAR:MP",
    "subject": "Request for Taxpayer Identification Number and Certification",
    "keywords": "Fillable",
    "creator": "Designer 6.5",
    "producer": "Designer 6.5",
    "created_at": "2024-03-06T13:18:13.000Z",
    "modified_at": "2024-03-06T13:18:13.000Z",
    "word_count": 6279,
    "char_count": 37686,
    "pages_without_text": 0,
    "pages_failed": 0,
    "has_text": true,
    "text": "Form W-9(Rev. March 2024)\nRequest for Taxpayer\nIdentification Number and Certification\n...",
    "pages": [
        {
            "page_number": 1,
            "text": "Form W-9(Rev. March 2024)\nRequest for Taxpayer\nIdentification Number and Certification\nDepartment of the Treasury\n...",
            "word_count": 932,
            "char_count": 5660
        },
        {
            "page_number": 2,
            "text": "Form W-9 (Rev. 3-2024) Page 2\nmust obtain your correct taxpayer identification number (TIN), which\n...",
            "word_count": 1349,
            "char_count": 8168
        }
    ]
}
```

Fields:

- `source_url`: the URL you gave. `final_url`: where it ended up after redirects.
- `fetched_at`: ISO 8601 time of the download. `status_code`: HTTP status. `file_size_bytes`: size of the PDF.
- `pdf_version`: the version in the file header, such as `1.7`.
- `page_count`: pages in the document. `pages_extracted`: pages read in this run.
- `truncated`: `true` when not every page was read. `truncated_reason` says why: `maxPagesPerPdf`, `size limit` (the text passed about 3.5 million characters, see Limits), or `time limit` (reading took over 3 minutes).
- `title`, `author`, `subject`, `keywords`, `creator`, `producer`: from the document info, falling back to XMP metadata. `null` when the PDF does not set them. Many PDFs made with LaTeX or scanners leave them empty.
- `created_at`, `modified_at`: converted to ISO 8601 UTC. `null` when missing or unreadable.
- `word_count`, `char_count`: totals over the pages read.
- `pages_without_text`: pages that had no text layer. `pages_failed`: pages pdf.js could not read (the rest of the document still comes back).
- `has_text`: `false` when no page had any text, which usually means a scanned document.
- `text`: all page text joined with a blank line between pages. Only when `includeFullText` is on.
- `pages`: `{ page_number, text, word_count, char_count }` for each page read. Only when `includePages` is on.

URLs that fail are not added to the dataset and are not charged. They are listed in the `FAILED_URLS` record in the run's key-value store, each with a reason, for example `HTTP 404`, `Not a PDF (server sent text/html)`, `Disallowed by robots.txt`, `File is 120 MB, over the 50 MB limit`, or `PDF is password protected`. A `SUMMARY` record in the same store gives the counts for the run.

### Limits

- **No OCR.** Scanned PDFs, and pages that are only images, return empty text. You can spot them with `has_text: false` or `pages_without_text`. They still count as a result because the page count and metadata are returned.
- Public files only. It does not log in, send cookies, or open password-protected PDFs. PDFs that are encrypted but open without a password (common for "no copying" settings) are read normally.
- The link has to point at the PDF file itself. A web page that embeds or links to a PDF is reported as `Not a PDF`.
- Text comes back in the order it is stored in the file. For most single-column documents that is reading order. Multi-column layouts, tables, and forms can come out with columns interleaved or cells on separate lines. There is no table detection.
- Sometimes a space between two words is lost when the PDF places them as separate pieces very close together (`Form W-9(Rev. March 2024)` above).
- Files over 100 MB are always skipped. Text past about 3.5 million characters in one PDF (several thousand dense pages) is cut off to keep each result under Apify's 9 MB item limit, and `truncated` is set.
- Files are fetched one at a time with a 300 to 800 ms pause between requests to the same site, or the site's `Crawl-delay` if it is longer. arXiv, for example, asks for 15 seconds, so a list of 40 arXiv papers takes about 10 minutes. Sites that ask for more than 120 seconds are skipped.
- If a site's `robots.txt` cannot be reached (server error or timeout), that site's files are skipped rather than guessed at.
- The output contains whatever text is in the PDF, including any names or contact details printed in it. Check that you have the right to use the documents you process.

### Pricing

Pay per result: $0.005 for each PDF that ends up in the dataset, whatever its page count. Failed and skipped URLs are free.

What a run costs:

- 20 PDFs: $0.10
- 100 PDFs (the default `maxItems`): $0.50
- 1,000 PDFs: $5.00

Set `maxItems` to cap the cost of a run.

This actor is not affiliated with any of the sites used in the examples.

### Use it from an AI agent

Add this actor as a tool through Apify's MCP server: `https://mcp.apify.com?tools=comradecoding/pdf-to-text`. The agent gets one JSON row per PDF with the text of every page plus title, author, dates and page count.

### Integrations

Anything that can call the Apify API can run this actor: the Apify API and client libraries, and Apify's integrations for Make, n8n, Zapier and LangChain. Results come back as a dataset you can download as JSON, CSV or Excel.

### What it costs

| Run size | Cost |
|---|---|
| 100 PDFs | about $0.50 |
| 1,000 PDFs | about $5 |
| 10,000 PDFs | about $50 |

Platform usage is included in the price.

### FAQ

**Does it read scanned PDFs?**

No, there is no OCR. Scanned pages return empty text and are flagged with `has_text: false`.

**Can I give it a web page that links to a PDF?**

No, the link has to point at the PDF file itself. Other links are reported as `Not a PDF`.

**Are tables kept?**

Text comes out in the order stored in the file. Tables and multi-column layouts can come out interleaved; there is no table detection.

**How big can a file be?**

Up to 100 MB. Very long text is cut at about 3.5 million characters and `truncated` is set.

### Related actors by ComradeCoding

- [URL to Markdown Converter](https://apify.com/comradecoding/url-to-markdown)
- [Sitemap URL Extractor](https://apify.com/comradecoding/sitemap-url-extractor)

# Actor input Schema

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

Required. Direct links to PDF files on public sites. Plain strings or { "url": "..." } objects. Duplicates are dropped. URLs that are not PDFs, return an error, or are blocked by robots.txt are listed in the FAILED_URLS record and are not charged.

## `maxItems` (type: `integer`):

Stop after this many PDFs have been extracted. You pay per PDF, so this caps the cost of a run.

## `maxPagesPerPdf` (type: `integer`):

Read only the first this-many pages of each PDF. 0 reads every page. Results that stop early have truncated set to true. The price per PDF is the same either way.

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

Skip PDFs larger than this. The download stops as soon as the limit is passed. Up to 100 MB.

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

Add a pages array with the text, word count, and character count of each page.

## `includeFullText` (type: `boolean`):

Add a text field with all pages joined, separated by a blank line. Turn off if you only need the pages array, to halve the result size.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    },
    {
      "url": "https://arxiv.org/pdf/1706.03762"
    }
  ],
  "maxItems": 20,
  "maxPagesPerPdf": 10,
  "maxFileSizeMb": 20,
  "includePages": true,
  "includeFullText": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

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

No description

## `failures` (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 = {
    "startUrls": [
        {
            "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("comradecoding/pdf-to-text").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 = { "startUrls": [{ "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" }] }

# Run the Actor and wait for it to finish
run = client.actor("comradecoding/pdf-to-text").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 '{
  "startUrls": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ]
}' |
apify call comradecoding/pdf-to-text --silent --output-dataset

```

## MCP server setup

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

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/Z9ILwShg3aLHD0uOV/builds/GRKGAqpdN7cbFYPjg/openapi.json
