# PDF Text Extractor: PDF to Markdown, Text, Pages & Metadata (`best_apis/pdf-text-extractor`) Actor

Extract text from any PDF URL as clean Markdown or plain text: per-page text, RAG chunks, title, author, dates, outline, links and scanned-page detection. Give PDF links or web pages that link to PDFs. No API key. $1 per 1,000 PDFs.

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

## Pricing

Pay per event

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 Extractor: PDF to Markdown, Text, Pages & Metadata

Turn any **PDF URL into clean, structured text** in seconds, with no API key and no setup. Give it direct PDF links, or a web page that links to PDFs, and get:

- **Full text as Markdown** (headings rebuilt from font sizes, wrapped lines joined into paragraphs, hyphenated line breaks fixed, bullet lists) or **plain text** with the original line breaks, or both
- **One row per document, per page, or per chunk**: chunk mode splits the text into **RAG-ready pieces** (size and overlap you choose) with page numbers, ready for a vector database, embeddings or an LLM
- **PDF metadata**: title, author, subject, keywords, creator app, producer, **creation and modification dates**, PDF version, language, page count, file size, SHA-256, fillable form and tagged-PDF flags
- **Document outline** (table of contents / bookmarks) and every **hyperlink** inside the PDF
- **Scanned-page detection**: pages without a text layer are listed (`noTextLayerPages`) and `likelyScanned` flags image-only documents, so you know which files need OCR
- **PDF link discovery**: put in a web page (an annual-report page, a government forms page, a documentation index) and every linked PDF is extracted
- Page ranges (`1-5, 8, 10-`), max pages, password-protected PDFs, max file size, optional Apify Proxy

Built on Mozilla's pdf.js, the PDF engine inside Firefox, so it reads real-world PDFs from Word, LaTeX, InDesign, Google Docs, scanners and government form tools.

### Pricing: $1 per 1,000 PDFs

| | This Actor | Typical PDF text extractors on Apify |
|---|---|---|
| Per PDF | **$0.001** ($1 per 1,000) | $0.0035 to $0.02 per PDF |
| Per page | **$0.00002** ($0.02 per 1,000 pages) | up to $0.001 per page |
| Actor start | $0.0005 per GB of memory (512 MB default) | $0.005 per run |
| Failed URLs, web pages scanned for links | **free** | often charged |

A 10-page report costs **$0.0012**. A 150-page manual costs $0.004. Markdown, plain text, chunks, metadata, outline and links are all included in the same price. Set **Max PDFs in total** to cap a run; your Apify maximum cost per run is always respected.

### Use cases

- **AI, RAG and LLM pipelines**: feed PDFs to ChatGPT, Claude, LangChain, LlamaIndex or a vector database as clean Markdown chunks with page numbers for citations.
- **Document search and archiving**: index reports, manuals, papers and policies; keep the SHA-256 to detect changed files.
- **Financial and ESG research**: pull the text of annual reports, filings and investor presentations straight from investor-relations pages.
- **Legal, tender and government documents**: extract forms, notices, regulations and tender documents in bulk from public portals.
- **Academic research**: get the text, outline and references links of arXiv and journal PDFs.
- **Monitoring**: schedule a daily run on a page that publishes PDFs and get the new documents' text by webhook, Slack, Google Sheets, Make or Zapier.

### Input examples

Two PDFs as Markdown, one row each:

```json
{ "urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf", "https://arxiv.org/pdf/1706.03762"] }
```

Every PDF linked on a web page:

```json
{ "urls": ["https://www.irs.gov/forms-pubs/about-form-1040"], "maxPdfsPerPage": 25 }
```

RAG chunks of about 1,500 characters with 200 characters of overlap:

```json
{ "urls": ["https://arxiv.org/pdf/1706.03762"], "outputMode": "chunks", "chunkSize": 1500, "chunkOverlap": 200 }
```

First 3 pages only, one row per page, Markdown and plain text:

```json
{ "urls": ["https://www.irs.gov/pub/irs-pdf/p17.pdf"], "outputMode": "pages", "pages": "1-3", "format": "both" }
```

### Output

