# PDF to Markdown & Office File Converter (`miacx/pdf-to-markdown-office-converter`) Actor

Convert PDF, DOCX, PPTX, XLSX, CSV, EPUB and HTML URLs to Markdown and tables. Supports text-based PDFs; no OCR. Pay per page, slide, sheet or text unit.

- **URL**: https://apify.com/miacx/pdf-to-markdown-office-converter.md
- **Developed by:** [mia.cx](https://apify.com/miacx) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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 & Office File Converter

Give your agent a document's text and tables as Markdown, with one result row per file.
Send public URLs of PDF, DOCX, PPTX, XLSX, CSV, EPUB or HTML files, or send the file content as base64.
PDFs need a text layer; no OCR runs.
Conversion costs $0.0009 per [Page unit](#pricing), plus $0.00005 per run start at default memory.

### Try it

Paste this input into the Actor to convert a one-page PDF:

```json
{"urls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"]}
```

An empty input `{}` runs the same one-page PDF example. No upstream API key is needed.

### Use from an agent

Add `https://mcp.apify.com?tools=miacx/pdf-to-markdown-office-converter` to your MCP client and sign in to Apify.
Call the dedicated Actor tool with the input above, then use `get-dataset-items` to read the completed run's dataset.
Or set `APIFY_TOKEN` to your Apify API token and call the API, which returns JSON rows:

```sh
curl --fail-with-body 'https://api.apify.com/v2/actors/miacx~pdf-to-markdown-office-converter/run-sync-get-dataset-items' \
  -H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' \
  -d '{"urls":["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"]}'
```

For an email attachment, SharePoint download or local file, send the content in `files` instead of a URL.
The `name` becomes the row's `input`, so keep names unique within a run:

```sh
jq -n --arg b64 "$(base64 -w0 report.pdf)" '{files: [{name: "report.pdf", contentBase64: $b64}]}' |
  curl --fail-with-body 'https://api.apify.com/v2/actors/miacx~pdf-to-markdown-office-converter/run-sync-get-dataset-items' \
    -H "Authorization: Bearer $APIFY_TOKEN" -H 'Content-Type: application/json' -d @-
```

### What you get

Read `status` first. On `ok`, use `markdown` and `tables`; on `needs_ocr`, send the PDF to an OCR tool.
The dataset contains one row per unique canonical file URL, then one row per inline file. Repeated URLs, including fragment variants, share one conversion.

This output comes from the recorded fixture `test/fixtures/dummy.pdf`. The timestamp is illustrative.

```json
{
  "status": "ok",
  "input": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "source": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "fetchedAt": "2026-10-31T00:00:00.000Z",
  "title": "dummy.pdf",
  "format": "pdf",
  "pageCount": 1,
  "markdown": "## Page 1\n\nDummy PDF file",
  "tables": [],
  "billablePages": 1,
  "textlessPages": [],
  "reason": null
}
```

`input` is the canonical URL, or the `name` of an inline file. `source` is the final URL after redirects, the input URL if fetching fails, or the inline file name.
`fetchedAt` is the fetch time, retained on cache hits, or the time an inline file was read; failures use the attempt time.
`title`, `format` and `pageCount` can be null when unknown. `reason` is null on success.

`pageCount` counts original PDF pages, slides or sheets before selection. Other formats use 3,000-character units.
`billablePages` counts charged Page events. `textlessPages` lists selected PDF pages without extracted text, including blank pages.
`markdown` contains text and tables in document order. It is empty on failure.

`tables` contains `source`, zero-based `index`, `headers` and `rows`. It is empty when no tables are extracted.

Table locations use a one-based `page`, one-based `slide`, or worksheet name in `sheet`.
Formats without pagination use an empty location object.
The first source row supplies the headers. Empty cells and merged continuation cells remain empty strings.

### Input

| Field | Default | Accepted input |
| --- | --- | --- |
| `urls` | The example PDF URL, unless `files` is set | Up to 10 public HTTP(S) file URLs. An empty list returns `no_results`. |
| `files` | `[]` | Up to 10 `{"name", "contentBase64"}` objects with unique names. The extension identifies CSV and other formats without a signature. |
| `pageRange` | `""`, all units | One-based PDF pages, PPTX slides or XLSX sheets, such as `"1-3,5"`. Values must be within 1–50. |

URLs and inline files share the limit of 10 per run. Selected units stay in document order.
A range on DOCX, CSV, EPUB or HTML returns `invalid_page_range`.

### Pricing

A Page costs $0.0009. It counts one selected text-bearing PDF page, PPTX slide or XLSX sheet.
For DOCX, EPUB, HTML and CSV, each started 3,000 Markdown characters counts as one Page.

A three-page text PDF costs $0.00275, including the $0.00005 run start at default 256 MB memory.
One thousand Page events cost $0.90, plus $0.00005 per run start at default memory.

Text-unit counts include Markdown tables and delimiters. One document can incur several Page events.
Failed conversions and textless PDF pages incur no Page events. Duplicate canonical URLs incur no additional Page events within a run.
Cached conversions still incur Page events. The $0.00005 run start applies even with no billable Pages at default memory.

### Limits and data freshness

- Each file must fit 4 MiB, decoded for inline files, and 50 PDF pages, PPTX slides or XLSX sheets, even with a smaller range.
- Office/EPUB ZIPs allow 12 MiB declared expansion and 1,000 entries. EPUBs allow 200 spine entries.
- Output caps are 500,000 Markdown characters per document and 50,000 cells per table.
- Legacy DOC, PPT and XLS files and encrypted documents are unsupported. HTML conversion neither crawls links nor renders scripts.
- PDF tables need aligned text; ambiguous layouts remain text. XLSX uses stored formula results and serial dates. Styling is lost.

Eligible downloads up to 256 KiB can be reused for one hour from the named store `file-to-markdown-cache-v1`.
Cache hits keep their original `fetchedAt` value. Cache expiry controls reuse; stored records remain until overwritten or deleted.

### If a row fails

| Status | Next step |
| --- | --- |
| `needs_ocr` | Use an OCR tool. None of the selected PDF pages yielded text. |
| `too_large` | Supply a smaller document. A page range does not bypass whole-file limits. |
| `invalid_page_range` | Check the range and format. Use ranges only for PDF, PPTX or XLSX. |
| `unsupported_format` | Supply one of the seven supported formats. |
| `http_error`, `fetch_error`, `rate_limited` | Check public access and the returned reason. |
| `invalid_input` | For an inline file, send standard padded base64. Otherwise fix the input the reason names. |
| `budget_exceeded` | Raise the run budget or reduce the requested work. Processing stops at this document. |

A mixed PDF can return `ok` while listing skipped pages in `textlessPages`.
Input with no URLs or files returns `no_results`. Other failed rows carry a reason, empty content and zero `billablePages`.

# Actor input Schema

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

Public HTTP(S) URLs of PDF, DOCX, PPTX, XLSX, CSV, EPUB or HTML files. Up to 10 per run, counting inline files, and 4 MiB per file. Omitted, with no inline files, uses the example PDF; \[] returns no_results. Canonical duplicates, including fragments, share one conversion per run. PDFs need text; no OCR or website crawling.

## `files` (type: `array`):

Send file content directly, for files that have no public URL, as \[{"name": "report.pdf", "contentBase64": "JVBERi0..."}]. Each name must be unique and becomes the row's input; its extension identifies formats without a signature, such as CSV. Content is standard padded base64, up to 4 MiB decoded. URLs and inline files total at most 10 per run.

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

One-based PDF pages, PPTX slides or XLSX sheets, such as "1-3,5", within 1-50. Empty or omitted selects all units. Selection follows document order and does not bypass whole-file limits. A range on DOCX, CSV, EPUB or HTML returns invalid_page_range.

## Actor input object example

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

# Actor output Schema

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

One row per unique file URL or inline file, each with a status field.

# 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.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("miacx/pdf-to-markdown-office-converter").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.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"] }

# Run the Actor and wait for it to finish
run = client.actor("miacx/pdf-to-markdown-office-converter").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.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
  ]
}' |
apify call miacx/pdf-to-markdown-office-converter --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,miacx/pdf-to-markdown-office-converter"
        }
    }
}
```

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/XDss4S7pSUtBGcf5v/builds/k21qcVAZ1o6fPg9GD/openapi.json
