# PDF, DOCX & PPTX to Markdown Converter for AI & RAG (`fedsignals/documents-to-markdown`) Actor

PDF to Markdown, DOCX to Markdown and PPTX to Markdown converter for AI and RAG: turns PDF, Word, PowerPoint and HTML files into clean Markdown with tables kept as Markdown tables, page and slide numbers, metadata and optional RAG chunks. Optional OCR for scanned PDFs. URLs or file upload.

- **URL**: https://apify.com/fedsignals/documents-to-markdown.md
- **Developed by:** [FedSignals Data](https://apify.com/fedsignals) (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 $5.00 / 1,000 converted documents

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, DOCX & PPTX to Markdown Converter for AI & RAG

**PDF to Markdown, DOCX to Markdown and PPTX to Markdown in one Actor.** Give it links to documents (or upload a file) and get back **clean, AI-ready Markdown**: headings, lists and **tables preserved as Markdown tables**, **page and slide numbers** kept as `<!-- page: N -->` (PowerPoint: `<!-- slide: N -->`) markers, **document metadata** (title, subject, creation and modification dates, page count), and optional **RAG chunks** with configurable size and overlap, each tagged with its page range and heading path. Scanned PDFs can be read with **optional OCR**.

It's built for **RAG pipelines, LLM ingestion, knowledge bases, search indexing and document analysis**. Feed the chunk rows straight into a vector database, or download the full `.md` files.

**Supported formats**

| format | how it is read | "page" means |
|---|---|---|
| **PDF** | text layer with layout-aware headings (font size), paragraph and hyphenation repair, ruled tables as Markdown tables; rotated margin text is dropped | one PDF page |
| **PDF, scanned** | OCR with Tesseract (optional, `ocr: auto` or `always`) | one PDF page |
| **DOCX** (Word) | headings, lists, tables, links | 3,000 characters of output (estimated page) |
| **PPTX** (PowerPoint) | slide titles, text, tables, notes | one slide |
| **HTML** | main content, headings, tables, links | 3,000 characters of output (estimated page) |
| plain text | as is | 3,000 characters |

The file type is detected from the bytes, so links without an extension work. Legacy `.doc`/`.ppt` and spreadsheets are rejected for free with a clear message.

### Example input

```json
{
  "urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf", "https://arxiv.org/pdf/1706.03762"],
  "ocr": "off",
  "chunkSize": 1000,
  "chunkOverlap": 100,
  "chunkOutput": "rows"
}
```

### Example output

One **document** row per input:

```json
{
  "recordType": "document", "docId": "f4fbd1d654f7", "source": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "fileName": "fw9.pdf", "fileType": "pdf", "status": "ok",
  "title": "Form W-9 (Rev. March 2024)", "subject": "Request for Taxpayer Identification Number and Certification",
  "createdAt": "2024-03-06T08:18:13", "pageUnit": "page", "totalPages": 6, "pagesProcessed": 6,
  "ocrPages": 0, "tables": 3, "charCount": 37882, "wordCount": 6522, "chunkCount": 47,
  "markdown": "<!-- page: 1 -->\n\n# W-9 Request for Taxpayer\n...",
  "markdownKey": "f4fbd1d654f7.md", "markdownUrl": "https://api.apify.com/v2/key-value-stores/.../records/f4fbd1d654f7.md"
}
```

and, when chunking is on with `chunkOutput: "rows"`, one **chunk** row per chunk:

```json
{
  "recordType": "chunk", "docId": "f4fbd1d654f7", "fileName": "fw9.pdf", "title": "Form W-9 (Rev. March 2024)",
  "chunkIndex": 3, "chunkCount": 47, "pageStart": 1, "pageEnd": 1, "charCount": 766,
  "headings": ["Part I Taxpayer Identification Number (TIN)"],
  "text": "### Part I Taxpayer Identification Number (TIN)\n\nEnter your TIN in the appropriate box..."
}
```

A ruled PDF table comes out as a Markdown table (W3C example PDF):

```
| Disability Category | Participants | Ballots Completed | Ballots Incomplete/ Terminated | Results Accuracy | Time to complete |
| --- | --- | --- | --- | --- | --- |
| Blind | 5 | 1 | 4 | 34.5%, n=1 | 1199 sec, n=1 |
```

The full Markdown of every document is also saved as `<docId>.md` in the run's key-value store, so you never lose content when the inline copy is turned off or capped.

| status | meaning | charged? |
|---|---|---|
| `ok` | converted | **yes**: one document fee (covers 10 pages), plus extra and OCR pages |
| `download_error` | 404, timeout, or file above `maxFileSizeMb` | no |
| `unsupported` | not a PDF/DOCX/PPTX/HTML/text file | no |
| `conversion_error` | the file is damaged or encrypted | no |
| `no_pages` / `skipped` | the page or spending limit was reached | no |

### Live test results (Oct 29, 2026)

A local run against **12 real public documents** (IRS forms and publications, an arXiv paper, a W3C table example, a scanned 1960s archive memo, US government and NIH Word templates, a PowerPoint deck, a Wikipedia article, plus a spreadsheet and a broken link) finished in **26 seconds** with OCR on `auto`:

- **10 converted**, 1 `unsupported` (the XLSX), 1 `download_error` (the 404).
- **243 pages** (242 text pages + 1 OCR page) and **823,500 characters** of Markdown. A 59-page IRS publication with 13 tables took 7.4 s, and a 67-page Word protocol template took 11 s.
- **47 tables** kept as Markdown tables: 23 from PDFs (IRS W-9: 3, arXiv: 6, IRS Pub 15: 13, W3C: 1) and 24 from Word, PowerPoint and HTML.
- The scanned memo had no text layer. It was flagged with a warning when OCR was off, and OCR returned 637 characters from it (the scan is low quality, so expect noisy text on old documents).
- Chunking at 1,500 characters with 150 characters of overlap produced **741 chunks** (median 1,381 characters, all within size plus overlap). **96%** of them carry a heading path.

### Use cases

- **RAG and AI assistants.** Turn policy PDFs, manuals and contracts into chunks with page citations.
- **LLM preprocessing.** Markdown is far cheaper in tokens than raw HTML and keeps the structure models need.
- **Knowledge bases and search.** Index slide decks and Word documents by heading and page.
- **Document analysis.** Use the metadata, page counts, table counts and word counts.

### Input options

- `urls`: links to documents, or keys of records in the run's default key-value store.
- `fileUrl`: upload a file from your computer.
- `ocr`: `off` (default), `auto` (only PDF pages without a text layer) or `always`. `ocrLanguage`: English, German, French, Spanish, Portuguese, Italian, Dutch, or mixed English with Spanish, French or German.
- `tables` (default on): detect ruled tables in PDFs.
- `pageMarkers` (default on): add `<!-- page: N -->` markers (`<!-- slide: N -->` for PowerPoint).
- `keepImageLinks` (default off): keep image links (embedded image data is never copied).
- `markdownInDataset` (default on): include the Markdown in the dataset row.
- `chunkSize` (0 = off), `chunkOverlap` (default 200), and `chunkOutput` (`rows` or `inline`).
- `maxPagesPerDocument` (default 500), `maxTotalPages` (0 = no limit), and `maxFileSizeMb` (default 50).
- `maxConcurrency` (1-4, default 2) and `timeoutSec` (download timeout, default 60).

### Known limits

- **Layout.** PDF text is read line by line. Multi-column layouts and form-style pages, such as tax forms, can interleave text from neighbouring columns. Headings are detected from font size, so they are a good guide but not perfect.
- **Tables.** PDF tables are found from ruling lines. Tables without borders come out as text. Complex merged-cell headers are flattened. A table row longer than the chunk size is split as text.
- **OCR** is Tesseract at 300 dpi. It works well on clean scans, but on faxes, handwriting and stamps it is noisy. It costs $0.01 per OCR page. OCR is CPU-heavy, so **run scanned batches with 4 GB of memory** (one full CPU core): in a live test on Oct 30 it OCR'd dense pages in about 14-15 seconds each. At the default 1 GB (a quarter of a core) the same 6-page form did not finish in 150 seconds, so expect it to be roughly four times slower there and set a longer run timeout.
- **Estimated pages.** DOCX and HTML have no fixed pages, so page numbers there are 3,000-character estimates, not the page numbers Word would print.
- **Not supported:** legacy `.doc`/`.ppt`, spreadsheets, password-protected PDFs, and images embedded inside DOCX/PPTX (only their alt text is kept). JavaScript-rendered web pages are read as served, without running scripts.
- **No personal-name metadata.** The author, creator and last-modified-by fields in document properties often contain people's names, so the Actor deliberately leaves them out. Text in the document body is converted as is.
- **Your links, your responsibility.** The Actor downloads exactly the links you provide, identifying itself as `FedSignalsBot`. It doesn't crawl or follow links, and it doesn't check `robots.txt`, because these are direct file requests that you make. Only convert documents you're allowed to process.

### Licenses

The Actor uses permissively licensed open-source components only (no GPL/AGPL): pdfplumber and pdfminer.six (MIT), pypdfium2 (Apache-2.0/BSD-3), Microsoft MarkItDown (MIT) with mammoth (BSD-2) and python-pptx (MIT), Tesseract OCR (Apache-2.0) via pytesseract (Apache-2.0), and httpx (BSD-3).

### How much does it cost?

Pay per event: you pay only for the events below plus Apify's $0.00005 run-start fee. Platform compute is included in the price.

- **$0.005 per document** converted ($5 per 1,000 documents), including its first 10 pages.
- **$0.001 per extra page** after the first 10 pages of a document ($1 per 1,000 pages).
- **$0.01 per OCR page**, charged separately and only when OCR is on and a page is actually OCR'd. A scanned-only document pays OCR pages only, with no document fee.
- **Free:** failed, unsupported and empty documents, and empty pages.

Real examples from our test runs:

- **Quick-start input** (IRS W-9, 6 pages): 1 document = **$0.005**.
- **A 59-page IRS publication**: $0.005 + 49 extra pages × $0.001 = **about $0.054**.
- **A $0.01 spending limit** on the W-9 plus the 59-page publication (live run, Oct 30): the publication converted its first 14 pages and stopped cleanly, and the W-9 was not started or charged: $0.005 + 4 extra pages × $0.001 + $0.00005 start = **$0.00905**. Which document gets the budget first depends on download order.
- **A scanned page with OCR**: $0.01 per OCR page, charged separately and not part of the 10 included pages.
- **4 scanned pages with OCR at 4 GB** (live run, Oct 30): 4 × $0.01 + Apify's start fee, which is $0.00005 per GB of memory (4 × $0.00005) = **$0.0402**.
- **1,000 typical documents of 10 pages or fewer**: **$5**.

Use `maxTotalPages`, `maxPagesPerDocument` and Apify's per-run spending limit to cap spend; the Actor stops cleanly when the limit is reached.

### Dates, time zones and currency

- `processedAt` is ISO 8601 in UTC (`Z`). `createdAt` and `modifiedAt` come from the file's own metadata and keep whatever time zone the file stored; many files store none.

### Other languages and regions

OCR (`ocrLanguage`) supports English, German, French, Spanish, Portuguese, Italian and Dutch, plus English mixed with Spanish, French or German.

Also searched as: convertir PDF a Markdown, OCR (ES) · converter PDF em Markdown, OCR (PT) · convertir PDF en Markdown (FR) · PDF in Markdown umwandeln (DE).

### FAQ

**Which formats are supported?**\
PDF (text and, with OCR, scanned), DOCX, PPTX, HTML and plain text. Legacy .doc/.ppt, spreadsheets and password-protected PDFs are rejected for free.

**Can I feed the output into a vector database?**\
Yes. Set `chunkSize` (for example 1500) and each chunk becomes its own row with page range and heading path.

**Where is the full Markdown?**\
In each document row (unless you turn off `markdownInDataset`) and always as a `.md` file in the run's key-value store, linked by `markdownUrl`.

**Does it output author names?**\
No. Author, creator and last-modified-by metadata are deliberately left out.

### Reliability

Measured on Apify as of Oct 29, 2026: 4 of 4 of our own test runs succeeded (100%), with a median run time of 7 seconds (longest 10 s) on small test inputs. It is a small sample from a new Actor; we will update it as runs accumulate. Local tests: 12 real documents in 26 seconds; all 10 supported files converted, 243 pages and 47 tables.

### Use it from AI agents (MCP)

Add this Actor as a tool in Claude Desktop, Cursor or any MCP client through the Apify MCP server:

```json
{
  "mcpServers": {
    "documents-to-markdown": {
      "url": "https://mcp.apify.com?tools=fedsignals/documents-to-markdown",
      "headers": { "Authorization": "Bearer YOUR_APIFY_TOKEN" }
    }
  }
}
```

### Use it from code (API)

One call runs the Actor and returns the dataset items. `maxTotalChargeUsd` caps what the run can charge.

Python:

```python
import requests

items = requests.post(
    "https://api.apify.com/v2/acts/fedsignals~documents-to-markdown/run-sync-get-dataset-items",
    params={"maxTotalChargeUsd": 1},
    headers={"Authorization": "Bearer YOUR_APIFY_TOKEN"},
    json={"urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"]},
    timeout=300,
).json()
print(len(items), "items")
```

JavaScript (Node 18+):

```js
const res = await fetch(
  "https://api.apify.com/v2/acts/fedsignals~documents-to-markdown/run-sync-get-dataset-items?maxTotalChargeUsd=1",
  {
    method: "POST",
    headers: { Authorization: "Bearer YOUR_APIFY_TOKEN", "Content-Type": "application/json" },
    body: JSON.stringify({"urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"]}),
  }
);
const items = await res.json();
console.log(items.length, "items");
```

### Related FedSignals Actors

- [Lever & Ashby Jobs Scraper: Career Site Job Feed](https://apify.com/fedsignals/career-site-jobs-feed): jobs straight from company career sites
- [Website Contact Scraper & Company Enrichment (Tech Stack)](https://apify.com/fedsignals/company-website-enricher): company data, tech stack and generic contacts from a domain
- [SEC Form D Scraper: EDGAR Startup Funding Rounds](https://apify.com/fedsignals/sec-form-d-monitor): new private funding rounds from SEC Form D filings

### Feedback

Found a bug or need a filter or source? Open an issue on the Issues tab or email **fedsignals.alerts@gmail.com**.

This Actor is not affiliated with or endorsed by Adobe, Microsoft or the publishers of the sample documents.

# Actor input Schema

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

Returns one Markdown document row per link (plus chunk rows if chunking is on). Public links to PDF, DOCX, PPTX or HTML files (one per line). The file type is detected from the content, so URLs without an extension work too. A key from this run's default key-value store also works.

## `fileUrl` (type: `string`):

Upload one file from your computer (PDF, DOCX, PPTX, HTML). It is converted together with the URLs above.

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

Controls OCR for scanned PDF pages; with OCR, scanned pages come back as text. off: never OCR (scanned pages come back empty with a warning, and are not charged). auto: OCR only PDF pages that have images but no text layer. always: OCR every PDF page. OCR pages cost $0.01 each, charged separately from the document price.

## `ocrLanguage` (type: `string`):

Language of scanned pages.

## `tables` (type: `boolean`):

Write ruled PDF tables as Markdown tables. DOCX, PPTX and HTML tables are always kept.

## `pageMarkers` (type: `boolean`):

Insert  /  comments so every passage can be traced to its page.

## `keepImageLinks` (type: `boolean`):

Keep ![alt](url) image links from HTML/Office files. Off: images become *\[Image: alt text]*. Embedded image data is never copied.

## `markdownInDataset` (type: `boolean`):

Include the Markdown in each dataset row. The full .md file is always saved to the key-value store as well.

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

Returns RAG chunk rows of about this many characters. Split the Markdown into chunks of about this many characters on heading, paragraph and sentence boundaries. 0 = no chunking. Tables are split by rows with the header repeated.

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

Characters from the end of each chunk repeated at the start of the next.

## `chunkOutput` (type: `string`):

rows: one dataset row per chunk (easy to feed into a vector database) plus the document row. inline: chunks as an array inside the document row.

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

Only the first N pages / slides / estimated pages of each document are converted and charged.

## `maxTotalPages` (type: `integer`):

Stop after this many pages across all documents. 0 = no limit (the run's spending limit still applies).

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

Larger files are skipped and not charged.

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

Documents converted at the same time (1-4).

## `timeoutSec` (type: `integer`):

Per-file download timeout.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://arxiv.org/pdf/1706.03762"
  ],
  "ocr": "off",
  "ocrLanguage": "eng",
  "tables": true,
  "pageMarkers": true,
  "keepImageLinks": false,
  "markdownInDataset": true,
  "chunkSize": 0,
  "chunkOverlap": 200,
  "chunkOutput": "rows",
  "maxPagesPerDocument": 500,
  "maxTotalPages": 0,
  "maxFileSizeMb": 50,
  "maxConcurrency": 2,
  "timeoutSec": 60
}
```

# Actor output Schema

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

One row per document (and one per chunk when chunk output is 'rows'). One item per row; field definitions are in the dataset schema.

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

JSON summary of the run: documents, converted, pages, OCR pages, chunks and counts by status.

# 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.irs.gov/pub/irs-pdf/fw9.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fedsignals/documents-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 = { "urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"] }

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

```

## MCP server setup

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