# PDF & Office to Markdown: Tables, Headings, OCR Fallback (`foresighted_sunset/pdf-office-to-markdown`) Actor

Convert PDFs, DOCX, PPTX and images to clean markdown for RAG: real tables, heading levels, page anchors, per-page token counts. Tesseract OCR runs only on pages without a text layer. Input from URLs, key-value store files or a dataset of links. Per-file error codes.

- **URL**: https://apify.com/foresighted_sunset/pdf-office-to-markdown.md
- **Developed by:** [Dano Desk](https://apify.com/foresighted_sunset) (community)
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.50 / 1,000 pages

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 & Office to Markdown: Tables, Headings, OCR Fallback

Turn PDFs, Word (DOCX), PowerPoint (PPTX) and images into clean markdown for RAG pipelines and LLMs:

- **Real tables**: ruled PDF tables become markdown tables (merged-cell artefacts removed, pipes escaped), with page and bounding box.
- **Heading levels**: `#`/`##`/`###` from the PDF's font-size hierarchy and bold section titles; DOCX heading styles; one `## Slide N: title` per slide.
- **Page anchors for citations**: `<a id="page-7"></a>` before each page (or HTML comments, `## Page N`, or none).
- **Per-page token counts** (tiktoken `cl100k_base` or `o200k_base`) to plan chunking.
- **OCR only where needed**: Tesseract runs only on pages without a text layer (scans), and on image files. In default `auto` mode, native-text pages are not OCR'd; `force` explicitly OCRs all selected pages.
- **Clean text**: repeated headers/footers and page numbers removed, hyphenated line breaks rejoined (real compounds like *high-level* kept), bullet glyphs turned into lists.
- **Inputs**: URLs, files in Apify key-value stores, or a dataset of links (for example from a crawler).
- **Robust batches**: each file gets its own error code; partial successes are retained. An all-file failure or billing-contract/limit failure can fail the run.

### Quick start

```json
{"urls":["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"],"pageRange":"1","outputMode":"pages","ocrMode":"auto"}
```

Read paid native pages from the dataset; open `OUTPUT.files[].resultUrl` for document summaries, OCR output and errors.

### Input

| Field | What it does |
|---|---|
| `urls` | Public document URLs (PDF, DOCX, PPTX, PNG/JPEG/TIFF/GIF/BMP/WEBP). |
| `keyValueStoreId` + `keyValueStoreKeys` | Pick one of your key-value stores and list the file keys. The Actor runs with limited permissions and gets read-only access to that store only. |
| `keyValueStoreRecords` | Advanced: `storeIdOrName/key` for stores this Actor created, or `key` for the run's own store. |
| `datasetId` + `datasetUrlField` | Read URLs from a dataset (read-only access to that dataset only); dotted paths like `file.url` work. |
| `pageRange` | PDF pages to convert, e.g. `1-5,9,12-`. |
| `tableStrategy` | `lines` (ruled tables, default), `text` (also whitespace tables), `off`. |
| `ocrMode` | `auto` (default), `force`, `off`. `ocrLanguages`: eng, deu, fra, spa, ita, por, nld. |
| `outputMode` | Controls **KV result records**: `document` = document summary/full markdown; `pages` = summary + page rows. Default dataset always holds only nonempty native-text pages. |
| `pageMarker` | `anchor`, `comment`, `heading`, `none`. |
| `pdfPassword` | Secret. Tried for encrypted PDFs; never written to output. |
| Limits | `maxFiles` 100, `maxFileSizeMb` 50, `maxPagesPerFile` 300, `maxOcrPagesPerFile` 30, `ocrDpi` 200, timeouts. |

**Empty input runs a demo**: pages 1-3 and 29-30 of the NIST Cybersecurity Framework 2.0 PDF (a U.S. government work) plus a small DOCX, PPTX and scanned-style PNG generated by the Actor.

### Output

Complete **KV result-record** rows (`recordType`):

- `document`: source, fileName, fileType, status (`ok`/`partial`), title, totalPages, pagesConverted, nativePages, ocrPages, emptyPages, tableCount, headingCount, tokens, `pages[]` (page, anchor, method `text`/`ocr`/`empty`/`error`, tokens, chars, tables, ocrConfidence), `tables[]` (page, bbox, rows, cols), markdown, markdownUrl, warnings.
- `page` (outputMode `pages`): one row per page with its markdown and token count.
- `error`: source, errorCode, message (and httpStatus for HTTP errors).

The **default dataset contains only nonempty native-text `page` rows** (`method: "text"`), independent of `outputMode`. Each paid row includes `resultKey` and `resultUrl` pointing to the complete KV result. OCR pages, document summaries, empty pages and errors do **not** become default-dataset rows.

Open `OUTPUT` in the key-value store: each `files[]` entry has its status and `resultKey` / `resultUrl`. The referenced `result-...` JSON record is `{ "outputMode": "document" | "pages", "rows": [...], "pages": [...] }`; it retains the chosen document/page output plus all converted page details, including OCR content and diagnostic/error rows. Store/API access requires permission to the run's KV store. `OUTPUT.counts` records file, native/OCR-page, token and table totals; `errorCodes` and `warnings` expose partial failures and caps.

When `saveMarkdownFiles` is true (default), complete document markdown is also saved as an `.md` KV record. Its `markdownKey`/`markdownUrl` is available in the document summary, and `markdownKey` is included in the corresponding `OUTPUT.files` entry. Dataset table views show native pages and their markdown only; use KV result records for document/OCR/error review.

### Pricing

| Live event | Billable unit | USD |
|---|---|---:|
| `apify-default-dataset-item` | One nonempty native-text page delivered to the default dataset | $0.0005 |
| `scanned-page` | One successfully converted nonempty OCR page, with content retained in KV | $0.003 |
| `apify-actor-start` | Automatic start event per started GB (minimum one), including empty/error runs | $0.00005 |

Native and OCR page events are **mutually exclusive** for a page: OCR content never also incurs the native dataset-page charge. Document/page-summary/error rows and empty pages have no page-event charge. 1,000 native pages cost **$0.50** in page events; 1,000 OCR pages cost **$3.00**, plus the standard start fee. DOCX page units are explicit-page-break sections and PPTX page units are slides.

Set Apify's `maxTotalChargeUsd` run option and input page/file limits. When an event budget is exhausted, further paid native-page delivery or OCR charging stops; already-produced KV artifacts can remain. This README/schema patch does not change runtime billing or prices.

#### Error codes

Per file: `INVALID_URL`, `BLOCKED_URL` (private/loopback hosts), `DOWNLOAD_HTTP_ERROR`, `DOWNLOAD_TIMEOUT`, `DOWNLOAD_FAILED`, `FILE_TOO_LARGE`, `EMPTY_FILE`, `KV_RECORD_NOT_FOUND`, `DATASET_NOT_FOUND`, `UNSUPPORTED_TYPE` (legacy .doc/.ppt, XLSX, ODT, HTML pages...), `ENCRYPTED_PDF`, `CORRUPT_FILE`, `OCR_FAILED`, `OCR_UNAVAILABLE`, `NO_TEXT`, `CONVERSION_FAILED`.
Page-level warnings: `PAGE_LIMIT`, `OCR_PAGE_LIMIT`, `NO_TEXT_LAYER`.
Run level: `INVALID_INPUT` and `ALL_FILES_FAILED`; billing-contract/limit or unexpected processing failures can also fail a run. A billing-contract mismatch is rejected before file processing.

### Limits and honest caveats

- Multi-column PDFs are read line by line top-to-bottom, so two-column papers can interleave columns.
- Borderless tables need `tableStrategy: text`, which can also over-detect; check the output.
- Formulas, images and charts are not described; text inside images in a *text* PDF is not OCR'd (only pages with no text layer are).
- DOCX has no fixed pages; its "pages" are sections split at explicit page breaks. PPTX pages are slides.
- OCR quality depends on scan quality; `ocrConfidence` (0-100) is reported per page.

### Licences

Built only on permissive open-source components (MIT/BSD/Apache-2.0). No AGPL code (PyMuPDF is deliberately not used). See [THIRD_PARTY_NOTICES.md](THIRD_PARTY_NOTICES.md).

# Actor input Schema

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

Public links to PDF, DOCX, PPTX or image files (PNG, JPEG, TIFF, GIF, BMP, WEBP). Apify storage record URLs (api.apify.com) work too. Leave everything empty to run a small demo (5 pages of the NIST CSF 2.0 PDF plus built-in DOCX, PPTX and PNG samples).

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

ID or name of one of your key-value stores holding the files. Selecting it here grants this Actor read-only access to that store (and nothing else in your account). List the record keys below.

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

Keys of the file records in the key-value store above (e.g. 'contract.pdf'). Without a store, keys are read from this run's own default store.

## `keyValueStoreRecords` (type: `array`):

Advanced: 'storeIdOrName/key' references. Only stores created by this Actor (or this run's default store, using just 'key') are readable; for your own stores use the two fields above.

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

ID or name of a dataset whose items contain document URLs (for example the output of a crawler). Grants this Actor read-only access to that dataset only.

## `datasetUrlField` (type: `string`):

Field holding the URL. Dotted paths work, e.g. 'file.url'.

## `maxFiles` (type: `integer`):

Stop after this many files (1-1000).

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

Only convert these pages of each PDF, e.g. '1-5,9,12-'. Empty = all pages.

## `tableStrategy` (type: `string`):

lines = ruled tables (most reliable). text = also whitespace-aligned tables (can over-detect). off = plain text only.

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

Drops lines repeated in the top/bottom margin of at least half the pages (PDFs with 3+ pages).

## `pageMarker` (type: `string`):

anchor = invisible <a id="page-N"></a> link targets (good for citations). comment = . heading = '## Page N'. none = no markers.

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

Controls KV result-\*.json rows: document = document summary/full markdown; pages = summary plus page rows. The default dataset always holds only nonempty native-text pages. OCR pages, summaries and errors are KV-only; OUTPUT.files lists resultKey/resultUrl.

## `tokenEncoding` (type: `string`):

tiktoken encoding used for per-page token counts.

## `includeSpeakerNotes` (type: `boolean`):

Add each slide's speaker notes as a quote block.

## `keepInlineFormatting` (type: `boolean`):

Keep **bold** and *italic* runs from Word documents.

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

Stores the full markdown of each file as a record (link in markdownUrl).

## `maxMarkdownCharsInDataset` (type: `integer`):

Maximum markdown characters embedded in each KV document-summary row (legacy input name retained). Complete markdown is available in the .md KV record when saveMarkdownFiles is enabled; default-dataset native page markdown is unchanged.

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

auto = OCR only pages without a text layer, and image files. force = OCR every PDF page. off = never OCR.

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

Tesseract language codes. Installed: eng, deu, fra, spa, ita, por, nld. Up to 5.

## `minTextChars` (type: `integer`):

Pages with fewer non-space characters than this are OCR'd in auto mode.

## `maxOcrPagesPerFile` (type: `integer`):

Cap on OCR'd pages per file (OCR is slower and billed separately). 0 = no OCR on PDFs.

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

Render resolution for OCR. 200 is a good balance; 300 for small print.

## `ocrTimeoutSecs` (type: `integer`):

Give up OCR on a page after this many seconds (page gets an OCR_FAILED warning).

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

Password for encrypted PDFs (tried on every PDF). Stored encrypted; never written to the output.

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

Larger files fail with FILE_TOO_LARGE before they are fully downloaded.

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

Pages (or slides) converted per file; the rest are skipped with a PAGE_LIMIT warning.

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

Per-download timeout; 5xx/429 are retried twice.

## Actor input object example

```json
{
  "datasetUrlField": "url",
  "maxFiles": 100,
  "tableStrategy": "lines",
  "removeHeadersFooters": true,
  "pageMarker": "anchor",
  "outputMode": "document",
  "tokenEncoding": "cl100k_base",
  "includeSpeakerNotes": true,
  "keepInlineFormatting": true,
  "saveMarkdownFiles": true,
  "maxMarkdownCharsInDataset": 1000000,
  "ocrMode": "auto",
  "ocrLanguages": [
    "eng"
  ],
  "minTextChars": 20,
  "maxOcrPagesPerFile": 30,
  "ocrDpi": 200,
  "ocrTimeoutSecs": 60,
  "maxFileSizeMb": 50,
  "maxPagesPerFile": 300,
  "requestTimeoutSecs": 60
}
```

# Actor output Schema

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

No description

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

No description

## `markdownFiles` (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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("foresighted_sunset/pdf-office-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("foresighted_sunset/pdf-office-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 '{}' |
apify call foresighted_sunset/pdf-office-to-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,foresighted_sunset/pdf-office-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/Ew3aAd5zNis0k5k9h/builds/B9v4TD3g3fI5evKEg/openapi.json
