# PDF to Text & Markdown Converter (PDF para Texto) (`kman-br/pdf-to-text-markdown`) Actor

Extracts the text layer of PDFs from public URLs and returns plain text and simple Markdown (headings by font size) plus the page count. No OCR, no browser, nothing stored outside your run.

- **URL**: https://apify.com/kman-br/pdf-to-text-markdown.md
- **Developed by:** [Kman](https://apify.com/kman-br) (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 Text & Markdown Converter (PDF para Texto)

Extracts the **text layer** of PDFs you point it at by URL and returns plain text and simple Markdown, plus the page count. Built for AI, RAG and document pipelines. No browser, no proxy, **no OCR**.

### Your responsibility

- You are responsible for having the right to process every PDF you submit: copyright, license and terms of use of the source, and any personal data inside the document (GDPR, LGPD and similar laws).
- The Actor does not look for, extract or label personal data. It returns the text exactly as the PDF contains it, and it never returns the PDF's author, creator or producer metadata.
- **No OCR.** Scanned or image-only PDFs have no text layer, so they come back with `status: "no_text_layer"` and no text.
- **Nothing is stored by the Actor outside your own run.** PDF bytes live only in memory while a file is processed and are discarded right after. The text appears only in the dataset of your run, never in the log, the key-value store or temporary files.

### Input

| Field | Description |
|---|---|
| `urls` | Public http(s) URLs of PDFs. Ports 80/443 only, no `user:password@` in the URL. Up to 100 per run, 20 MiB per file. |
| `outputFormat` | `both` (default), `markdown` or `text`. |
| `maxPagesPerPdf` | Pages extracted per PDF, from page 1 (default and hard cap 500). |
| `maxTotalPages` | Pages extracted per run (default and hard cap 2000). Remaining PDFs are skipped. |

Input values can only lower the built-in caps, never raise them.

> **Note on charges:** the US$0.02 per-PDF fee is charged once a URL passes input validation and before the file is downloaded. A URL that then returns 404 or another error, is not a PDF, exceeds the size limit, or resolves to a blocked address is still charged the per-PDF fee. Pages are charged only when text is actually extracted. Please check that your URLs point to reachable, public PDF files before running.

### Output

One dataset item per URL:

| Field | Meaning |
|---|---|
| `url` | The URL you sent (credentials, if any, removed). |
| `status` | `ok`, `no_text_layer`, `encrypted`, `invalid_pdf`, `not_pdf`, `too_large`, `download_timeout`, `download_error`, `http_error`, `parse_timeout`, `memory_limit`, `blocked_url`, `invalid_url` or `skipped`. |
| `http_status` | HTTP status when the download did not return 2xx. |
| `page_count` | Pages in the document. |
| `pages_extracted` | Pages included in `text` / `markdown`. |
| `truncated`, `truncated_reason` | `page_limit`, `run_page_limit`, `item_size`, `billing_limit`, `margin_limit` or `prazo_interno` (the run deadline). |
| `title` | The document title, when it has one. |
| `text` | Plain text. Paragraphs are separated by a blank line and pages by a form feed (`\f`). |
| `markdown` | Markdown with `#`/`##`/`###` headings guessed from font size and `<!-- page N -->` markers. |
| `chars` | Characters of extracted text. |
| `bytes_downloaded` | Bytes received for this URL. |

The key-value store record `OUTPUT` has only counts, the stop reason and the events charged.

### Limits

100 PDFs, 20 MiB per file, 500 pages per PDF, 2000 pages per run, about 8 MB of text per item (the item is cut at a page boundary and flagged `item_size`), 45 s per download, 10 s per page and 90 s per PDF, and an internal run deadline of 10 minutes. When a limit is hit the run ends with a partial summary and exit code 0, and you are charged only for work that was done.

### Pricing

Pay per event. Prices: run start US$0.0005, US$0.02 per PDF processed, US$0.001 per page extracted. The PDF event is charged right before the download, so a slow, invalid or encrypted file still costs the compute it used. Page events are charged after the pages are extracted, in blocks of up to 25. Pages of a scanned PDF are not charged. URLs refused before any request (invalid or private addresses) are free. The Actor honors your maximum charge per run.

> **Note on charges:** the US$0.02 per-PDF fee is charged once a URL passes input validation and before the file is downloaded. A URL that then returns 404 or another error, is not a PDF, exceeds the size limit, or resolves to a blocked address is still charged the per-PDF fee. Pages are charged only when text is actually extracted. Please check that your URLs point to reachable, public PDF files before running.

### Safety

- Only public http(s) addresses are fetched. Private, loopback, link-local and cloud-metadata addresses are refused when connecting and on every redirect (up to 5).
- The PDF parser runs in an isolated worker thread with a memory limit and a hard deadline, with script evaluation off. It is configured so that it never makes a network request or reads a local file for fonts or character maps.

### Known limitations

- No OCR. Only the first 10 pages are probed: if all of them have no text the PDF is reported as `no_text_layer`.
- Plain text order follows the PDF drawing order. Multi-column layouts, tables and lists are not reconstructed.
- No external CMaps or font files are bundled, so PDFs that rely on them (some older CJK documents) can return empty or partial text.
- Password-protected PDFs return `encrypted`.

### Third-party software

The text is extracted with [pdf.js](https://github.com/mozilla/pdf.js) by Mozilla (Apache-2.0), in the single-file build packaged by [unpdf](https://github.com/unjs/unpdf) (MIT). Both license texts are in `vendor/LICENSE-pdfjs` and `vendor/LICENSE-unpdf`.

### Related Actors

More Actors from the same publisher:

- [Website Health Checker: Broken Links & SEO (Saúde do Site)](https://apify.com/kman-br/website-health-checker): HTTP-only audit of a site you own: broken links, robots.txt, sitemap.xml, llms.txt and per-page SEO tags.
- [EU TED Tenders Scraper (Licitações da UE)](https://apify.com/kman-br/eu-ted-tenders): EU public procurement notices from the TED Search API v3, normalized: title, buyer, country, CPV, value and deadline.
- [Brazil Public Tenders & Procurement (Licitações BR)](https://apify.com/kman-br/licitacoes-br): Brazilian public tenders and procurement from the PNCP portal, with a source link on every record.
- [Brazil Bankruptcy Auctions (Leilões de Falência)](https://apify.com/kman-br/leiloes-falencia): Auction notices in company bankruptcy and judicial recovery cases from the CNJ DJEN/Comunica feed.

Browse the full catalog: [kman-br.github.io](https://kman-br.github.io/) ([Português](https://kman-br.github.io/pt/)).

# Actor input Schema

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

Public http(s) URLs of PDF files (ports 80/443 only, no user:password in the URL). Up to 100 per run and 20 MiB per file. Private, local and metadata addresses are refused.

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

Which fields to fill in each result. Markdown marks pages with HTML comments and turns larger fonts into headings.

## `maxPagesPerPdf` (type: `integer`):

Pages extracted from each PDF, counted from the first page. Each extracted page is charged. Hard cap 500.

## `maxTotalPages` (type: `integer`):

Total pages extracted across all PDFs in the run. When it is reached, remaining PDFs are skipped. Hard cap 2000.

## Actor input object example

```json
{
  "urls": [
    "https://www.irs.gov/pub/irs-pdf/fw9.pdf"
  ],
  "outputFormat": "both",
  "maxPagesPerPdf": 500,
  "maxTotalPages": 2000
}
```

# Actor output Schema

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

No description

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

OUTPUT: status, reason for stopping (prazo_interno, billing_limit, margin_limit, run_page_limit), counts and events charged. No PDF content.

# 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"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("kman-br/pdf-to-text-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"] }

# Run the Actor and wait for it to finish
run = client.actor("kman-br/pdf-to-text-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"
  ]
}' |
apify call kman-br/pdf-to-text-markdown --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kman-br/pdf-to-text-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/iBa3My5PwYVprWRyT/builds/xRf5wejhR9CpdfALO/openapi.json
