# Document Extractor: PDF, Word & Image to Markdown (OCR) (`cluuny/document-extractor`) Actor

Document extractor that converts PDF, DOCX and image files into clean text, Markdown or tables, with OCR for scanned pages. One row per document, paid per page processed. Ideal for RAG pipelines and AI agents.

- **URL**: https://apify.com/cluuny/document-extractor.md
- **Developed by:** [Vícente Matallana](https://apify.com/cluuny) (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.00 / 1,000 page processeds

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, Word & Image to Text, Markdown & Tables (OCR)

Turn PDF, DOCX and image files into clean text, Markdown or tables, with OCR for scanned pages. Give it a list of file URLs and get one row per document, ready for a RAG pipeline, an AI agent, a spreadsheet or a database.

### What it does

- Downloads each file from its URL and detects the type from the content, not the file name: PDF, DOCX (Word) or image (PNG, JPEG, TIFF, BMP, GIF, WebP).
- **Digital PDFs:** reads the text layer and detects tables, page by page.
- **Scanned PDFs and images:** reads the text with OCR (Tesseract), in English, Spanish or both. A PDF that mixes digital and scanned pages is handled page by page.
- **Word documents:** keeps headings, bulleted and numbered lists and tables, so the Markdown mirrors the document's structure.
- Returns, for each document, its `url`, `fileType`, `pageCount`, `pagesProcessed` and `ocrPages`, the content in the output mode you choose (`text` or `markdown`), every table as rows of cells in `tables`, and the file's `metadata` (title, creation and modification dates). It never returns the author or any other personal field from the file properties.
- A file that cannot be processed (broken link, unsupported type, corrupted or password-protected file) gets a row with an `error` message and is not charged; the rest of the run continues.

### Input example

```json
{
    "urls": [
        "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
        "https://example.com/reports/sales_report.pdf"
    ],
    "outputMode": "markdown",
    "maxPagesPerDocument": 100,
    "ocrLanguages": ["eng"]
}
```

- `urls` (required): absolute http(s) URLs of the files.
- `outputMode`: `markdown` (default), `text` or `tables`. Tables come back in every mode; `tables` returns only them.
- `maxPagesPerDocument`: process at most this many pages of each file, from the first one (1 to 1,000, default 100).
- `ocrLanguages`: languages of the scanned text, `eng` and/or `spa` (default `eng`).

### Output example

One row per URL, in the same order as the input:

```json
{
    "url": "https://example.com/reports/sales_report.pdf",
    "fileType": "pdf",
    "pageCount": 3,
    "pagesProcessed": 3,
    "ocrPages": 0,
    "text": null,
    "markdown": "Quarterly Sales Summary\n\n| Region | Units | Revenue |\n|---|---|---|\n| North | 120 | 4800 |\n| South | 95 | 3800 |\n| West | 60 | 2400 |\n\nSecond page. Sales grew in every region during the quarter.\n\nThird page. Figures are in US dollars.",
    "tables": [
        {
            "page": 1,
            "rows": [["Region", "Units", "Revenue"], ["North", "120", "4800"], ["South", "95", "3800"], ["West", "60", "2400"]]
        }
    ],
    "metadata": {
        "title": "Quarterly Sales Summary",
        "createdAt": "2026-01-15T09:30:00Z",
        "modifiedAt": null
    },
    "error": null
}
```

A failed URL looks like this:

```json
{
    "url": "https://example.com/reports/missing.pdf",
    "fileType": null,
    "pageCount": null,
    "pagesProcessed": null,
    "ocrPages": null,
    "text": null,
    "markdown": null,
    "tables": null,
    "metadata": null,
    "error": "Download failed: HTTP 404"
}
```

How pages are counted: a PDF page is a page; each image is one page (each frame of a multi-page TIFF counts; an animated GIF is one page); a DOCX has the pages Word last laid out, or one page per 3,000 characters when the file has no layout information (for example, files generated by a script). When a page limit applies, `pagesProcessed` is lower than `pageCount`.

Export the dataset as JSON, CSV, Excel or HTML, or read the **Overview** and **Tables** views in the Console.

### Pricing

This Actor uses pay-per-event pricing. You pay only for pages processed successfully, and platform usage is included in the price. See the **Pricing** tab for the current price per event.

| Event | When it is charged |
|---|---|
| `page-processed` | Once per page extracted from a document that returns a row without `error`, OCR included |

Files that fail are not charged. Pages beyond `maxPagesPerDocument` are not read and not charged. If you set a maximum charge for the run, the Actor processes only the pages that fit in it, keeps every row produced so far and stops cleanly.

### Free plan limits

Users on the Apify Free plan can process up to **10 documents per run** and **20 pages per document**. The Actor processes the first 10 documents and the first 20 pages of each, saves them and finishes with a clear message. Paid plans have no per-run document limit and use `maxPagesPerDocument` (up to 1,000 pages).

### Use cases

- **RAG and AI agents:** convert PDFs and Word files into Markdown that keeps headings and tables before chunking and embedding them.
- **Scanned archives:** get searchable text out of scanned contracts, invoices and forms with OCR.
- **Table extraction:** pull tables out of financial reports and price lists straight into rows for a spreadsheet or database.
- **Document pipelines:** schedule runs or call the Actor from Make, Zapier, n8n or the API whenever new files land in storage.
- **Spanish documents:** OCR in Spanish, with accents and ñ, for Latin American and Spanish paperwork.

### Comparison with alternatives

| Option | Good for | Trade-off |
|---|---|---|
| This Actor | Batches of mixed PDF, DOCX and image files from the Console, the API, a schedule or an AI agent, paid per page | OCR handles printed text, not handwriting; layout of complex multi-column pages is simplified |
| Online PDF converters | Converting one file by hand | One file at a time, no API, no Markdown or tables as data |
| Cloud document AI services | Forms, handwriting and key-value extraction | Need an account, credentials and setup, and are priced for enterprise volumes |
| Your own script with open-source libraries | Full control | You host it, install OCR and maintain the edge cases of every file type |

You are responsible for having the right to process the documents you send. The Actor stores nothing outside the run's dataset.

# Actor input Schema

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

Absolute http(s) URLs of PDF, DOCX or image files (PNG, JPEG, TIFF, BMP, GIF, WebP). The Actor returns one dataset row per URL, in the same order. A file that fails gets a row with an error and does not stop the run.

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

What to return for each document. 'markdown': Markdown with headings, lists and tables, best for LLMs and RAG. 'text': plain text. 'tables': only the tables found, as rows of cells. Tables are returned in every mode.

## `maxPagesPerDocument` (type: `integer`):

Process at most this many pages of each document, starting from the first. You pay only for pages processed. On the Apify Free plan the Actor processes at most 20 pages per document.

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

Languages of the text in scanned pages and images, used by OCR. Pick every language present in the documents.

## Actor input object example

```json
{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ],
  "outputMode": "markdown",
  "maxPagesPerDocument": 100,
  "ocrLanguages": [
    "eng"
  ]
}
```

# Actor output Schema

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

No description

## `tables` (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.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("cluuny/document-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.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("cluuny/document-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.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call cluuny/document-extractor --silent --output-dataset

```

## MCP server setup

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