# Document Text Extractor - PDF, DOCX & HTML to Text/Markdown (`clearfetch/document-text-extractor`) Actor

Extract clean text and markdown from PDF, DOCX and HTML documents, with per-page text, document metadata and real line breaks. Detects scanned PDFs that have no text layer instead of returning an empty result. No proxy, no login.

- **URL**: https://apify.com/clearfetch/document-text-extractor.md
- **Developed by:** [Nada Hanad](https://apify.com/clearfetch) (community)
- **Categories:** Developer tools, AI, 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 document processeds

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Document Text Extractor - PDF, DOCX & HTML to Text/Markdown

Turn documents into text you can actually use. Give this Actor links to PDF, DOCX or HTML files and it returns
plain text with the real line breaks intact, a markdown version with headings, the text of each PDF page
separately, and the document's own metadata. **$0.005 per document**, plus $0.0005 per page beyond the first 20.
Documents it cannot fetch are free.

### Why the output is different

Most PDF extraction hands back one long run-on paragraph, because the underlying library returns positioned
text fragments rather than lines, and the naive fix is to join them with spaces. This Actor rebuilds the lines
from the fragment positions: fragments sharing a baseline become one line, and a vertical gap noticeably larger
than the page's usual line spacing becomes a paragraph break. Headings are then inferred by comparing each
line's font size with the document's own body text, so a title becomes `#` and a section becomes `##`.

It also tells you when a PDF is a scan. An image-only PDF has pages but no text layer, and returning an empty
string for it looks like a bug. Here `hasTextLayer` is `false` and a note explains that the file needs OCR.

### What data you get

- **Plain text** with line and paragraph breaks preserved.
- **Markdown** with headings, and with lists, links, quotes, code blocks and tables for DOCX and HTML.
- **Per-page text** for PDFs, so you can cite or chunk by page.
- **Metadata**: title, author, subject, keywords, creator, producer and creation and modification dates.
- **Counts**: pages, words, characters and file size, which is what you need to budget an LLM pipeline.
- **Type detected from the file's own bytes**, not its name or the server's content type, so a `.docx` link
  that actually serves a PDF is handled correctly.

### How to use

1. Paste document links into **Documents**, one per line.
2. Leave the defaults, or set **Maximum pages** if you only need the start of long files.
3. Run it. Each document is one row, exportable as JSON, CSV or Excel, or readable from the API.

### Input

| Field | Type | Default | Description |
|-------|------|---------|-------------|
| `urls` | array | — | Links to PDF, DOCX, HTML or text files. Also accepts `url` and `startUrls`. |
| `maxPages` | integer | `0` | Stop after this many pages of a PDF. 0 reads the whole document. |
| `includeText` | boolean | `true` | Include the plain text. |
| `includeMarkdown` | boolean | `true` | Include the markdown version. |
| `includePages` | boolean | `true` | Include per-page text for PDFs. Turn off for smaller output. |
| `maxConcurrency` | integer | `5` | Documents processed in parallel. |
| `timeoutSecs` | integer | `60` | Download timeout per document. |
| `proxyConfiguration` | object | off | Optional. Not needed for most hosts. |

### Output example

A 15-page PDF read with `maxPages: 6`, trimmed here for readability:

```json
{
  "url": "https://arxiv.org/pdf/1706.03762",
  "finalUrl": "https://arxiv.org/pdf/1706.03762",
  "ok": true,
  "type": "pdf",
  "statusCode": 200,
  "title": null,
  "text": "Provided proper attribution is provided, Google hereby grants permission to\nreproduce the tables and figures in this paper solely for use in journalistic or\nscholarly works.\n\nAttention Is All You Need\n\n∗ ∗ ∗ ∗\nAshish Vaswani Noam Shazeer Niki Parmar Jakob Uszkoreit\nGoogle Brain Google Brain Google R …",
  "markdown": "### Provided proper attribution is provided, Google hereby grants permission to\n\nreproduce the tables and figures in this paper solely for use in journalistic or\n\nscholarly works.\n\n# Attention Is All You Need\n\n∗ ∗ ∗ ∗\n\nA …",
  "pages": [
    {
      "page": 1,
      "text": "Provided proper attribution is provided, Google hereby grants permission to\nreproduce the tables and figures in this paper solely for use in journalistic or\nsch …"
    }
  ],
  "pageCount": 15,
  "pagesRead": 6,
  "hasTextLayer": true,
  "characters": 18356,
  "words": 2932,
  "meta": {
    "creator": "LaTeX with hyperref",
    "producer": "pdfTeX-1.40.25",
    "createdAt": "2024-04-10T21:11:43Z",
    "modifiedAt": "2024-04-10T21:11:43Z"
  },
  "notes": [
    "Only the first 6 of 15 pages were read because of the \"maxPages\" setting."
  ],
  "bytes": 2215244,
  "elapsedMs": 509,
  "extractedAt": "2026-09-06T09:41:34.763Z"
}
```

A document that cannot be fetched is reported and costs nothing:

```json
{
  "url": "https://this-domain-does-not-exist-12345.com/file.pdf",
  "ok": false,
  "statusCode": null,
  "error": "getaddrinfo ENOTFOUND this-domain-does-not-exist-12345.com",
  "errorCode": "ENOTFOUND",
  "elapsedMs": 1111,
  "extractedAt": "2026-09-06T09:41:35.366Z"
}
```

### Pricing

- **$0.005 per document**, whatever its format.
- **$0.0005 per page beyond the first 20** of a PDF, because long documents genuinely cost more to read. A
  15-page paper is $0.005. A 75-page report is $0.0325. A 200-page book is $0.095.
- Documents that fail to download are free.

### Use cases

- **RAG and LLM pipelines**: get clean text and per-page chunks with page numbers you can cite.
- **Contract and report processing**: pull text out of filings, tenders and statements at scale.
- **Search indexing**: index the contents of PDFs you link to, not just their titles.
- **Migration**: convert a library of DOCX files to markdown for a static site or wiki.
- **Research**: turn a reading list of papers into plain text for analysis.
- **AI agents**: a tool that reads a document and returns its text, with the page count and a warning when the
  file is a scan.

### Integrations

```bash
curl -X POST "https://api.apify.com/v2/acts/clearfetch~document-text-extractor/run-sync-get-dataset-items?token=YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"urls": ["https://arxiv.org/pdf/1706.03762"], "includePages": true}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_TOKEN")
run = client.actor("clearfetch/document-text-extractor").call(
    run_input={"urls": ["https://arxiv.org/pdf/1706.03762"]}
)

for doc in client.dataset(run["defaultDatasetId"]).iterate_items():
    if not doc["hasTextLayer"]:
        print(doc["url"], "needs OCR")
        continue
    for page in doc["pages"] or []:
        print(f'--- page {page["page"]} ---')
        print(page["text"][:200])
```

Works with the Apify integrations for n8n, Make, Zapier, Google Sheets, Slack and webhooks, with scheduled runs,
and with AI agents through the Apify MCP server.

### FAQ

**Does it do OCR?** No. Scanned pages need optical character recognition, which is a different and far more
expensive job. This Actor detects those files and says so, with `hasTextLayer: false`, instead of returning an
empty string and letting you find out later.

**Which formats are supported?** PDF, DOCX, HTML and plain text. Legacy `.doc`, `.pptx` and `.xlsx` are not
supported yet; ask if you need one.

**How faithful is the markdown?** For DOCX and HTML it follows the real tags, so headings, lists, links, quotes,
code and tables come through. For PDF there are no tags at all, so headings are inferred from font size relative
to body text. That works well for papers, reports and books, and less well for heavily designed brochures.

**Are password-protected PDFs supported?** No. They fail with a clear error rather than returning nothing.

**Do I need a proxy?** No. A proxy input exists for hosts that block datacenter traffic.

**Is this legal?** It downloads documents you point it at and extracts their text. Whether you may use a given
document is between you and its licence; this Actor does not change that.

### Changelog

- **1.0.0** (2026-09) — first release: PDF, DOCX, HTML and text; line and paragraph reconstruction for PDFs;
  markdown with inferred headings; per-page text; metadata; scanned-PDF detection.

# Actor input Schema

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

Links to PDF, DOCX, HTML or plain-text files, one per line. Also accepts "url" or "startUrls".

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

Stop after this many pages of a PDF. 0 reads the whole document. Useful for very long files when you only need the beginning.

## `includeText` (type: `boolean`):

Include the extracted text with its line breaks preserved.

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

Include a markdown version, with headings inferred from font size in PDFs and from the real tags in DOCX and HTML.

## `includePages` (type: `boolean`):

For PDFs, include an array with the text of each page separately. Turn off for smaller output.

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

Documents processed in parallel.

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

Give up on downloading a document after this many seconds.

## `proxyConfiguration` (type: `object`):

Optional. Most document hosts answer plain requests, so no proxy is needed in normal use.

## Actor input object example

```json
{
  "urls": [
    "https://arxiv.org/pdf/1706.03762"
  ],
  "maxPages": 0,
  "includeText": true,
  "includeMarkdown": true,
  "includePages": true,
  "maxConcurrency": 5,
  "timeoutSecs": 60,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One row per document: detected type, plain text with real line breaks, a markdown version with headings, per-page text for PDFs, document metadata such as title, author and creation date, word and character counts, and a flag saying whether the file had a text layer at all. Documents that cannot be fetched appear with ok=false and are not charged.

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

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

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

```

## MCP server setup

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

```

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/ermA217uCyNTNhomq/builds/CCPvK8DuoSVYZDNcw/openapi.json
