# PDF to Markdown, Text & JSON Tables - OCR for Scans & Images (`jeanswiegers/pdf-image-to-markdown`) Actor

Convert PDFs and images (PNG, JPG, TIFF, WebP) to clean Markdown, plain text and JSON tables, page by page. Uses the PDF text layer first, Tesseract OCR for scans. 7 languages. Ideal for RAG, LLM and data-entry pipelines. Files are never stored.

- **URL**: https://apify.com/jeanswiegers/pdf-image-to-markdown.md
- **Developed by:** [Jean Swiegers](https://apify.com/jeanswiegers) (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

$4.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.

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 Markdown, Text & JSON Tables – OCR for Scanned PDFs and Images

**Convert PDFs, scanned documents and images (PNG, JPEG, TIFF, WebP) to clean Markdown, plain text and JSON tables, one row per page.** Digital PDFs are read from their embedded text layer (fast and exact), and pages without text – scans, faxes, photos of documents – automatically go through **Tesseract OCR** in 7 languages. Detected tables come back both as **Markdown tables** and as **JSON arrays of rows**. Built for RAG pipelines, LLM prompts, vector databases, spreadsheets and data-entry automation. **$0.004 per page, text layer or OCR, no start fee – you only pay for pages processed successfully.**

### What you get

One dataset row per page (an image file is one page):

| Field | What it contains |
|---|---|
| `fileUrl`, `fileName` | Source file URL and file name |
| `fileType` | `pdf`, `png`, `jpeg`, `tiff` or `webp` (detected from file contents, not the extension) |
| `page`, `pageCount` | Page number (1-based) and total pages in the file |
| `status` | `succeeded` or `failed` |
| `method` | `text-layer` or `ocr` |
| `ocrConfidence` | Average OCR word confidence 0–100 (OCR pages only, otherwise `null`) |
| `markdown` | Page content as Markdown, with tables as Markdown tables |
| `text` | Plain text (when `includePlainText` is on) |
| `tables`, `tableCount` | Detected tables as `[{ "rows": [["cell", …], …] }]` and how many there are |
| `charCount`, `wordCount`, `isEmpty` | Size of the extracted text; `isEmpty` is `true` if no text was found |
| `durationMs` | Processing time for the page |
| `error` | `null` on success, otherwise `{ code, message }` |

Dataset views in Apify Console: **Pages** (overview) and **Tables** (one row per table). A `PAGES_PROCESSED` record and a `RUN_SUMMARY` record in the key-value store hold the run totals.

### How much does it cost?

**$0.004 per page** – the same price whether the page is read from the text layer or with OCR. An image file counts as one page. You only pay for successful results: failed files and failed pages are free, there is no start fee, and there are no extra platform usage charges.

| Pages | Price |
|---|---|
| 100 | $0.40 |
| 1,000 | $4.00 |
| 10,000 | $40.00 |

Use `pageRange`, `maxPagesPerFile` and a maximum cost per run in Apify Console to keep spend predictable. The Actor stops processing new pages when the maximum is reached.

### Use cases

- 🤖 **RAG & AI agents** – turn document libraries into per-page Markdown for embeddings and retrieval.
- 🧾 **Invoices, statements & reports** – pull tables of figures into JSON or CSV.
- 🗂️ **Digitize scanned archives** – get searchable text from scanned PDFs, faxes and photos of documents.
- 📊 **Data extraction** – send extracted tables to Google Sheets, Airtable or your database via integrations.
- 🔍 **Compliance & search** – index contracts and policies.

### How to convert PDF to Markdown

1. Add direct links to your files in **File URLs** – public URLs, pre-signed S3 or Google Drive download links, or Apify key-value store record URLs.
2. Leave **OCR mode** on `auto` (text layer first, OCR only for pages without text), and pick the OCR languages in your documents.
3. Click **Start**, then download the results as JSON, CSV or Excel, or fetch them via the API.

#### Example input

```json
{
  "fileUrls": [
    { "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" },
    { "url": "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png" }
  ],
  "ocrMode": "auto",
  "ocrLanguages": ["eng", "deu"],
  "pageRange": "1-10",
  "maxPagesPerFile": 50,
  "includePlainText": true
}
```

All input options:

| Field | Default | What it does |
|---|---|---|
| `fileUrls` | a sample PDF and a sample image | Direct links to PDF, PNG, JPEG, TIFF or WebP files |
| `ocrMode` | `auto` | `auto` (OCR only pages without text), `always` (OCR every page) or `never` |
| `ocrLanguages` | `["eng"]` | `eng`, `deu`, `fra`, `spa`, `por`, `ita`, `nld`. More languages = slower OCR |
| `pageRange` | all pages | PDF pages to process, e.g. `1-3,5,10-` |
| `maxPagesPerFile` | `50` | Safety limit per file (1–2,000) |
| `includePlainText` | `true` | Add a plain-text field next to the Markdown |
| `ocrDpi` | `200` | Rendering resolution for OCR of scanned PDF pages (72–400) |
| `maxFileSizeMb` | `50` | Larger files are rejected (1–200 MB) |
| `maxConcurrency` | `2` | Files processed in parallel (1–8) |

#### Example output

A scanned (image-only) PDF page with a table, read with OCR in a test run:

```json
{
  "fileUrl": "https://example.com/scanned-report.pdf",
  "fileName": "scanned.pdf",
  "fileType": "pdf",
  "page": 1,
  "pageCount": 1,
  "status": "succeeded",
  "method": "ocr",
  "ocrConfidence": 94,
  "markdown": "Quarterly Report 2026\n\nRevenue grew strongly across all regions. The table below summarises results.\n\n| Region | Q1 Revenue | Q2 Revenue | Growth |\n| --- | --- | --- | --- |\n| Gauteng | R 1,200,000 | R1,450,000 | 20.8% |\n| Western Cape | R 980,000 | R 1,010,000 | 3.1% |\n| KwaZulu-Natal | R 640,000 | R 702,000 | 9.7% |\n| Eastern Cape | R 310,000 | R 355,000 | 14.5% |",
  "tables": [
    {
      "rows": [
        ["Region", "Q1 Revenue", "Q2 Revenue", "Growth"],
        ["Gauteng", "R 1,200,000", "R1,450,000", "20.8%"],
        ["Western Cape", "R 980,000", "R 1,010,000", "3.1%"],
        ["KwaZulu-Natal", "R 640,000", "R 702,000", "9.7%"],
        ["Eastern Cape", "R 310,000", "R 355,000", "14.5%"]
      ]
    }
  ],
  "tableCount": 1,
  "wordCount": 45
}
```

Failed files and pages get `"status": "failed"` and an `error` object with a `code` and a `message` (for example a download error, an unsupported file type or a password-protected PDF).

### Privacy

Files are downloaded to the run's temporary disk, processed and deleted straight away. Only the extracted output is saved, in your own dataset.

### FAQ

**Does it work on scanned PDFs?** Yes. With `ocrMode: auto`, any page with no usable text layer is rendered and read with Tesseract OCR. Pages that already have text are not OCR'd.

**How accurate is OCR?** Tesseract works well on clean scans at 200–300 DPI. Low-quality photos, handwriting and unusual fonts reduce accuracy – check `ocrConfidence` on each page.

**How fast is it?** In our test runs a 30-page text PDF took about 5 seconds, while OCR of a 10-page scanned paper (`ocrMode: always`) took about 3 minutes at the default 1 GB memory. OCR is the slow part.

**Are complex tables supported?** Simple and medium tables (aligned columns, with or without borders) are rebuilt well. Merged cells and nested tables may be flattened. The full text is always returned, so no content is lost.

**Can it read password-protected PDFs?** No – the file fails with a clear error and is not charged.

**Can it read Word, Excel or HTML files?** No. Supported inputs are PDF, PNG, JPEG, TIFF and WebP.

**How do I process files from Google Drive or S3?** Use a direct download link or a pre-signed URL.

**Something broke?** Open an issue on the Actor's Issues tab with the run ID.

# Changelog

This Actor's version history is a separate document: https://apify.com/jeanswiegers/pdf-image-to-markdown/changelog.md

# Actor input Schema

## `fileUrls` (type: `array`):

Direct links to PDF, PNG, JPEG, TIFF or WebP files (public URLs, signed S3/Drive links, or Apify key-value store record URLs). The type is detected from file contents.

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

'auto' uses the PDF's embedded text and OCRs only pages without text (scans). 'always' OCRs every page. 'never' skips OCR (images will be empty).

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

Languages present in scanned documents. More languages = slower OCR.

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

Optional PDF pages to process, e.g. '1-3,5,10-'. Empty = all pages (up to the max below).

## `maxPagesPerFile` (type: `integer`):

Safety limit to control cost.

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

Add a plain-text field next to the Markdown.

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

Rendering resolution for OCR of scanned PDF pages. 200-300 is best for small print.

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

Files larger than this are rejected.

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

Files processed in parallel.

## Actor input object example

```json
{
  "fileUrls": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    },
    {
      "url": "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
    }
  ],
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "maxPagesPerFile": 50,
  "includePlainText": true,
  "ocrDpi": 200,
  "maxFileSizeMb": 50,
  "maxConcurrency": 2
}
```

# Actor output Schema

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

One item per page: Markdown, plain text, detected tables as JSON rows, extraction method and OCR confidence.

## `tables` (type: `string`):

Detected tables, one row per table.

## `runSummary` (type: `string`):

Totals of succeeded and failed files.

# 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 = {
    "fileUrls": [
        {
            "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
        },
        {
            "url": "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("jeanswiegers/pdf-image-to-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 = { "fileUrls": [
        { "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" },
        { "url": "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("jeanswiegers/pdf-image-to-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 '{
  "fileUrls": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    },
    {
      "url": "https://raw.githubusercontent.com/tesseract-ocr/tessdoc/main/images/eurotext.png"
    }
  ]
}' |
apify call jeanswiegers/pdf-image-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,jeanswiegers/pdf-image-to-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/4cy7OJV9Xyk6zTgR9/builds/JEr4VVfNd8M1UoDu4/openapi.json
