# PDF Text Extractor (`cyber1nj3ct0r/pdf-text-extractor`) Actor

Extract the text of PDF files from their web addresses. For each PDF you get the full text, optional text per page, page count, document data (title, author, dates), word count and a flag for scanned PDFs. Paste a list or link a text file.

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

## Pricing

from $1.40 / 1,000 pdfs

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

### What does PDF Text Extractor do?

PDF Text Extractor downloads PDF files from their web addresses and gives you what is inside them: the **full text**, the **text of each page** (if you want it), the **page count** and the **document data** (title, author, subject, keywords, program, dates). Paste the addresses or link a file that lists them, and export the results to Excel, CSV, JSON or XML, or send them straight into your own pipeline.

- One result per PDF, thousands of PDFs in one run
- Full text, with one line per line of the page and a blank line between pages
- Optional text per page, with page number and character count, so you can say where something was found
- Title, author, subject, keywords, creator, producer and creation and modification dates (dates in ISO 8601, UTC)
- Character and word counts and the file size
- Flags PDFs that are probably scanned pictures with no text in them, so you know they need OCR
- Reads password-protected PDFs when you give the password
- Clear error codes for addresses that are not PDFs, files that are too big, missing pages, broken files and servers that do not answer

The tool reads the text that is stored in the PDF. It does not do OCR, so a PDF that only holds pictures of pages comes back with empty text and a note that says so.

### What data do you get?

| Field | Example |
|---|---|
| PDF address | `https://www.irs.gov/pub/irs-pdf/f1040.pdf` |
| Final address | where the address ended up after redirects |
| Pages | `2` (and how many were read) |
| Title, author, subject, keywords | `2025 Form 1040`, `C:DC:TS:CAR:MP`, ... |
| Creator, producer | `Designer 6.5` |
| Created, modified | `2026-01-02T13:24:31.000Z` |
| Text | the text of all pages read |
| Pages (optional) | `[{ "page": 1, "text": "...", "characters": 1234 }, ...]` |
| Characters, words | `10152`, `2225` |
| File size | `220237` bytes |
| Probably scanned | `false` |
| Note | why a result is cut short, or what went wrong |
| Error | empty, or a code and a message |

### How to extract text from PDFs

1. Under **PDF web addresses**, paste your list with **Bulk edit**, add addresses one by one, or link a text file (a Google Sheet published as CSV works too).
2. Choose whether you want the text of each page, and how many pages to read of each PDF.
3. Press **Start**.
4. Open the **PDFs** table to see the overview, or export the full results.

The addresses must point to the PDF file itself. An address of a web page that links to a PDF returns the error `NOT_A_PDF`.

### How much does it cost to extract text from PDFs?

You pay for results, not for time: **$2.00 per 1,000 PDFs**. Apify subscribers on the Silver plan pay $1.70 and on the Gold plan $1.40 per 1,000 PDFs. Each run also has a start fee of $0.00005. Duplicates in your list are read, and paid, once.

| PDFs | Cost |
|---|---|
| 100 | $0.20 |
| 1,000 | $2.00 |
| 10,000 | $20.00 |
| 100,000 | $200.00 |

Every address in your list gives one result, also when the PDF could not be read: that result holds the error, and it counts like any other. Apify's free plan includes $5 of credit each month, which covers about 2,500 PDFs. Set a maximum cost for the run in Apify Console and the tool stops there and keeps what it has read.

### Input

The only required field is **PDF web addresses**. The other options:

- **Text of each page**: adds the text of every page as its own entry. Makes the results about twice as large.
- **Maximum pages per PDF**: by default the first 100 pages of each PDF are read, up to 500. Reading a page takes the same effort in every PDF, so this limit keeps the price per PDF fair. A longer PDF comes back with the pages it has read and a note that says how many it skipped.
- **Maximum file size**: 50 MB by default. A larger file is not downloaded fully and comes back with `FILE_TOO_LARGE`. With the default memory a run reads files up to about 70 MB.
- **Maximum number of PDFs**: reads only the first part of a long list.
- **Password for protected PDFs**: one password, tried on every PDF of the run. It is stored encrypted and is not saved in the results.
- **Time limit per download**: how long one PDF may take to download.

