# PDF & Document to Markdown and Text: PDF, DOCX, PPTX, HTML (`fairfetch/document-to-markdown`) Actor

Convert PDFs, Word, PowerPoint, Excel, CSV, text files and web pages from URLs into clean Markdown and plain text for LLMs, RAG and AI agents. Headings, lists, tables, metadata, page splits, scanned-PDF detection. $1 per 1,000 documents + $0.10 per 1,000 PDF pages.

- **URL**: https://apify.com/fairfetch/document-to-markdown.md
- **Developed by:** [Naveen Raja](https://apify.com/fairfetch) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 document converteds

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 & Document to Markdown and Text: PDF, DOCX, PPTX, HTML

Turn **PDFs, Word documents, PowerPoint decks, Excel sheets, CSV, text files and web pages into clean Markdown and plain text**, ready for LLMs, RAG pipelines and AI agents. Paste links (or pick a key-value store with files), and get one row per document with the content, the title, author and other document metadata, page count, word count and language. The real file type is detected from the file itself, so links without a file extension work too. Headings, lists and tables are kept where the format has them; running headers, footers and page numbers are removed from PDFs; scanned PDFs are flagged instead of returning garbage.

| What you pay for | Price |
|---|---|
| `document`: one converted document (any format) | **$0.001** ($1 per 1,000 documents) |
| `page`: one PDF page or PowerPoint slide with text, or 1,000 spreadsheet data rows (started; header rows are free) | **$0.0001** ($0.10 per 1,000 pages) |
| Actor start (standard Apify event) | $0.00005 per GB of run memory, once per run ($0.00005 at the default 512 MB) |

| Example | Charged | Cost |
|---|---|---|
| 1 web page or Word file | 1 document | **$0.001** |
| A spreadsheet or CSV with up to 1,000 data rows (the default `maxRowsPerSheet` converts at most 1,000 per sheet) | 1 document + 1 page | $0.0011 |
| A CSV with 2,500 data rows and `maxRowsPerSheet` 2,500 or more | 1 document + 3 pages | $0.0013 |
| A 15-page PDF (an arXiv paper) | 1 document + 15 pages | $0.0025 |
| A 100-page PDF report | 1 document + 100 pages | $0.011 |
| A 492-page PDF (NIST SP 800-53, tested) | 1 document + 492 pages | $0.050 |
| 1,000 web pages | 1,000 documents | $1.00 |
| 1,000 PDFs of 10 pages | 1,000 documents + 10,000 pages | $2.00 |
| Our mixed test batch: 5 PDFs (743 pages), 3 PowerPoint decks (13 slides), 2 Word files, an Excel workbook, 2 CSV files, a text file, a Markdown file and 4 web pages, plus 3 files that were free (2 scanned PDFs, 1 legacy .ppt) | 19 documents + 758 pages (755 pages and slides + 1 page each for the 3 spreadsheets) | $0.095 |

Apify platform costs are included. **You don't pay for** failed downloads (404, access denied, login pages, timeouts), files over your size limit, unsupported formats, empty documents, web pages that need JavaScript, scanned PDFs (also when they carry a stamp or header line), pages without text or with only a page number, the same document given twice (after redirects), or documents skipped at your spending limit. Set **Maximum cost per run** (`maxTotalChargeUsd` in the API) and the Actor stops cleanly at that amount; it never converts pages it can't charge for.

### What you get

- **One tool for the common formats**: PDF, Word (`.docx`, `.docm`, `.dotx`), PowerPoint (`.pptx`, `.pptm`, `.ppsx`), Excel (`.xlsx`, `.xlsm`), CSV / TSV, plain text, Markdown and any web page.
- **Viewer links work**: GitHub / GitLab file pages, Dropbox share links and Google Drive / Docs links are rewritten to the file itself (public files only), with a note in the row.
- **Markdown that keeps the structure**: headings (`#`, `##`...), bullet and numbered lists with their levels, tables as Markdown tables, bold and italic (Word), footnotes and endnotes (`[^1]`), text boxes, and optionally links. Or plain text, or both.
- **Clean PDF text**: paragraphs re-joined across line breaks, words split by end-of-line hyphens repaired, headings detected from font sizes, bold numbered titles ("3.1 Methods") and the PDF's bookmarks, bullets turned into lists, superscripts kept on their line, and **repeated page headers, footers and page numbers removed**.
- **Web pages**: only the main content (navigation, menus, cookie banners, footers and ads dropped), with title, author, publish date, site name and description.
- **PowerPoint**: one section per slide (`## Slide 3: Title`) in reading order, bullet levels, tables, chart titles and the **speaker notes**.
- **Excel and CSV**: every sheet as a Markdown table (header row first, formulas as their saved values), with a row limit per sheet so huge sheets stay usable. Charged per document plus one `page` per started 1,000 data rows (the header row is free); each row shows its `metadata.rows` and `pageEvents`.
- **Metadata**: title, author, subject, keywords, created / modified dates, creating application, page count, word count, character count, table count and language (ISO 639-1).
- **One row per page if you want it** (`splitPages`): page-level rows with `pageNumber` for citations and RAG chunking.
- **Page selection**: `pageRange` ("1-5, 8, 10-") and `maxPages` to convert only what you need (and pay only for that).
- **Scanned PDFs are detected**: a page that is an image with no text, or only a stamp / header line (court headers, Bates numbers), is reported with `needsOcr: true` and listed in `pagesWithoutText`; a document made only of such pages gets `status: "needs_ocr"`. Not charged.
- **Clear errors per document**: every input gets a row with `status` (`ok`, `needs_ocr`, `empty`, `failed`, `skipped`) and, if it wasn't converted, an `errorKind` (`not_found`, `access_denied`, `login_required`, `too_large`, `unsupported_type`, `encrypted`, `timeout`, `duplicate`, `charge_limit`...) and a readable `error`. One bad link never stops the others, and a run whose links are all bad still succeeds with the error rows.
- **Safe on long runs**: progress is saved in the run's key-value store, so if Apify moves the run to another server it continues where it stopped, without returning or charging a document twice.
- Built for automation and AI agents: small, predictable rows, pay-per-event pricing without usage fees, limited permissions, and a `SUMMARY` record per run.

### Use cases

- **RAG and knowledge bases**: convert manuals, policies, reports, papers and help-center pages into Markdown chunks (one row per page with `splitPages`) for your vector database.
- **AI agents (MCP)**: give an agent one tool for "read this PDF / this deck / this page" that returns clean Markdown and says clearly when a file can't be read.
- **LLM pipelines**: summarise, classify or extract data from documents without writing a parser per format.
- **Document search and analytics**: word counts, languages, page counts and metadata for a list of document links.
- **Content migration**: Word and PowerPoint to Markdown for docs sites, wikis and static site generators.

### How to use

1. Add **Document URLs**: direct links to files or web pages, one per line.
2. Or select a **Key-value store with files** (optionally only some **Record keys**) to convert files you uploaded to Apify.
3. Choose the **Output format** (Markdown, plain text or both) and, if you want, **One row per page**, a **Page range** or **Max pages per document**.
4. Run it and download the results as JSON, CSV or Excel, or read them through the API. The dataset has the views **Documents**, **Content** and **Status & errors**; the run's key-value store has a `SUMMARY` record.

#### Input examples

The prefilled input (the IRS Form W-9 PDF and a W3C page):

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://www.w3.org/WAI/fundamentals/accessibility-intro/"
  ]
}
```

Page-level rows of the first 20 pages of a long PDF, as Markdown and plain text:

```json
{
  "urls": ["https://nvlpubs.nist.gov/nistpubs/SpecialPublications/NIST.SP.800-53r5.pdf"],
  "splitPages": true,
  "pageRange": "1-20",
  "outputFormat": "both"
}
```

Files you uploaded to one of your key-value stores:

```json
{
  "keyValueStore": "<your store ID or name>",
  "keyValueStoreKeys": ["contract.docx", "slides.pptx"]
}
```

#### Output example

A real row from the prefilled run (Markdown shortened):

```json
{
  "url": "https://www.w3.org/WAI/fundamentals/accessibility-intro/",
  "finalUrl": "https://www.w3.org/WAI/fundamentals/accessibility-intro/",
  "status": "ok",
  "error": null,
  "errorKind": null,
  "fileType": "html",
  "contentType": "text/html",
  "fileName": null,
  "fileSizeBytes": 57249,
  "title": "Introduction to Web Accessibility",
  "author": null,
  "language": "en",
  "pageCount": null,
  "pagesProcessed": null,
  "pageNumber": null,
  "wordCount": 996,
  "charCount": 6750,
  "tableCount": 0,
  "needsOcr": false,
  "pagesWithoutText": [],
  "truncated": false,
  "notes": [],
  "metadata": {
    "description": "Accessibility resources free online from the international standards organization: W3C Web Accessibility Initiative (WAI).",
    "siteName": "Web Accessibility Initiative (WAI)",
    "publishedAt": "2026-02-03",
    "canonicalUrl": "https://www.w3.org/WAI/fundamentals/accessibility-intro/"
  },
  "markdown": "# Introduction to Web Accessibility\n\n## Accessibility in Context\n\nThe power of the Web is in its universality.\n\nAccess by everyone regardless of disability is an essential aspect.\n\nThe Web is fundamentally designed to work for all people, whatever their hardware, software, language, location, or ability. When the Web meets this goal, it is accessible to people with a diverse range of hearing, movement, sight, and cognitive ability.\n\nThus the impact of disability is radically changed on the Web because the Web removes barriers to communication and interaction that many people face in the physical world. However, when websites, applications, technologies, or tools are badly designed, they can create barriers that exclude people from using the Web.\n\n**Accessibility is essential for developers and organizations that want to create high-quality websites and web tools, and not exclude people from using their products and services.**\n\n## What is Web Accessibility\n\nWeb accessibility means that websites, tools, and technologies are designed and developed so that people with disabilities can use them. More specifically, people can:\n\n- perceive, understand, navigate, and interact with the Web\n- contribute to the Web\n\n...",
  "text": null,
  "markdownUrl": null,
  "textUrl": null,
  "processedAt": "2026-10-18T16:03:34Z"
}
```

The PDF of the same run (Markdown shortened):

```json
{
  "url": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "finalUrl": "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
  "status": "ok",
  "error": null,
  "errorKind": null,
  "fileType": "pdf",
  "contentType": "application/pdf",
  "fileName": "fw9.pdf",
  "fileSizeBytes": 140815,
  "title": "Form W-9 (Rev. March 2024)",
  "author": "SE:W:CAR:MP",
  "language": "en",
  "pageCount": 6,
  "pagesProcessed": 6,
  "pageNumber": null,
  "wordCount": 6391,
  "charCount": 37712,
  "tableCount": 0,
  "needsOcr": false,
  "pagesWithoutText": [],
  "truncated": false,
  "notes": [],
  "metadata": {
    "subject": "Request for Taxpayer Identification Number and Certification",
    "keywords": "Fillable",
    "createdAt": "2024-03-06T13:18:13Z",
    "modifiedAt": "2024-03-06T13:18:13Z",
    "creatorTool": "Designer 6.5",
    "producer": "Designer 6.5"
  },
  "markdown": "Form W-9 (Rev. March 2024)\n\nRequest for Taxpayer Identification Number and Certification Department of the Treasury Internal Revenue Service Go to www.irs.gov/FormW9 for instructions and the latest information.\n\nGive form to the requester. Do not send to the IRS.\n\n...",
  "text": null,
  "markdownUrl": null,
  "textUrl": null,
  "processedAt": "2026-10-18T16:03:34Z"
}
```

A link that couldn't be converted (not charged):

```json
{
  "url": "https://github.com/settings/profile",
  "finalUrl": "https://github.com/login?return_to=https%3A%2F%2Fgithub.com%2Fsettings%2Fprofile",
  "status": "failed",
  "error": "The URL returned a login page (password form), not a document. Only public pages can be converted.",
  "errorKind": "login_required",
  "contentType": "text/html; charset=utf-8",
  "markdown": null
}
```

#### Output fields

| Field | Meaning |
|---|---|
| `url`, `finalUrl` | The input URL and the URL after redirects |
| `status` | `ok` (converted, charged), `needs_ocr` (scanned, no usable text layer), `empty` (no text), `failed`, `skipped` (spending limit reached, or the same document as another input) |
| `error`, `errorKind` | Why a document failed, in words and as a stable code |
| `fileType` | `pdf`, `docx`, `pptx`, `xlsx`, `csv`, `html`, `markdown` or `txt` (detected from the content) |
| `contentType`, `fileName`, `fileSizeBytes` | As sent by the server (file name from the URL or `Content-Disposition`) |
| `title`, `author`, `language` | From the document metadata (or the first heading); language detected from the text |
| `pageCount`, `pagesProcessed`, `pageNumber` | Pages / slides / sheets in the document, how many were converted, and the page of a page row |
| `wordCount`, `charCount`, `tableCount` | Size of the converted content |
| `needsOcr`, `pagesWithoutText` | Scanned pages that have no text layer |
| `pageEvents` | `page` events for this row as planned when it was stored: PDF pages / slides with text, or one per started 1,000 spreadsheet data rows; 0 for free rows. In page-row mode (`splitPages`) PDF / PowerPoint page rows show 1 or 0 and a workbook's events are on its first sheet row. Rows are stored before their `page` events are charged, so a failed row never costs anything; the run's `SUMMARY` (`chargedEvents`, `perDocument.billedPages`, `pageChargesCut`) is the source of truth for what was charged. It only differs if a charge is cut at the limit (the status message then says so). |
| `truncated`, `notes` | Whether something was left out (page range, limits) and why |
| `metadata` | Subject, keywords, description, created / modified / published dates, creating application, producer, site name |
| `markdown`, `text` | The content (null for the format you didn't ask for) |
| `markdownUrl`, `textUrl` | Links to the full content in the key-value store when it is too large for one dataset row (over 5 MB) |

### Use it from the API, an app or an AI agent

Run synchronously and get the rows in one call (keep your API token in the `APIFY_TOKEN` environment variable):

```bash
curl -X POST "https://api.apify.com/v2/acts/fairfetch~document-to-markdown/run-sync-get-dataset-items?maxTotalChargeUsd=0.10" \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://www.irs.gov/pub/irs-pdf/fw9.pdf"], "outputFormat": "markdown"}'
```

Python (`pip install "apify-client>=3"`):

```python
import os
from decimal import Decimal

