# PDF to Markdown and Text Converter with OCR (`maliksaid/pdf-to-text-markdown`) Actor

PDF to Markdown and text converter. Turns PDF files into Markdown with headings, plain text and tables as rows. Scanned pages use Tesseract OCR, Arabic included. Returns title, page count, metadata and optional RAG chunks for embeddings.

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

## Pricing

from $2.40 / 1,000 pdf documents

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 & Tables (OCR, RAG)

This Actor converts PDF files to plain text and Markdown. It finds headings, paragraphs, bullet lists and simple tables. Scanned pages are read with Tesseract OCR, including Arabic. You get one record per PDF with metadata such as title and page count. You can also get chunks sized for embeddings, ready for RAG and vector databases.

### What it does

- Reads PDFs from URLs you give, or from your own Apify key-value stores.
- Returns plain text and Markdown with `#`, `##` and `###` headings. Heading levels come from font sizes.
- Finds simple tables and returns them as rows and as Markdown tables.
- Drops running headers and footers, such as page numbers, that repeat on many pages.
- Reads scanned pages with Tesseract OCR. English and Arabic ship with the Actor. 13 more languages are available.
- Turns Arabic presentation-form letters back into normal letters, so Arabic text can be searched and embedded.
- Splits the Markdown into chunks with page numbers and the section heading, if you set a chunk size.
- Returns title, author, subject, dates, language, page count and the file's SHA-256.

The Actor runs pdf.js and Tesseract inside the container. Your files are not sent to any outside service.

### What you get

| Field | Meaning |
|---|---|
| `url` | The PDF URL, or `kv://store/KEY` for a file in a key-value store |
| `status` | `ok`, or `failed` with the reason in `error`. Failed files are free |
| `title` | Title from the PDF metadata. If there is none, the first top-level heading on page 1 |
| `author`, `subject`, `keywords`, `creator`, `producer`, `language` | PDF metadata. Empty when the file does not set them |
| `creationDate`, `modificationDate` | Dates from the PDF metadata, ISO 8601 |
| `pageCount`, `pagesProcessed`, `truncated` | Pages in the file, pages read, and whether a page limit cut the file short |
| `ocrPages`, `ocrPagesSkipped` | Pages read with OCR, and scanned pages skipped because of a limit |
| `text` | Plain text. Table cells are separated by tabs |
| `markdown` | Markdown with headings, paragraphs, lists and tables |
| `headings` | Level, text and page of each heading |
| `tables` | Page, row count, column count and rows of each table |
| `chunks` | Only with a chunk size above 0: text, page range and section of each chunk |
| `pages` | Only with **Include each page separately**: text and Markdown per page |
| `charCount`, `wordCount` | Size of the text |
| `fileName`, `fileSizeBytes`, `sha256` | File details |
| `passwordProtected` | `true` when the PDF needed a password, `false` when it opened without one |