### Output example

```json
{
    "url": "https://www.irs.gov/pub/irs-pdf/f1040.pdf",
    "finalUrl": null,
    "position": 1,
    "httpStatus": 200,
    "pageCount": 2,
    "pagesRead": 2,
    "truncated": false,
    "title": "2025 Form 1040",
    "author": "C:DC:TS:CAR:MP",
    "subject": "U.S. Individual Income Tax Return",
    "keywords": "Fillable",
    "creator": "Designer 6.5",
    "producer": "Designer 6.5",
    "creationDate": "2026-01-02T13:24:31.000Z",
    "modificationDate": "2026-01-02T13:24:31.000Z",
    "pdfVersion": "1.7",
    "text": "Form\n1040 2025U.S. Individual Income Tax Return\nDepartment of the Treasury—Internal Revenue Service\n...",
    "characters": 10152,
    "words": 2225,
    "fileSizeBytes": 220237,
    "probablyScanned": false,
    "message": null,
    "extractedAt": "2026-10-16T06:41:21.606Z",
    "error": null
}
```

The text is shortened here. With **Text of each page** on, the result also has a `pages` list. An address that fails gives a result with the same fields, empty, and the reason in `error` and `message`:

```json
{
    "url": "https://www.example.org/old/report.pdf",
    "httpStatus": 404,
    "text": null,
    "message": "The server answered with HTTP 404.",
    "error": { "code": "HTTP_ERROR", "message": "The server answered with HTTP 404." }
}
```

### What the error codes mean

| Code | Meaning |
|---|---|
| `INVALID_INPUT` | the entry is not a web address |
| `HTTP_ERROR` | the server answered with an error such as 404, 403 or 500 |
| `NOT_A_PDF` | the address returned a web page or another kind of file |
| `FILE_TOO_LARGE` | the file is above **Maximum file size** |
| `PASSWORD_REQUIRED`, `PASSWORD_INCORRECT` | the PDF needs a password, or the one you gave does not open it |
| `TEXT_COPY_NOT_ALLOWED` | the owner of the PDF switched off copying its text; the tool respects that |
| `PDF_CORRUPT`, `PDF_UNREADABLE` | the file is damaged or cannot be read |
| `PARSE_TIMEOUT`, `PARSE_OUT_OF_MEMORY` | the file took too long or needed too much memory and was stopped; the rest of the run carries on |
| `DNS_NOT_FOUND`, `TIMEOUT`, `CONNECTION_REFUSED`, `BLOCKED_ADDRESS` and similar | the server could not be reached, or the address is a private network address, which the tool never fetches |

### What can you use it for?

- Feed PDF text into a search index, a database or an AI pipeline, with the page number of every passage
- Collect the title, author and dates of a folder full of reports, papers or manuals
- Check which PDFs on your list are scanned and need OCR, and which have text
- Count the pages and words of the documents you publish
- Monitor a list of PDFs on a schedule in Apify Console

### Use it from the API

```bash
curl -X POST "https://api.apify.com/v2/acts/cyber1nj3ct0r~pdf-text-extractor/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"pdfUrls": [{"url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"}], "includePages": true}'
```

The same works from Python, JavaScript, Make, Zapier and n8n through Apify's integrations.

### FAQ

#### Does it read scanned PDFs?

No. It reads the text that is stored in the PDF. A PDF that is only a set of scanned pictures has no text to read: the result has empty text, `probablyScanned` is `true` and the note suggests running the file through an OCR tool first. A PDF counts as probably scanned when most of its pages have almost no text.

#### Why is the text not laid out like the page?

The text comes out line by line, in the order the PDF stores it. Columns, tables and text boxes become lines of text, and words that were split at the end of a line stay split. This is fine for search and for feeding text to other tools, but it does not rebuild tables.

#### Which languages does it handle?

The text is returned as the PDF stores it, in Unicode. It was tested with English text, accented Latin letters and a Japanese document. The word count splits on spaces, so it is not meaningful for Chinese and Japanese, which are written without them.

#### Why were only some pages read?

