# Document to Text, Markdown & Tables (PDF, Office, OCR) (`genesis.labs.co/document-to-text-markdown-ocr`) Actor

Convert PDF, Word, Excel, PowerPoint, HTML and images to clean Markdown, text and tables with metadata. Free built-in OCR for scans in 29 languages, RAG chunks, $0.30 per 1,000 pages.

- **URL**: https://apify.com/genesis.labs.co/document-to-text-markdown-ocr.md
- **Developed by:** [Nicholas Powers](https://apify.com/genesis.labs.co) (community)
- **Categories:** AI, Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.25 / 1,000 text pages

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

## Document to Text, Markdown & Tables (PDF, Office, OCR)

Convert **PDF, Word (DOCX), Excel (XLSX), PowerPoint (PPTX), HTML, TXT/CSV and images** into clean **Markdown**, plain **text**, **tables (JSON + CSV)** and **metadata**, one page at a time. Scanned pages and images are read with **free, built-in Tesseract OCR in 29 languages**. OCR only runs on pages that have no text layer, so digital PDFs stay fast and cheap. Optional **RAG chunks** with page numbers and heading paths are ready for a vector database.

No API keys, no external OCR service, no browser. You pay per page, and documents that fail are free.

### What it does

| Input | What you get |
|---|---|
| **PDF** (digital) | Text in reading order (works with two-column papers), Markdown headings detected from font size and weight, bullet lists, tables with ruling lines as rows/CSV/Markdown, and metadata (title, author, dates, producer) |
| **PDF** (scanned) | OCR text with paragraphs and a confidence score per page. Mixed PDFs get OCR only on their scanned pages |
| **DOCX** | Headings, lists, bold, tables (merged cells handled), core properties |
| **XLSX** | Every worksheet as a table (dates, numbers and formula values), plus sheet names |
| **PPTX** | One page per slide: title, text boxes in visual order, tables and speaker notes |
| **HTML** | The main content as Markdown (navigation, footers, scripts and cookie banners removed), absolute links and tables |
| **TXT / MD / CSV** | Text, or a table for CSV/TSV (the delimiter is detected) |
| **Images** (PNG, JPG, TIFF incl. multi-page, WebP, BMP, GIF) | OCR text with confidence |

### Where the documents come from

- **URLs.** Paste direct file links. Redirects are followed.
- **A dataset.** Pick a dataset whose items contain file URLs, for example the output of a scraper that collects PDF links. The URL field is detected automatically (`url`, `fileUrl`, `pdfUrl`, `documentUrl`, `downloadUrl`, `link`, `href`, `src`), or you can set **URL field**, which accepts dot paths.
- **A key-value store.** Pick a store with uploaded files and, if you want, list specific record keys.

#### Use it in an integration

Add this Actor under **Integrations → Run another Actor when a run succeeds** on your scraper, and set the input to:

```json
{ "datasetId": "{{resource.defaultDatasetId}}", "urlField": "pdfUrl" }
```

Each time your scraper finishes, every linked document is converted. The Actor runs with **limited permissions**: it can read only the dataset or store you pass in, and it writes only to its own run storage.

### Output

Choose how rows are shaped with **Output rows**:

- **One row per document** (default): `markdown`, `text`, `tables`, `metadata`, a `pages` array, and counts of text and OCR pages.
- **One row per page / slide / sheet**: suited to search indexes and page-level citations.
- **One row per chunk**: overlapping chunks (default 2,000 characters with 200 overlap) that break at paragraph and sentence boundaries. Each chunk carries `pageStart`, `pageEnd` and `headings`, the section path. You can load the rows straight into Pinecone, Qdrant, pgvector and similar stores.

Sample row (document mode, trimmed):

```json
{
  "url": "https://arxiv.org/pdf/1706.03762",
  "fileName": "1706.03762v7.pdf",
  "fileType": "pdf",
  "status": "ok",
  "pageCount": 15,
  "textPages": 15,
  "ocrPages": 0,
  "metadata": { "creator": "LaTeX with hyperref", "producer": "pdfTeX-1.40.25", "createdAt": "2024-04-10T21:11:43" },
  "markdown": "# Attention Is All You Need\n\n...\n\n### Abstract\n\nThe dominant sequence transduction models...",
  "wordCount": 6205,
  "tables": [],
  "pages": [ { "page": 1, "method": "text", "wordCount": 412, "markdown": "..." } ]
}
```

A table, here from IRS Form W-9 page 3:

```json
{ "index": 0, "page": 3, "rowCount": 6, "columnCount": 2,
  "rows": [["IF the entity/individual on line 1 is a(n) . . .", "THEN check the box for . . ."],
           ["• Corporation", "Corporation."], ["• Individual or • Sole proprietorship", "Individual/sole proprietor."]],
  "csv": "IF the entity/individual on line 1 is a(n) . . .,THEN check the box for . . .\n• Corporation,Corporation.\n..." }
```

A scanned page shows `"method": "ocr"` and `"ocrConfidence": 93.9` (the mean Tesseract word confidence, 0 to 100).

The run's **Output** tab links to the results as JSON, an overview table, CSV, Excel, optional `.md` files and a run summary. Very large documents that would exceed the 9 MB dataset item limit are trimmed automatically. The full Markdown is saved as a `.md` file and linked in `markdownFileUrl`.

### Pricing (pay per event)

| Event | Price |
|---|---|
| Document extracted | $0.001 |
| Text page (PDF page with a text layer, slide, sheet, DOCX/HTML/TXT file) | **$0.30 per 1,000 pages** ($0.25 on Gold and higher plans) |
| OCR page (scanned page or image) | **$3.00 per 1,000 pages** ($2.50 on Gold and higher plans) |
| Actor start | Apify's standard start fee ($0.00005 per GB) |

**You are never charged for failed documents.** That includes 404s, blocked or private addresses, password-protected files, unsupported formats, files that are too large and documents with no text. Blank pages are not charged either.

Examples:

- **1,000 digital PDFs, 10 pages each:** 1,000 × $0.001 + 10,000 × $0.0003 = **$4.00**
- **One 300-page report:** $0.001 + 300 × $0.0003 = **$0.09**
- **500 scanned invoices, 2 pages each:** 500 × $0.001 + 1,000 × $0.003 = **$3.50**
- **2,000 receipt photos:** 2,000 × ($0.001 + $0.003) = **$8.00**
- **Mixed 50-page PDF with 5 scanned pages:** $0.001 + 45 × $0.0003 + 5 × $0.003 = **$0.03**

Set **Maximum cost per run** in the run options to cap spending. The Actor checks the budget before every page and stops cleanly when the cap is reached. Unfinished documents are marked `BUDGET_LIMIT` and are not charged.

### Limits and settings

- **Pages**: `pageRange` (e.g. `1-5,8,10-`) and `maxPagesPerDocument` (default 500).
- **Size**: `maxFileSizeMb` (default 50 MB). Larger files fail fast with `FILE_TOO_LARGE`.
- **Time**: `documentTimeoutSecs` (default 600). Pages finished before the timeout are kept and the row is marked `truncated: "timeout"`.
- **OCR**: `auto` (default), `off` or `always`. Pick up to 5 languages and the render DPI (default 200; use 300 for small print).
- **Speed**: the default 2 GB of memory handles about 2 documents in parallel. For big batches or long PDFs, use 4 to 8 GB.
- **Proxy**: off by default. Turn it on for sites that block data-center IPs or are geo-restricted.

### Error codes

Every failed document produces a row with `status: "failed"`, an `errorCode` and a readable `errorMessage`:

| Code | Meaning |
|---|---|
| `INVALID_URL` | Malformed URL, a non-http(s) scheme or embedded credentials |
| `BLOCKED_ADDRESS` | The host resolves to a private, loopback, link-local or metadata address (SSRF protection; every redirect hop is checked) |
| `DNS_FAILED` | The host name could not be resolved |
| `HTTP_ERROR` | The server returned 4xx or 5xx |
| `DOWNLOAD_FAILED` / `TIMEOUT` | Network failure or timeout |
| `FILE_TOO_LARGE` / `EMPTY_FILE` | Over `maxFileSizeMb`, or zero bytes |
| `UNSUPPORTED_FORMAT` | Not a supported type. This includes legacy .doc/.xls/.ppt, and login or error pages served instead of the file |
| `ENCRYPTED` | Password-protected PDF |
| `CORRUPT_FILE` | The file is damaged or truncated |
| `NO_TEXT_FOUND` | No text layer, and OCR was off or found nothing |
| `RECORD_NOT_FOUND` / `ACCESS_DENIED` | Problem with the key-value store record or storage access |
| `BUDGET_LIMIT` | Skipped because the run hit its maximum cost |

### Good to know

- PDF tables are detected from their ruling lines. Borderless tables, common in academic papers, stay as readable text. Diagrams that only look like grids are filtered out.
- Text that is rotated in the margins (arXiv stamps, form sidebars) is skipped so it does not break sentences.
- OCR quality depends on the scan. Check `ocrConfidence`: above 85 is usually very clean. Handwriting is not supported.
- DOCX files have no fixed pages, so the whole document counts as page 1 and is charged as one page.
- Downloads are capped and checked against private networks, and nothing is sent to third-party services. Files are processed inside the run and are not kept beyond your run storage.

### FAQ

**Can it read scanned PDFs?** Yes. Pages without a text layer go through OCR automatically. Pages that already have text are never OCR'd unless you choose `always`.

**Which languages?** English, German, French, Spanish, Italian, Portuguese, Dutch, Polish, Czech, Swedish, Danish, Norwegian, Finnish, Romanian, Hungarian, Turkish, Russian, Ukrainian, Greek, Arabic, Hebrew, Hindi, Chinese (Simplified and Traditional), Japanese, Korean, Vietnamese, Indonesian and Thai. Text-layer extraction works for any language.

**Can I call it from code or an AI agent?** Yes, through the Apify API, the Python or JavaScript client, or the Apify MCP server. Use the `chunk` mode for RAG pipelines.

**Is the output good for LLMs?** The Markdown keeps headings, lists and tables, which helps chunking and retrieval. Each chunk records the pages it came from, so answers can cite pages.

# Actor input Schema

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

Direct links to PDF, DOCX, XLSX, PPTX, HTML, TXT, CSV or image files (PNG, JPG, TIFF, WebP). One per line.

## `datasetId` (type: `string`):

Process file URLs found in a dataset, e.g. the output of a scraper. In integrations set it to {{resource.defaultDatasetId}}. URLs are read from "URL field" or auto-detected (url, fileUrl, pdfUrl, documentUrl, downloadUrl, link, href, src).

## `urlField` (type: `string`):

Field in the dataset items that holds the file URL. Dot paths work (e.g. attachment.url). Leave empty to auto-detect.

## `keyValueStoreId` (type: `string`):

Process files uploaded to an Apify key-value store (e.g. files saved by another Actor).

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

Keys of the records to process. Leave empty to process every record in the store (except INPUT/OUTPUT).

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

Document: full Markdown, text, tables and metadata per file. Page: one row per page. Chunk: overlapping text chunks with page numbers and heading path, ready for a vector database.

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

Target chunk length. 0 = no chunks (in "chunk" output mode the default is 2000). Chunks break at paragraph and sentence boundaries.

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

How much text each chunk repeats from the previous one (default 200). Must be less than half the chunk size.

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

Return tables as rows (JSON) and CSV, and render them as Markdown tables. PDF tables need visible ruling lines; borderless tables stay as text.

## `includePlainText` (type: `boolean`):

Add a plain-text version next to the Markdown.

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

In document mode, add a pages array with text, Markdown, extraction method and OCR confidence for each page.

## `saveMarkdownFiles` (type: `boolean`):

Also save each document's Markdown as a .md file in the run's key-value store and link it in the row.

## `ocr` (type: `string`):

Auto runs Tesseract OCR only on scanned pages and images, so digital PDFs stay fast and cheap. Images always need OCR.

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

Languages in your scans (up to 5). Add every language that appears; each extra one slows OCR a little.

## `ocrDpi` (type: `integer`):

Resolution used to render scanned PDF pages for OCR. 200 is a good balance; use 300 for small print.

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

Only these pages (or slides/sheets), e.g. 1-5,8,10-. Leave empty for all.

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

Stop after this many pages of one document; the row is marked truncated.

## `maxDocuments` (type: `integer`):

Maximum number of documents to process in this run.

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

Larger files are rejected with FILE_TOO_LARGE (not charged).

## `documentTimeoutSecs` (type: `integer`):

Pages finished before the timeout are kept (row marked truncated: timeout). Large or complex PDFs (hundreds of pages) may need more memory or a longer timeout.

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

0 = automatic (1 per GB of memory, max 8).

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

Optional. Use a proxy for sites that block data-center IPs or are geo-restricted (e.g. some government sites). Off by default.

## Actor input object example

```json
{
  "urls": [
    "https://arxiv.org/pdf/1706.03762"
  ],
  "outputMode": "document",
  "extractTables": true,
  "includePlainText": true,
  "includePages": true,
  "saveMarkdownFiles": false,
  "ocr": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "ocrDpi": 200,
  "maxPagesPerDocument": 500,
  "maxDocuments": 1000,
  "maxFileSizeMb": 50,
  "documentTimeoutSecs": 600,
  "maxConcurrency": 0,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

All extracted documents, pages or chunks.

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

Status, type and page counts per document.

## `csv` (type: `string`):

Results as CSV.

## `xlsx` (type: `string`):

Results as an Excel file.

## `files` (type: `string`):

Saved .md files (when Save .md files is on, or for very large documents).

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

Documents ok/failed, text and OCR page counts, error codes.

# 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://arxiv.org/pdf/1706.03762"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("genesis.labs.co/document-to-text-markdown-ocr").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://arxiv.org/pdf/1706.03762"] }

# Run the Actor and wait for it to finish
run = client.actor("genesis.labs.co/document-to-text-markdown-ocr").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://arxiv.org/pdf/1706.03762"
  ]
}' |
apify call genesis.labs.co/document-to-text-markdown-ocr --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,genesis.labs.co/document-to-text-markdown-ocr"
        }
    }
}
```

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/iCTOVuHWnzqfGI3Ft/builds/Ms6vPb8zxdLFEV8D0/openapi.json