from apify_client import ApifyClient

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("fairfetch/document-to-markdown").call(
    run_input={"urls": ["https://www.irs.gov/pub/irs-pdf/p17.pdf"], "splitPages": True, "maxPages": 30},
    max_total_charge_usd=Decimal("0.05"),
)
for row in client.dataset(run.default_dataset_id).iterate_items():
    print(row["pageNumber"], row["wordCount"], row["markdown"][:80])
```

The Actor runs with limited permissions and pay-per-event pricing without usage fees, so AI agents can call it through the Apify MCP server and pay per run. Rows have a fixed set of fields, failures come back as rows with a clear `errorKind`, and `maxPages` / `pageRange` keep long documents from flooding an agent's context.

### Performance and limits

- From our test runs on Apify: the prefilled input (a 6-page PDF and a web page) takes 5-9 seconds at the default 512 MB; a single web page about 3 seconds. A batch of 22 mixed files with 764 pages took 18 seconds at 1,024 MB and 41-93 seconds at 512 MB. A 492-page PDF converts in about 3 seconds of CPU; memory stayed around 100-225 MB.
- **For big batches of large PDFs or long web pages, give the run 1,024-2,048 MB**: Apify gives more CPU to more memory, so it runs several times faster for about the same platform cost.
- Downloads run in parallel (`maxConcurrency`, default 4); documents are converted one at a time in a separate process, so a damaged file that crashes a parser or a document that takes longer than about 5 minutes fails alone and the run continues.
- Files are limited to `maxFileSizeMb` (default 50 MB, up to 200 MB), checked from `Content-Length` before downloading and again while downloading; `requestTimeoutSecs` limits each download, retries included. **Plain text and web pages** are converted up to about 30 MB per GB of run memory (about 15 MB at the default 512 MB); longer files are cut there and marked `truncated` with a note. Spreadsheets and CSV files are cut at `maxRowsPerSheet` (default 1,000) rows per sheet and 100 columns; if you raise it to tens of thousands of rows, give the run 2 GB or more (a 45,000-row x 65-column workbook peaked at about 760 MB).
- **No OCR**: scanned PDFs and images are reported (`needs_ocr` / `unsupported_type`), not read. A page counts as scanned when it has no text of its own and images cover at least 30% of it, or when images cover at least 90% of it and its few characters (under 100, after running headers and page numbers are removed) are stamp-like: in the top or bottom 12% of the page, or vertical, or, only when the covering image looks like a scanner's output (1-bit or grayscale, JBIG2 or CCITT compression), in small type (12 pt or less) anywhere on the page, like a mid-page "RECEIVED" stamp. Born-digital pages are not scans: photo slides with a caption, and full-page colour photos with a small caption (portfolios, brochure covers), are converted and billed as text pages. A colour scan with a small mid-page stamp and no other text is therefore billed as a one-line text page.
- **PDF tables** come out as text lines in reading order; there is no table reconstruction for PDFs (Word, PowerPoint, Excel, CSV and HTML tables are real Markdown tables). Multi-column PDFs follow the order of the text in the file, which is the reading order for most generated PDFs.
- Not supported: legacy `.doc` / `.xls` / `.ppt`, OpenDocument, RTF, EPUB, ZIP archives, images, audio and video (reported with a clear error, not charged). Password-protected PDFs are reported as `encrypted`.
- Web pages are downloaded, not rendered: pages that build their content with JavaScript come back `empty` with a note (not charged). Pages behind a login are reported as `login_required`. Links to local or private network addresses (`localhost`, `10.x`, `169.254.x`...) are refused, also after redirects (`blocked_host`).
- The Actor identifies itself with the User-Agent `fairfetch-document-to-markdown/1.0`; some sites refuse automated downloads (`access_denied`).
- Content over 5 MB per row (whole document or one page row) is stored in the run's key-value store and linked from the row (`markdownUrl`, `textUrl`); the row keeps the first 1.5 MB of each.
- The run ends SUCCEEDED when documents were converted or when every failure was the input's own problem (404, unsupported, too large, invalid link, empty, scanned); the status message and the error rows say what happened. It FAILS only when nothing could be converted because every download was blocked by the sources or because of an internal error.

### FAQ

**Which file types are supported?** PDF, DOCX / DOCM / DOTX, PPTX / PPTM / PPSX, XLSX / XLSM, CSV / TSV, TXT, Markdown and HTML web pages. The type is detected from the file content (magic bytes and the parts inside Office files), then from the `Content-Type` header and the file name, so `https://example.com/download?id=123` works.