Document row (text shortened):

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf", "status": "ok", "fileName": "fw9.pdf", "fileSizeBytes": 140815,
  "sha256": "2d420cbb4123dcf1fb82595b2359cfbb5d81f00b9df9d359fcc7af361d093f53",
  "numPages": 6, "pagesExtracted": 6, "title": "Form W-9 (Rev. March 2024)",
  "subject": "Request for Taxpayer Identification Number and Certification", "author": "SE:W:CAR:MP",
  "creator": "Designer 6.5", "producer": "Designer 6.5", "createdAt": "2024-03-06T13:18:13.000Z", "modifiedAt": "2024-03-06T13:18:13.000Z",
  "pdfVersion": "1.7", "language": "en-US", "hasForm": true, "isTagged": false,
  "wordCount": 6282, "charCount": 37924, "noTextLayerPages": [], "likelyScanned": false,
  "outline": null, "links": [],
  "markdown": "# Form W-9\n\n(Rev. March 2024)\n\n## Request for Taxpayer Identification Number and Certification\n\n...",
  "extractedAt": "2026-11-03T05:19:49.029Z"
}
```

Page rows add `page`, `wordCount`, `noTextLayer` and that page's `links`. Chunk rows have `chunkIndex`, `chunkCount`, `pageStart`, `pageEnd`, `text` and `charCount`.

URLs that fail (404, not a PDF, too large, wrong password) get a free row with `status: "failed"` and an `error`, are listed in the `FAILURES` record of the key-value store, and the rest of the run continues. `SUMMARY` holds the run totals. Very large documents (over about 4 million characters) are saved to the key-value store and the row links them as `markdownUrl` / `textUrl`, because dataset rows are limited to 9 MB.

The dataset has three views: **PDFs** (overview table), **Text** and **Metadata**.

### How it works and limits

- Text comes from the PDF's own text layer. **Scanned PDFs (images of text) have no text layer**: those pages are listed in `noTextLayerPages` and are not OCRed. Most PDFs made by software (reports, forms, papers, invoices, manuals) have a text layer.
- Markdown structure is rebuilt from layout (font sizes, line spacing, bullets). Tables come out as text lines, and multi-column layouts follow the PDF's reading order.
- Downloads use a clear user agent, three PDFs at a time, and retries with back-off. Only use it on documents you are allowed to access.
- Default memory is 512 MB, enough for PDFs up to about 100 MB. Raise memory for very large files.

### FAQ

**Do I need an API key or account anywhere?** No. Only public URLs are fetched.

**How fast is it?** Small PDFs take well under a second each; a 140-page publication takes a few seconds at 512 MB.

**Can I extract password-protected PDFs?** Yes, if you know the password: set **PDF password**.

**Can I use it from AI agents?** Yes, through the Apify API, the Apify MCP server, or the Python and JavaScript clients:

```python
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("best_apis/pdf-text-extractor").call(run_input={"urls": ["https://arxiv.org/pdf/1706.03762"], "outputMode": "chunks"})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["pageStart"], item["text"][:80])
```

```bash
curl -X POST "https://api.apify.com/v2/acts/best_apis~pdf-text-extractor/run-sync-get-dataset-items?token=<YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" -d '{"urls":["https://www.irs.gov/pub/irs-pdf/fw9.pdf"]}'
```

**Is my data stored?** Results stay in your own Apify storage. The Actor keeps nothing.

### Related Actors

- [Sitemap URL Extractor](https://apify.com/best_apis/sitemap-url-extractor): find every PDF URL a website lists in its sitemaps.
- [Broken Link Checker](https://apify.com/best_apis/broken-link-checker): find dead links, including links to PDFs.
- [SEC EDGAR Scraper](https://apify.com/best_apis/sec-edgar-scraper): company filings and financial data.
- [Federal Register API](https://apify.com/best_apis/federal-register-api): US rules and notices, with their PDF links.

# Actor input Schema

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

Direct links to PDF files (https://example.com/report.pdf), or web pages that link to PDFs: every PDF linked on the page is extracted (see Find PDF links on web pages). Leave empty for a demo on two public PDFs.

## `outputMode` (type: `string`):

Document: one row per PDF with full text and metadata. Page: one row per page. Chunk: text split into pieces of about Chunk size characters at paragraph boundaries, with page numbers, for vector databases and LLMs. Price is the same in every mode.

## `format` (type: `string`):

Markdown rebuilds headings from font sizes, joins wrapped lines into paragraphs and fixes hyphenated line breaks. Plain text keeps the PDF's line breaks.

## `pages` (type: `string`):

Optional. Only these pages, e.g. 1-5, 8, 10- (from 10 to the end). Empty = all pages.

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

Stop after this many pages of each PDF (the row gets truncated: true).

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

Document mode only: also add a pages array with the text of each page.

## `includeLinks` (type: `boolean`):

Collect the clickable URLs inside the PDF.

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

Chunk mode only. Target chunk length; chunks break at paragraph boundaries.

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

Chunk mode only. Repeat up to this many characters of whole paragraphs from the end of the previous chunk.

## `findPdfLinks` (type: `boolean`):

When a URL is a web page instead of a PDF, extract every PDF it links to (up to Max PDFs per web page).

## `maxPdfsPerPage` (type: `integer`):

Cap on PDFs taken from each web page.

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

Hard cap for the whole run, so cost stays predictable.

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

Larger files are skipped (not charged). Raise memory to 1-2 GB for files over 100 MB.

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

Optional. Password for encrypted PDFs (used for every URL in the run).

## `proxyConfiguration` (type: `object`):

Optional. Not needed for most sites. Use Apify Proxy if a site blocks direct downloads.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://arxiv.org/pdf/1706.03762"
  ],
  "outputMode": "document",
  "format": "markdown",
  "maxPages": 2000,
  "includePages": false,
  "includeLinks": true,
  "chunkSize": 2000,
  "chunkOverlap": 200,
  "findPdfLinks": true,
  "maxPdfsPerPage": 25,
  "maxPdfs": 1000,
  "maxFileSizeMb": 100,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `overview` (type: `string`):

No description

## `full` (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 = {
    "urls": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://arxiv.org/pdf/1706.03762"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("best_apis/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 = { "urls": [
        "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
        "https://arxiv.org/pdf/1706.03762",
    ] }

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,best_apis/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/WqPDTcbx9Y30owsqH/builds/YXnnghiW2ExQFwDyN/openapi.json
