# PDF to Text & Markdown Converter (Metadata, Tables) (`blond_terrarium/pdf-to-text-markdown`) Actor

Convert PDF URLs to clean Markdown or plain text for LLMs, RAG and search: headings, paragraphs, lists, best-effort tables, title/author/date metadata, page count and word count. Fast, no OCR. Pay only for PDFs with extracted text.

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

## Pricing

from $1.50 / 1,000 pdf converteds

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

## PDF to Text & Markdown Converter (Metadata, Headings, Tables)

Give it PDF URLs and get **clean Markdown or plain text** for each document, ready for LLMs, RAG pipelines, search indexes and data extraction. Headings are detected from font sizes, and paragraphs are rebuilt across line breaks with de-hyphenation. Bulleted and numbered lists and simple tables are kept, and running headers, footers and page numbers are removed. Every row also includes the **PDF metadata** (title, author, subject, keywords, creator, producer, creation and modification dates, PDF version, language), plus **page count, word count, character count** and file size.

It's fast because it reads the PDF's text layer directly, with no browser and no OCR. In our tests a 15-page arXiv paper took under 1 second, and a 142-page IRS publication about 3 seconds.

**You only pay for PDFs where text was extracted.** 404s, timeouts, non-PDF responses, password-protected, corrupt or oversized files, and scanned PDFs without a text layer are all free.

### What you can use it for

- **LLM / RAG ingestion**: turn research papers, reports, manuals and filings into Markdown chunks with headings intact
- **Search and indexing**: full text plus title, author and dates for every PDF in a list
- **Document monitoring**: re-run on government forms, price lists or policy PDFs and diff the text
- **Data extraction**: pull text from invoices, statements or forms for downstream parsing
- **Crawling PDF libraries**: point it at an HTML page and it finds and converts the linked PDFs (`findPdfLinks`)
- Feeding documents to AI agents (works via the Apify MCP server)

### Input example

```json
{
  "startUrls": [
    { "url": "https://arxiv.org/pdf/1706.03762" },
    { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" }
  ],
  "outputFormat": "markdown",
  "pageRange": "",
  "maxPagesPerDoc": 0,
  "includePages": false,
  "removeHeadersFooters": true,
  "findPdfLinks": false,
  "maxChargedDocuments": 0,
  "maxFileSizeMb": 100
}
```

- `outputFormat`: `markdown`, `text` or `both`
- `pageRange`: e.g. `1-5,8,10-` (empty means all pages). `maxPagesPerDoc` caps pages per document (0 means no limit)
- `includePages`: adds a `pages` array with per-page text/Markdown, useful for citations and page-level chunking
- `findPdfLinks`: for HTML pages in your input, finds linked/embedded PDFs (`<a>`, `<iframe>`, `<embed>`, `<object>`) and converts them (up to `maxPdfLinksPerPage` per page)
- `maxChargedDocuments`: stop after this many charged documents (0 means no limit)

