# PDF to Markdown & Text Extractor (Failed Files Free) (`locaihost/doc-to-markdown`) Actor

Convert PDF, Word, PowerPoint, Excel, CSV and web pages into clean Markdown with headings and tables kept, or heading-aware chunks for a vector database. Each row has title, markdown, pageCount, wordCount, tokensApprox and needsOcr. Mixed formats in one run; failed files free. $1 per 1,000 pages.

- **URL**: https://apify.com/locaihost/doc-to-markdown.md
- **Developed by:** [locaihost data](https://apify.com/locaihost) (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.55 / 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.
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 to Markdown & Text Extractor (Failed Files Free)

Turn PDFs, Word documents, PowerPoint decks, Excel sheets, CSVs and web pages into clean Markdown with headings and tables kept, one row per file, or into heading-aware chunks with page numbers ready for a vector database. No login needed for public or signed links.

- **Build a RAG index:** PDFs and DOCX files to chunks with `headingPath` and `page`, ready for Pinecone, Qdrant or pgvector.
- **Chat with your documents:** an LLM gets Markdown headings, tables and lists instead of a wall of PDF text.
- **Give an AI agent a "read this link" tool** for PDF, Word, PowerPoint and Excel URLs.
- **Migrate a document library** to Markdown for a wiki, static site or search index.
- **Pull tables out of files:** Word, PowerPoint, Excel, CSV and HTML tables become Markdown tables.

### Quick start

Paste this into the **JSON** tab of the input and click **Start**. It converts a PDF paper, a Word file, a web page and an Excel sheet in one run:

```json
{
  "startUrls": [
    {"url": "https://arxiv.org/pdf/1706.03762"},
    {"url": "https://filesamples.com/samples/document/docx/sample3.docx"},
    {"url": "https://en.wikipedia.org/wiki/Retrieval-augmented_generation"},
    {"url": "https://filesamples.com/samples/document/xlsx/sample1.xlsx"}
  ]
}
```

You get 4 rows, one per file, in about 15 seconds, for 31 billed pages (about $0.03). Public links and signed links (S3, GCS, Azure, Dropbox) both work. Google Docs, Sheets and Slides, Google Drive, Dropbox `?dl=0`, GitHub `blob` and arXiv `abs` links are turned into direct downloads for you. You can also upload a file, or pick an Apify key-value store that holds your files.

### What data can I get?

![Real rows from this Actor's output: a PDF paper, a Word file, a PowerPoint deck, a web page and a PDF table converted to Markdown, with page, word and token counts](https://locaihost.org/img/readme/doc-to-markdown.png)

**Document mode (default):** one row per file.

| Group | Fields |
|---|---|
| Source | `sourceUrl`, `fileName`, `mimeType`, `format` (`pdf`, `docx`, `pptx`, `xlsx`, `csv`, `html`, `markdown`, `text`), `documentId` |
| Content | `title`, `markdown` (the whole document), `chunks` and `chunkCount` (only with *Also add chunks*) |
| Size | `pageCount` (PDF pages, slides or Excel sheets; null for HTML, CSV and text), `wordCount`, `tokensApprox` (characters ÷ 4) |
| Billing and checks | `billedPages`, `needsOcr` (little or no text layer, so probably scanned), `truncated`, `warnings`, `error`, `processedAt` |

One real row (run on 2026-10-18, `markdown` shortened):

```json
{
  "sourceUrl": "https://filesamples.com/samples/document/docx/sample3.docx",
  "fileName": "sample3.docx",
  "mimeType": "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
  "format": "docx",
  "documentId": "9c1f429213c75337",
  "title": "Sample Document",
  "pageCount": 2,
  "wordCount": 439,
  "tokensApprox": 751,
  "needsOcr": false,
  "markdown": "# Sample Document\n\nThis document was created using accessibility techniques for headings, lists, image alternate text, tables, and columns. …",
  "billedPages": 2,
  "truncated": false,
  "warnings": [],
  "error": null,
  "processedAt": "2026-10-18T05:37:20.908Z"
}
```

In the same run the arXiv PDF came back as 15 pages and 5,697 words, and the Wikipedia page as 2,263 words.

**Chunks mode:** one row per chunk instead, with `id` (`documentId-index`, stable across re-runs, so upserts never duplicate), `documentId`, `sourceUrl`, `title`, `chunkIndex`, `chunkCount`, `headingPath`, `page`, `pageEnd`, `tokensApprox` and `text`. A chunk never mixes two sections; tables are split by rows with the header repeated, and overlap applies to prose only. Turn on *Prefix chunks with their heading path* to start each chunk's text with "Title > Section > Subsection".

```json
{
  "id": "dd93e90470f88607-6",
  "documentId": "dd93e90470f88607",
  "sourceUrl": "https://arxiv.org/pdf/1706.03762",
  "title": "Attention Is All You Need",
  "chunkIndex": 6,
  "chunkCount": 38,
  "headingPath": ["Attention Is All You Need", "2 Background"],
  "page": 2,
  "pageEnd": 2,
  "tokensApprox": 375,
  "text": "Attention Is All You Need > 2 Background\n\n## 2 Background\n\nThe goal of reducing sequential computation …"
}
```

The dataset has two views, **Documents** and **Chunks**, and exports to JSON, CSV, Excel or HTML. Every run also writes a `SUMMARY` record listing each input with its status, format, billed pages, chunk count and any error.

### How much does it cost?

You pay per page converted, whichever output mode you pick. There is no charge for Apify platform usage on top.

| Row | Free and Starter | Scale | Business |
|---|---|---|---|
| Page converted | $1.00 per 1,000 | $0.75 per 1,000 | $0.55 per 1,000 |

Plus $0.00005 per run start (charged per GB of memory, so $0.0001 at the default 2 GB). **Apify's free $5 monthly credit covers about 5,000 pages.** Free-plan runs convert up to 50 documents each; the rest are listed as skipped in `SUMMARY`, and any paid Apify plan removes the cap.

How a page is counted: **PDF and PowerPoint**, each page or slide that produced text. **Word, Excel, CSV, HTML, Markdown and text**, one page per 3,000 characters of Markdown output, minimum 1.

**What's free:** documents that fail (bad or private link, unsupported or legacy format, image, timeout, oversize file), blank and scanned PDF pages with no text layer, duplicate links to the same file (converted and charged once), documents skipped by the free-plan cap, *Max documents*, your cost limit or the run's time limit, and runs with no usable input. To cap a run's spend, set **Maximum cost per run** in the run options: the run stops cleanly before the first document that would go over it. *Max pages per document* converts, and charges, only the first N pages of each PDF or deck.

### Limits, stated up front

- Up to 1,000 documents per run (50 on the free plan). Files up to 50 MB by default, adjustable to 200 MB. Each document gets 180 seconds by default (10 to 3,600).
- **Scanned PDFs are flagged, not read:** `needsOcr: true` and a warning, and their blank pages are not billed. Run them through an OCR Actor first.
- **Not supported:** legacy `.doc`, `.xls` and `.ppt` (save as DOCX, XLSX, PPTX or PDF), ODT, EPUB, ZIP archives, password-protected PDFs and images. Each gets a clear error and is not charged.
- **PDF tables and maths:** a PDF stores positioned text, not tables, so tables are rebuilt from how the text lines up. Clean grid tables come through cell by cell; columns set very close together can merge, and multi-row headers may join. Superscripts and maths are flattened to plain text (10²⁰ becomes `1020`), and text inside figures is left out.
- **Web pages** are fetched as static HTML and reduced to the main content (no menus or footers). A page that builds its content with JavaScript may give little text. Images are dropped, and Excel formulas are not recalculated (you get the value stored in the file).
- Excel keeps the first 20,000 rows per sheet. A document row is cut at about 8 MB of text (`truncated: true`, with a warning); use chunks mode for very large files.
- Chunk size is 64 to 8,192 tokens, overlap 0 to 2,048. In chunks mode, failed documents appear only in `SUMMARY`, so the dataset stays clean for your vector database.
- Runs need at least 512 MB of memory; a very large PDF (1,000+ pages) needs 2 GB or more. If memory runs low, the document in progress is stopped (not charged) and the run carries on. If the run's own time limit comes first, the rest are listed as skipped, not charged, and the run still succeeds.

### Schedules

Each run downloads every file at that moment, so you always get the current version behind each link. Save a task that points at a list of links or a key-value store, then add a schedule under **Schedules** in Apify Console to keep an index current. Chunk `id`s are built from the link and the chunk's position, so re-upserting the same document overwrites its chunks by `id` instead of duplicating them.

### Integrations: API, n8n, Make and AI agents

**API.** Run it and get the rows back in one call (synchronous runs wait up to 5 minutes; use the asynchronous run endpoint for large batches):

```bash
curl -X POST "https://api.apify.com/v2/acts/locaihost~doc-to-markdown/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"startUrls": [{"url": "https://arxiv.org/pdf/1706.03762"}], "outputMode": "chunks", "chunkSize": 512}'
```

Python, with `pip install apify-client`, reading chunks for a vector database:

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")
run = client.actor("locaihost/doc-to-markdown").call(run_input={
    "startUrls": [{"url": "https://arxiv.org/pdf/1706.03762"}],
    "outputMode": "chunks",
    "chunkContextHeader": True,
})
for chunk in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(chunk["id"], chunk["headingPath"], chunk["page"], chunk["tokensApprox"])
```

**n8n, Make, Zapier, LangChain, LlamaIndex, Pinecone, Qdrant, Google Sheets, webhooks.** Apify's built-in integrations send each run's dataset wherever you need it.

**AI agents (MCP).** Add this Actor to Claude, Cursor or any MCP client with `https://mcp.apify.com/?tools=locaihost/doc-to-markdown`, then ask something like *"Convert the arXiv paper 1706.03762 to Markdown and summarise section 3, keeping the tables."* The minimal input is `{"startUrls": [{"url": "…"}]}`; for long files add `"maxPagesPerDocument": 20` so the agent reads, and pays for, only what it needs.

### FAQ

#### How much does it cost to convert PDF to Markdown?

$1.00 per 1,000 pages on the Free and Starter plans, $0.75 on Scale and $0.55 on Business. A 1,000-page knowledge base costs $1.00 on Starter. Apify's free $5 monthly credit covers about 5,000 pages.

#### Can I use it through the API or from Python?

Yes. Use the `run-sync-get-dataset-items` call above, or Apify's Python and JavaScript clients. Every run's rows are also available as JSON, CSV or Excel from its dataset.

#### Can I use it from an AI agent?

Yes. Through the Apify MCP server (`https://mcp.apify.com/?tools=locaihost/doc-to-markdown`) any MCP client can send a document link and read the Markdown back.

#### Is it legal to convert documents this way?

The Actor converts only the documents you supply: public links, signed links, share links, an upload, or records in your own Apify key-value store. It does not crawl websites or look for files on its own, and it does not look for or extract personal data; whatever is in your file is in the output, and it stays in your Apify account. You are responsible for having the right to process and store the documents you send.

#### Why are some fields empty?

`pageCount` is null for formats without pages (HTML, CSV, text). A failed document has `markdown` null, `billedPages` 0 and the reason in `error`, for example a private share link (`Got a web page instead of the file…`), a file over the size limit, or a legacy `.doc`. A scanned PDF has little text and `needsOcr: true`. A web page built with JavaScript can come back nearly empty.

#### Which formats are supported?

PDF, DOCX, PPTX (with speaker notes, optional), XLSX (one Markdown table per sheet), CSV, HTML, Markdown and plain text.

### More from locaihost data

- [OCR for images and scanned PDFs](https://apify.com/locaihost/ocr): read the text out of scans this Actor flags with `needsOcr`.
- [YouTube transcript scraper](https://apify.com/locaihost/youtube-transcripts): add video transcripts to the same knowledge base.
- [Website screenshot API](https://apify.com/locaihost/screenshot): keep an image or PDF of each page next to its text.

### Did it help?

If this saved you time, a quick rating on the Store helps other people find it.

### Support

Something not working, or a format or field you need? Open an issue on the **Issues** tab: we answer issues within 24 hours.

# Changelog

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

# Actor input Schema

## `startUrls` (type: `array`):

Links to your documents: PDF, DOCX, PPTX, XLSX, CSV, HTML pages, Markdown or text. Public URLs or signed links (S3, GCS, Azure, Dropbox). Google Docs/Sheets/Slides, Google Drive, Dropbox, GitHub and arXiv share links are converted to direct downloads automatically. Up to 1,000 per run.

## `uploadedFile` (type: `string`):

Or upload one document from your computer. It is converted together with any links above.

## `keyValueStore` (type: `string`):

Convert documents already stored on Apify. Pick the store; the Actor gets read-only access to it for this run.

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

Keys of the documents in the store above. Leave empty to convert every record in the store.

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

"document": one dataset item per document with the full Markdown. "chunks": one item per chunk, ready to embed and upsert into a vector database (Pinecone, Qdrant, Weaviate, pgvector...).

## `includeChunks` (type: `boolean`):

In document mode, also include a "chunks" array in each item.

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

Maximum chunk size in approximate tokens (1 token ≈ 4 characters of English text). 256–1,024 suits most embedding models.

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

When a long section is split, repeat this many tokens from the end of one chunk at the start of the next. Capped at half the chunk size.

## `chunkContextHeader` (type: `boolean`):

Start each chunk's text with "Document title > Section > Subsection", which usually improves retrieval. The path is always available separately in headingPath.

## `includePageMarkers` (type: `boolean`):

Insert  comments at PDF page and slide boundaries in the full Markdown. Chunks always carry page numbers either way.

## `keepLinks` (type: `boolean`):

Keep hyperlinks as [text](url). Turn off for plain text links only.

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

Add each slide's speaker notes below its content.

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

Only convert the first N pages of each PDF (or slides of each PPTX). Caps cost on very long files. 0 = all pages.

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

Stop after this many documents. 0 = no limit (up to 1,000 per run).

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

Skip files larger than this, with an error on that document.

## `timeoutSecs` (type: `integer`):

Give up on a single document (download plus conversion) after this long; the rest of the run continues.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://arxiv.org/pdf/1706.03762"
    },
    {
      "url": "https://filesamples.com/samples/document/docx/sample3.docx"
    },
    {
      "url": "https://en.wikipedia.org/wiki/Retrieval-augmented_generation"
    },
    {
      "url": "https://filesamples.com/samples/document/xlsx/sample1.xlsx"
    }
  ],
  "outputMode": "document",
  "includeChunks": false,
  "chunkSize": 512,
  "chunkOverlap": 64,
  "chunkContextHeader": false,
  "includePageMarkers": false,
  "keepLinks": true,
  "includeSpeakerNotes": true,
  "maxPagesPerDocument": 0,
  "maxDocuments": 0,
  "maxFileSizeMb": 50,
  "timeoutSecs": 180
}
```

# Actor output Schema

## `documents` (type: `string`):

One item per document: Markdown, title, pages, word count, errors (document mode).

## `chunks` (type: `string`):

One item per chunk with heading path and page (chunks mode).

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

Per document: status, format, pages billed, chunk count and any error.

# 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 = {
    "startUrls": [
        {
            "url": "https://arxiv.org/pdf/1706.03762"
        },
        {
            "url": "https://filesamples.com/samples/document/docx/sample3.docx"
        },
        {
            "url": "https://en.wikipedia.org/wiki/Retrieval-augmented_generation"
        },
        {
            "url": "https://filesamples.com/samples/document/xlsx/sample1.xlsx"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("locaihost/doc-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 = { "startUrls": [
        { "url": "https://arxiv.org/pdf/1706.03762" },
        { "url": "https://filesamples.com/samples/document/docx/sample3.docx" },
        { "url": "https://en.wikipedia.org/wiki/Retrieval-augmented_generation" },
        { "url": "https://filesamples.com/samples/document/xlsx/sample1.xlsx" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("locaihost/doc-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 '{
  "startUrls": [
    {
      "url": "https://arxiv.org/pdf/1706.03762"
    },
    {
      "url": "https://filesamples.com/samples/document/docx/sample3.docx"
    },
    {
      "url": "https://en.wikipedia.org/wiki/Retrieval-augmented_generation"
    },
    {
      "url": "https://filesamples.com/samples/document/xlsx/sample1.xlsx"
    }
  ]
}' |
apify call locaihost/doc-to-markdown --silent --output-dataset

```

## MCP server setup

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