By default only the first 100 pages of each PDF are read, and 500 is the most you can ask for. The result says how many pages the PDF has and how many were read. Reading is also stopped after about 40 seconds for one PDF, and when the text would make the saved result too large (about 7.5 MB); the note says so.

#### Can it read a PDF that asks for a password?

Yes, if you give the password under **Password for protected PDFs**. Without it, or with a wrong one, the result has the error `PASSWORD_REQUIRED` or `PASSWORD_INCORRECT`. The tool does not try to guess passwords. PDFs that open without a password but have copying switched off return `TEXT_COPY_NOT_ALLOWED`.

#### Does it read robots.txt?

It requests only the addresses you give it, once each, and follows no links, so it does not crawl and does not read `robots.txt`.

#### Does it keep my files?

No. A PDF is held in memory while it is read and then dropped. Only the results go to your dataset.

#### A server blocks the tool. What can I do?

The tool identifies itself honestly in its User-Agent and does not try to get round blocks, logins or CAPTCHAs. If the PDF is yours, allow the tool in your firewall, or download the file yourself and host it where the tool can reach it.

#### Is it allowed to extract text from PDFs?

The tool downloads each address once, like a visitor opening the file, and reads the text. You are responsible for using only files you are allowed to read and for how you use the text and the document data in the results, including copyright and personal data in the documents.

#### Something does not work

Open an issue on the **Issues** tab with an address that shows the problem. Every report is read and answered.

# Actor input Schema

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

The addresses of the PDF files, for example <code>https://www.example.com/report.pdf</code>. Add them one by one, paste many at once with <b>Bulk edit</b>, or link a text file (or a Google Sheet published as CSV) that holds them. Duplicates are read once. Only these addresses are fetched; the tool follows no links.

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

Add the text of every page as a separate entry (page number, text, number of characters) next to the full text. Useful when you need to say on which page something stands. Makes the results about twice as large.

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

Only the first pages of a longer PDF are read, and the result says so. Reading a page takes the same effort in every PDF, so this limit keeps the price per PDF fair. Raise it (up to 500) for longer reports; a PDF longer than 500 pages is read up to page 500.

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

PDF files larger than this are not downloaded fully and come back with the error FILE_TOO_LARGE. A run with the default memory (1024 MB) reads files up to about 70 MB; for larger ones start the run with more memory.

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

Read at most this many PDFs from your list, in the order of the list. Leave empty to read all of them.

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

Only needed for PDFs that ask for a password to open. The same password is tried on every PDF in the run. It is stored encrypted and never saved in the results. PDFs with a password and no password given come back with the error PASSWORD_REQUIRED.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one PDF to download completely before counting it as not answering.

## Actor input object example

```json
{
  "pdfUrls": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    },
    {
      "url": "https://raw.githubusercontent.com/mozilla/pdf.js/master/examples/learning/helloworld.pdf"
    }
  ],
  "includePages": false,
  "maxPagesPerPdf": 100,
  "maxFileSizeMb": 50,
  "requestTimeoutSecs": 60
}
```

# Actor output Schema

## `pdfs` (type: `string`):

One record per PDF with its text, page count, document data and any error.

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

Counts for the run: PDFs read, failed, probably scanned and cut short, pages and characters.

# 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 = {
    "pdfUrls": [
        {
            "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
        },
        {
            "url": "https://raw.githubusercontent.com/mozilla/pdf.js/master/examples/learning/helloworld.pdf"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cyber1nj3ct0r/pdf-text-extractor").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 = { "pdfUrls": [
        { "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" },
        { "url": "https://raw.githubusercontent.com/mozilla/pdf.js/master/examples/learning/helloworld.pdf" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("cyber1nj3ct0r/pdf-text-extractor").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 '{
  "pdfUrls": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    },
    {
      "url": "https://raw.githubusercontent.com/mozilla/pdf.js/master/examples/learning/helloworld.pdf"
    }
  ]
}' |
apify call cyber1nj3ct0r/pdf-text-extractor --silent --output-dataset

```

## MCP server setup

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

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/zNyjyXGarb2qMAseR/builds/7YrGetHInGytzFgGe/openapi.json