### Output example (one row per document; text shortened)

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "finalUrl": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "httpStatus": 200,
  "contentType": "application/pdf",
  "fileSizeBytes": 140815,
  "status": "OK",
  "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",
  "creationDate": "2024-03-06T13:18:13.000Z",
  "modificationDate": "2024-03-06T13:18:13.000Z",
  "pdfVersion": "1.7",
  "language": "en-US",
  "inferredTitle": "Form W-9(Rev. March 2024)",
  "pageCount": 6,
  "pagesExtracted": 6,
  "wordCount": 6348,
  "charCount": 37776,
  "markdown": "# Form W-9(Rev. March 2024)\n\n## Request for Taxpayer Identification Number and Certification\n\nDepartment of the Treasury Internal Revenue Service Go to www.irs.gov/FormW9 for instructions and the latest information.\n\nGive form to the requester. Do not send to the IRS.…",
  "textTruncated": false,
  "fullTextKey": null,
  "charged": true,
  "error": null,
  "durationMs": 927,
  "scrapedAt": "2026-10-23T13:16:57.305Z"
}
```

In Markdown, pages are separated by `---`. `inferredTitle` is the largest text on the first page, which is handy when the metadata title is missing. Very long documents (over 4 million characters) are stored in full in the key-value store (`fullTextKey`), and the row text is marked `textTruncated: true`.

#### Statuses

| status | Charged | Meaning |
|---|---|---|
| `OK` | yes | Text extracted |
| `NO_TEXT_LAYER` | no | Scanned/image-only PDF (OCR isn't included) |
| `ENCRYPTED` | no | Password-protected PDF |
| `CORRUPT` / `PARSE_ERROR` | no | Broken or unreadable PDF |
| `NOT_PDF` | no | The URL returned HTML, JSON or another non-PDF file |
| `TOO_LARGE` | no | Bigger than `maxFileSizeMb` |
| `HTTP_404` (and other HTTP codes), `TIMEOUT`, `DNS_ERROR`, `INVALID_URL`… | no | Download failed |
| `HTML_PAGE_SCANNED` / `NO_PDF_LINKS` | no | An HTML page scanned with `findPdfLinks` |

The `RUN_SUMMARY` record in the key-value store has the run totals.

### Pricing (pay per event)

You pay per **PDF converted**: the file downloaded, parsed and returned text. The price is per document, not per page.

| Apify plan | Price per 1,000 PDFs |
|---|---|
| Free | $3.00 |
| Bronze (Starter) | $2.50 |
| Silver (Scale) | $2.00 |
| Gold (Business) | $1.50 |

Failed, scanned, encrypted, oversized and non-PDF inputs are never charged. Platform usage (compute, storage, data transfer) is included, so you don't pay it separately. Each run also has Apify's standard start fee of $0.00005 per GB of memory. Optional Apify datacenter proxy (or your own proxy URLs) is included in the price; residential proxy isn't supported. Set **Max total charge** or `maxChargedDocuments` to cap your spend. The run stops cleanly when the cap is reached, without storing unpaid results.

### Features

- Headings (up to 4 levels) from font-size analysis, paragraphs, bulleted and numbered lists, and best-effort tables
- Multi-column layouts read in column order; rotated margin text is kept out of headings
- Running headers, footers and page numbers removed (can be turned off)
- De-hyphenation across line breaks, and CJK-aware spacing and word counts (Chinese, Japanese, Korean)
- Full PDF metadata with ISO dates
- Page ranges, per-page output and a page cap per document
- Follows redirects, retries 429/5xx, and runs documents in parallel with a per-host delay
- Refuses private/internal addresses

### Limitations

- **No OCR.** Scanned PDFs return `NO_TEXT_LAYER` for free. Use an OCR tool for those.
- Tables are best-effort. Aligned columns become Markdown tables, while complex or ruled tables may come out as tab-separated lines or paragraphs.
- Layout reconstruction is heuristic. Unusual layouts (posters, slides, magazine spreads) may mix reading order.
- Only direct PDF URLs and static HTML pages (for `findPdfLinks`) are supported. Pages that need a login or JavaScript to reach the PDF aren't.
- Default file size limit is 100 MB (up to 500 MB). Raise the run memory for very large PDFs.

### License

Built on Mozilla's [pdf.js](https://github.com/mozilla/pdf.js) (Apache-2.0). This Actor's code is licensed Apache-2.0.

### Responsible use

Only convert documents you are allowed to access and use. Each document takes one download request (plus redirects), with a per-host delay between requests.

# Actor input Schema

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

Direct links to PDF files (any URL that returns a PDF, also without .pdf in the name). One row per document.

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

Markdown keeps structure (good for LLMs/RAG). Plain text is paragraphs only.

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

Optional, e.g. '1-5', '1,3,10-12' or '20-' (from page 20 to the end). Empty = all pages.

## `maxPagesPerDoc` (type: `integer`):

0 = no limit. Applied after the page range. Price is per document, not per page.

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

Adds a 'pages' array with the text (and Markdown) of every page, e.g. for page-level citations.

## `removeHeadersFooters` (type: `boolean`):

Drops lines that repeat at the top/bottom of most pages (documents with 3+ pages) and bare page numbers.

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

If an input URL is a web page instead of a PDF, convert the .pdf files it links to (one level deep). The HTML page itself is free.

## `maxPdfLinksPerPage` (type: `integer`):

Only used with 'Find PDF links on HTML pages'.

## `maxChargedDocuments` (type: `integer`):

Stop after this many converted (charged) documents. 0 = no limit (the run's Max total charge still applies).

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

Larger files are skipped (free row with status TOO_LARGE). Very large PDFs may need more memory.

## `includeFailedDocuments` (type: `boolean`):

Adds a free row with the reason (404, not a PDF, encrypted, scanned/no text layer...) so you can see every input.

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

Parsing is CPU-bound, so more than 4-8 rarely helps.

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

Per file, including the download.

## `maxRetries` (type: `integer`):

For timeouts, resets, 429 and 5xx.

## `userAgent` (type: `string`):

Leave empty for the default browser-compatible UA.

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

Optional. Apify datacenter proxy or your own proxy URLs. Residential proxy isn't supported (it would raise the price for everyone).

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://arxiv.org/pdf/1706.03762"
    },
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ],
  "outputFormat": "markdown",
  "maxPagesPerDoc": 0,
  "includePages": false,
  "removeHeadersFooters": true,
  "findPdfLinks": false,
  "maxPdfLinksPerPage": 50,
  "maxChargedDocuments": 0,
  "maxFileSizeMb": 100,
  "includeFailedDocuments": true,
  "maxConcurrency": 4,
  "requestTimeoutSecs": 120,
  "maxRetries": 1,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Status, title, author, pages and word count per PDF.

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

Every row incl. the full Markdown/text and failed documents.

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

Totals: converted (charged), no text layer and failed (not charged).

# 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://arxiv.org/pdf/1706.03762"
        },
        {
            "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
        }
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("blond_terrarium/pdf-to-text-markdown").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://arxiv.org/pdf/1706.03762" },
        { "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf" },
    ],
    "proxyConfiguration": { "useApifyProxy": False },
}

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

```

## MCP server setup

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

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/xBbKP2ZNEVesgWVwd/builds/IFjPNdzcZtYLcwzib/openapi.json