**Can it read scanned PDFs?** It detects them (`needsOcr: true`), including scans with a stamped header line, but doesn't run OCR, and you don't pay for them. PDFs that already have a text layer (including scans that were OCR'd before) are converted normally.

**How are pages counted?** Each PDF page and PowerPoint slide that has text is one `page` event, the same with or without `splitPages`. Pages without text, with only a page number or running header, or scanned pages are free. Excel and CSV files count one `page` per started 1,000 converted data rows, all sheets together, header rows not counted (1-1,000 data rows = 1 page; 2,500 data rows = 3 pages if you raise `maxRowsPerSheet` to 2,500, since the default converts at most 1,000 rows per sheet). Every row shows the `page` events it was charged in `pageEvents`. Word, text and web pages are charged per document only.

**How do I convert my own files?** Upload them to a key-value store in Apify Console (Storage → Key-value stores) and select that store in **Key-value store with files** (Apify then lets the run read it), or link them from any public URL, including GitHub, Dropbox and Google Drive / Docs share links of public files.

**Why is my Markdown different from what I see in the PDF?** PDFs store positioned text, not structure. The Actor rebuilds paragraphs, headings and lists from fonts and positions; complex layouts (tables, sidebars, forms) can come out in a different order. Word, PowerPoint and HTML keep their real structure.

**Does it follow robots.txt?** It downloads only the URLs you give it (no crawling), follows redirects, and sends a clear User-Agent. Use it only for documents you are allowed to access.

**Can I limit the cost?** Yes: `maxPages`, `pageRange` and `maxFileSizeMb` per document, and **Maximum cost per run** for the whole run.

### Legal

This Actor downloads only the documents and pages whose links you provide and converts them; it does not log in, bypass paywalls or access controls, or crawl other pages. You are responsible for having the right to access and process the documents, and for complying with their licenses, the sites' terms and the laws that apply to you, including data protection laws if documents contain personal data. The open-source libraries used (PDFium via pypdfium2, python-docx, python-pptx, openpyxl, trafilatura) are under permissive licenses (BSD, MIT, Apache-2.0). This Actor is not affiliated with Adobe, Microsoft or any website whose documents you convert; product names are used only to describe file formats.

# Actor input Schema

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

Direct links to documents: PDF, Word (.docx, .docm), PowerPoint (.pptx, .ppsx), Excel (.xlsx, .xlsm), CSV / TSV, plain text, Markdown, or any web page (its main content is extracted). The real file type is detected from the content, so links without a file extension work too. GitHub, GitLab, Dropbox and Google Drive / Docs viewer links are rewritten to the file itself. One row per document is returned.

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

Optional: convert files you uploaded to one of your key-value stores (every record, or only the keys below). Apify grants this run read access to the store you select.

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

Optional: only these records of the selected key-value store (default: all records except INPUT and SUMMARY, up to 1,000).

## `outputFormat` (type: `string`):

"markdown" (headings, lists, tables; best for LLMs and RAG), "text" (plain text, tables as tab-separated rows) or "both".

## `splitPages` (type: `boolean`):

Return one dataset row per PDF page, PowerPoint slide or Excel sheet (with pageNumber) instead of one row per document. Handy for page-level citations and RAG chunking. Other formats always give one row.

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

Optional: only convert these pages / slides / sheets, e.g. "1-5, 8, 10-" ("10-" = page 10 to the end). Leave empty for all pages.

## `maxPages` (type: `integer`):

Convert at most this many pages / slides / sheets of each document (after the page range). 0 = no limit. Limits the cost of very long PDFs.

## `includeTables` (type: `boolean`):

Keep tables from Word, PowerPoint and web pages as Markdown tables (tab-separated in plain text). Spreadsheets and CSV files are always tables. PDF tables come out as text lines (no table reconstruction).

## `includeLinks` (type: `boolean`):

Keep hyperlinks as [text](url) in the Markdown of web pages and Word files. Off = cleaner text for LLMs.

## `maxRowsPerSheet` (type: `integer`):

Excel and CSV: convert at most this many rows per sheet (huge sheets make huge Markdown).

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

Files larger than this are skipped (not charged). Checked before and during the download.

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

How many documents are downloaded at the same time.

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

Maximum time to download one document, all retries included.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf",
    "https://www.w3.org/WAI/fundamentals/accessibility-intro/"
  ],
  "outputFormat": "markdown",
  "splitPages": false,
  "maxPages": 0,
  "includeTables": true,
  "includeLinks": false,
  "maxRowsPerSheet": 1000,
  "maxFileSizeMb": 50,
  "maxConcurrency": 4,
  "requestTimeoutSecs": 90
}
```

# Actor output Schema

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

All converted documents as JSON (failed ones too, with status and error).

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

Counts by status and file type, charged events and per-document timings.

# 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",
        "https://www.w3.org/WAI/fundamentals/accessibility-intro/"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("fairfetch/document-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",
        "https://www.w3.org/WAI/fundamentals/accessibility-intro/",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("fairfetch/document-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",
    "https://www.w3.org/WAI/fundamentals/accessibility-intro/"
  ]
}' |
apify call fairfetch/document-to-markdown --silent --output-dataset

```

## MCP server setup

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