# PDF to Markdown & Text Extractor for RAG (DOCX, HTML, TXT) (`wintersworks/document-to-text`) Actor

Input: URLs of PDF, DOCX, HTML or TXT files. Output: PDF to Markdown or plain text per document, per page, or as RAG-ready chunks with token counts. $2 per 1,000 documents plus $0.30 per 1,000 pages. No OCR.

- **URL**: https://apify.com/wintersworks/document-to-text.md
- **Developed by:** [Winters Works](https://apify.com/wintersworks) (community)
- **Categories:** AI, 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 & Text Extractor for RAG (DOCX, HTML, TXT)

Convert **PDF to text**, **PDF to Markdown-ready text**, DOCX, HTML and TXT files from URLs into clean, structured data for **RAG pipelines, LLM ingestion, search indexing and document parsing**. Get one row per document, per page, or per ready-to-embed **chunk** (with overlap and approximate token counts). Pure HTTP and pure JavaScript: no browser, no OCR, fast and cheap.

### Use cases

- Feed PDFs (papers, reports, manuals, filings, contracts you are allowed to process) into a vector database or an LLM context window.
- Build a RAG index: choose `chunk` mode and get chunks with `chunkIndex`, word counts and `approxTokens`.
- Pull metadata (title, author, page count) from large batches of PDFs.
- Convert Word (.docx) documents to Markdown with headings preserved.
- Quickly check how long a document is in tokens before sending it to a model.

### Input

| Field | Description |
|---|---|
| `urls` | Public URLs of PDF, DOCX, HTML or TXT files (up to 50 MB each). |
| `outputMode` | `document` (default), `page` or `chunk`. |
| `maxPages` | Extract only the first N pages of each PDF (default 3, 0 = all). Controls cost. |
| `chunkSize`, `chunkOverlap` | Approximate tokens (chars / 4) per chunk and overlap, used in `chunk` mode. |
| `includeMarkdown` | Add a best-effort `markdown` field (default on). |
| `maxFileSizeMb` | Skip files larger than this (max 50). |

Running with empty input processes two small example PDFs (a tiny test file and the first 3 pages of the "Attention Is All You Need" paper).

```json
{ "urls": ["https://arxiv.org/pdf/1706.03762"], "outputMode": "chunk", "maxPages": 0, "chunkSize": 500, "chunkOverlap": 50 }
```

### Output

Document mode row (abridged):

```json
{
  "url": "https://arxiv.org/pdf/1706.03762", "fileName": "1706.03762", "mimeType": "application/pdf",
  "pageCount": 15, "pagesExtracted": 3, "title": null, "author": null, "metadata": {"producer": "..."},
  "likelyScanned": false, "charCount": 5412, "wordCount": 1353, "approxTokens": 1353,
  "text": "...", "markdown": "...", "pages": [{ "page": 1, "text": "..." }]
}
```

Page mode adds `page`; chunk mode adds `chunkIndex` and `chunkCount`. Failed URLs produce a row with `error` and are not charged.

### Pricing (pay per event)

- **$0.002 per document** processed successfully (`document-processed`)
- **$0.0003 per page** extracted (`page-extracted`). For DOCX/HTML/TXT, a "page" is a block of about 3,000 characters.

Example: a 20-page PDF costs $0.002 + 20 x $0.0003 = $0.008. Failed downloads, unsupported files and oversize files are free. Use `maxPages` to cap spend per document, and the run's max-cost setting to cap the whole run.

### Limitations

- Supported: PDF, DOCX, HTML, and TXT/MD/CSV. Legacy .doc, PPTX, XLSX and password-protected PDFs are not supported and return an error (not charged).
- No OCR. Scanned or image-only PDFs return little or no text with `likelyScanned: true`, and are still charged as a processed document.
- Maximum file size 50 MB (lower via `maxFileSizeMb`); each download times out after 60 s. Only public URLs, no logins or cookies.
- Processes `maxPages` pages per PDF (default 3; 0 means all). Pages for DOCX, HTML and text are logical blocks of about 3,000 characters, not print pages.
- PDF text keeps reading order but loses layout and tables, and PDF `markdown` is plain text. Token counts are approximate (characters / 4). Documents are fetched one after another, with no site-specific rate limiting beyond that.

### FAQ

**Does it do OCR?** No. Scanned or image-only PDFs return empty or near-empty text and the row has `likelyScanned: true`. You are still charged the normal event price for a document that parsed, so check `likelyScanned` if you process scans.
**Is the Markdown accurate?** DOCX headings, lists and tables and HTML structure convert to Markdown. PDFs have no reliable structure, so their `markdown` is plain extracted text.
**Which formats?** PDF, DOCX, HTML, TXT/MD/CSV. Legacy .doc, .pptx and .xlsx are not supported.
**Password-protected PDFs?** Not supported; they return an error and are not charged.
**Token counts?** Approximate (characters / 4), good for planning, not exact for any specific model.
**Is this legal?** The Actor only downloads files from URLs you supply and converts them to text. That is lawful only for documents you have the right to access and process. Copyright, confidentiality and privacy rules still apply to the content, and scraping a site's documents can violate its terms. You are responsible for what you process and how you use the output.

Built by Winters Works. Questions or feature requests: use the Issues tab.

### Use from code and AI agents

Call it from any language with the Apify API, or let an AI agent find and run it through the [Apify MCP server](https://mcp.apify.com).

```python
from apify_client import ApifyClient
run = ApifyClient("<YOUR_APIFY_TOKEN>").actor("wintersworks/document-to-text").call(run_input={"urls": ["https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf", "https://arxiv.org/pdf/1706.03762"], "outputMode": "document", "maxPages": 3})
items = ApifyClient("<YOUR_APIFY_TOKEN>").dataset(run["defaultDatasetId"]).list_items().items
```

Results land in the run's dataset (JSON, CSV, Excel). Set a maximum charge per run in the run options to cap spend.

# Actor input Schema

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

Public URLs of PDF, DOCX, HTML or TXT files. Up to 50 MB each.

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

document = one row per file; page = one row per page; chunk = one row per RAG chunk.

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

Only the first N pages of each PDF are extracted (and charged). 0 = all pages.

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

Approximate tokens (chars/4) per chunk. Used in chunk mode; if > 0 in document mode, a chunks array is added to the row.

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

Approximate tokens of overlap between consecutive chunks.

## `includeMarkdown` (type: `boolean`):

Add a best-effort markdown field (headings from DOCX and HTML; PDFs are plain text).

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

Files larger than this are skipped (not charged). Hard cap 50.

## Actor input object example

```json
{
  "urls": [
    "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
    "https://arxiv.org/pdf/1706.03762"
  ],
  "outputMode": "document",
  "maxPages": 3,
  "chunkSize": 500,
  "chunkOverlap": 50,
  "includeMarkdown": true,
  "maxFileSizeMb": 50
}
```

# Actor output Schema

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

All result items from the run's default dataset.

# 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",
        "https://arxiv.org/pdf/1706.03762"
    ],
    "maxPages": 3
};

// Run the Actor and wait for it to finish
const run = await client.actor("wintersworks/document-to-text").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",
        "https://arxiv.org/pdf/1706.03762",
    ],
    "maxPages": 3,
}

# Run the Actor and wait for it to finish
run = client.actor("wintersworks/document-to-text").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",
    "https://arxiv.org/pdf/1706.03762"
  ],
  "maxPages": 3
}' |
apify call wintersworks/document-to-text --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wintersworks/document-to-text"
        }
    }
}
```

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/S70AYMIcxBXzYq3TY/builds/7mR3k007I0qpLSx6E/openapi.json