Sample record from a test run on IRS Form W-9 (a US government work in the public domain). Long fields are shortened here:

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "source": "url",
  "status": "ok",
  "error": null,
  "fileName": "fw9.pdf",
  "fileSizeBytes": 140815,
  "title": "Form W-9 (Rev. March 2024)",
  "subject": "Request for Taxpayer Identification Number and Certification",
  "language": "en-US",
  "creationDate": "2024-03-06T13:18:13.000Z",
  "pageCount": 6,
  "pagesProcessed": 6,
  "truncated": false,
  "ocrPages": [],
  "charCount": 37544,
  "wordCount": 6481,
  "headings": [
    { "level": 1, "text": "Request for Taxpayer Identification Number and Certification", "page": 1 },
    { "level": 1, "text": "General Instructions", "page": 1 },
    { "level": 2, "text": "Purpose of Form", "page": 1 }
  ],
  "markdown": "Form W-9 (Rev. March 2024)\n\n# Request for Taxpayer Identification Number and Certification\n\nDepartment of the Treasury ..."
}
```

A table from IRS Publication 15-T, page 6, as the Actor returned it:

```json
{
  "page": 6,
  "rowCount": 8,
  "columnCount": 2,
  "rows": [["Payroll period", "Add additional"], ["Weekly", "$226.90"], ["Biweekly", "453.80"], ["Semimonthly", "491.70"]]
}
```

A chunk from IRS Publication 1 with **Chunk size** 1500:

```json
{
  "index": 1,
  "text": "communications from the IRS, and to speak to a supervisor about inadequate service.\n\n### 3. The Right to Pay No More than the Correct Amount of Tax\n\nTaxpayers have the ri...",
  "charCount": 1356,
  "pageStart": 1,
  "pageEnd": 1,
  "section": "3. The Right to Pay No More than the Correct Amount of Tax"
}
```

### How to use it

1. Paste the PDF links into **PDF URLs**. Each link must point straight to a PDF file.
2. For files stored in Apify, pick the stores in **PDFs in your Apify key-value stores**. Add record keys, or leave the keys empty to read every PDF in the stores.
3. For RAG, set **Chunk size for embeddings** to 1,000 or 2,000.
4. For scanned Arabic documents, add Arabic to **OCR languages**.
5. Run the Actor. Download the results as JSON, CSV or Excel, or read them through the API.

### Input

| Field | Default | Meaning |
|---|---|---|
| `startUrls` | IRS Form W-9 | PDF links |
| `pdfPassword` | none | PDF password. Opens password-protected PDFs. It is tried on every PDF in the run and never saved |
| `keyValueStores`, `keyValueStoreKeys` | none | Your Apify stores and record keys |
| `ocrMode` | `auto` | `auto` reads pages that have almost no text layer and hold a large image. `off` skips OCR |
| `ocrLanguages` | `["eng"]` | OCR languages. Each extra language makes OCR slower |
| `extractTables` | true | Find simple tables |
| `removeHeadersFooters` | true | Drop running headers and footers |
| `includeText`, `includeMarkdown` | true | Turn either output off to save space |
| `includePages` | false | Add text and Markdown per page |
| `chunkSize`, `chunkOverlap` | 0, 150 | Chunk length and overlap in characters. 0 turns chunks off |
| `maxItems` | 100 | Maximum PDFs per run. 0 means no limit |
| `maxPagesPerPdf` | 500 | Pages read per PDF, from page 1 |
| `maxPagesPerRun` | 5,000 | Pages read per run |
| `maxOcrPagesPerRun` | 50 | OCR pages per run |
| `maxFileSizeMb` | 50 | Larger files are skipped |

### Pricing

You pay per event:

| Event | Price |
|---|---|
| Actor start | $0.02 per run |
| PDF document, first 20 pages included | $0.003 per PDF ($3 per 1,000 PDFs) |
| Extra page, above 20 pages in a PDF | $0.0002 per page ($0.20 per 1,000 pages) |
| OCR page | $0.01 per scanned page ($10 per 1,000 pages) |

Failed files are free. Pages with a text layer are never charged for OCR. Examples:

- 100 text PDFs of up to 20 pages: $0.02 + $0.30 = $0.32.
- One 71-page text PDF: $0.02 + $0.003 + 51 x $0.0002 = $0.033.
- One 10-page scan: $0.02 + $0.003 + 10 x $0.01 = $0.123.

The Actor never does work it cannot charge for. When your maximum cost per run is close, it reads fewer pages and lists the skipped scanned pages.

### Limits and good to know

- Each download is capped at 60 seconds and retried at most twice.
- Only fetch files you have the right to use. The Actor reads robots.txt before each download and skips blocked files. It refuses private network addresses.
- Heading levels come from font sizes. Documents that use bold text of the same size for headings get fewer headings.
- Table detection works for tables whose cells line up in columns. Merged cells and multi-row headers can come back split. Tables on OCR pages come back as text.
- The Actor runs with 1,024 MB of memory. OCR of one dense 300 dpi page took about 35 seconds on the platform at 512 MB. It should take about half that at 1,024 MB, which is not yet measured. Large scans are scaled to at most 2,500 pixels on the long side. OCR stops 2 minutes before the run timeout, so finished work is saved.
- English uses Tesseract's fast model. Arabic and the other languages use the more accurate integer model.
- Password-protected PDFs open when you give the password. Without it, they fail with a clear, free error. Very large results go to the run's key-value store, and the record keeps the first 200,000 characters with a pointer in `fullOutputKey`.
- Right-to-left text comes back in reading order. Lines that mix Arabic and Latin, such as URLs, can come back partly reordered.

### FAQ

**Can it read password-protected PDFs?** Yes. Put the password in **PDF password**. The Actor tries it on every PDF in the run. PDFs without a password open as usual. A missing or wrong password gives a failed row with the reason, and failed rows are free. The password never appears in the output or the log.

# Actor input Schema

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

Links to the PDF files you want to convert. Each link must point straight to a PDF file. The Actor checks robots.txt before it downloads a file and skips files that robots.txt blocks.

## `pdfPassword` (type: `string`):

Optional. Opens password-protected PDFs. The same password is tried on every PDF in the run. PDFs without a password open as usual. The password is never saved in the output or the log.

## `keyValueStores` (type: `array`):

Pick key-value stores that hold your PDF files. The run gets read access to these stores only. Use this for files you uploaded to Apify or saved from another Actor.

## `keyValueStoreKeys` (type: `array`):

Keys of the PDF records to read from the chosen stores, for example report-2025.pdf. Leave empty to read every PDF record in the stores.

## `ocrMode` (type: `string`):

Auto reads scanned pages with Tesseract OCR. A page counts as scanned when it has almost no text layer and holds a large image. Off skips OCR, so scanned pages come back empty.

## `ocrLanguages` (type: `array`):

Languages for OCR. English and Arabic ship with the Actor. Other languages are downloaded once per run. Add every language that appears in the scans; each extra language makes OCR slower.

## `extractTables` (type: `boolean`):

Find simple tables (rows of cells that line up in columns) and return them as rows and as Markdown tables. Tables on OCR pages are returned as text.

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

Drop lines that repeat at the top or bottom of at least half of the pages, such as page numbers and running titles. Needs 3 pages or more.

## `includeText` (type: `boolean`):

Return the full plain text. Cells of a table row are separated by tabs.

## `includeMarkdown` (type: `boolean`):

Return Markdown with headings, paragraphs, bullet lists and tables.

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

Also return a list with the text and Markdown of each page.

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

Split the Markdown into chunks of about this many characters for RAG and vector databases. Chunks break between paragraphs and keep their page numbers and section heading. 0 turns chunks off. 1,000 to 2,000 suits most embedding models.

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

Each chunk starts with this many characters from the end of the chunk before it. Used only when chunks are on.

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

Stop after this many PDFs. The default keeps test runs short and cheap. Set 0 for no limit.

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

Read at most this many pages of each PDF, from the first page. The output says when a PDF was cut short. Set 0 for no limit.

## `maxPagesPerRun` (type: `integer`):

Stop the run after this many pages in total. Set 0 for no limit.

## `maxOcrPagesPerRun` (type: `integer`):

OCR at most this many scanned pages in the run. One dense scanned page took about 35 seconds on the platform at 512 MB. It should take about half that at the default 1,024 MB. Further scanned pages are listed as skipped and are not charged. OCR also stops 2 minutes before the run timeout. Set 0 for no limit.

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

Skip files larger than this. Larger files need more memory.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
    }
  ],
  "keyValueStoreKeys": [],
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "extractTables": true,
  "removeHeadersFooters": true,
  "includeText": true,
  "includeMarkdown": true,
  "includePages": false,
  "chunkSize": 0,
  "chunkOverlap": 150,
  "maxItems": 100,
  "maxPagesPerPdf": 500,
  "maxPagesPerRun": 5000,
  "maxOcrPagesPerRun": 50,
  "maxFileSizeMb": 50
}
```

# Actor output Schema

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

No description

## `markdown` (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("maliksaid/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://www.irs.gov/pub/irs-pdf/fw9.pdf" }] }

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

```

## MCP server setup

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