# PDF to Structured Markdown Converter (`automation-lab/pdf-to-structured-markdown-converter`) Actor

Convert PDFs into page-aware Markdown with typed headings, paragraphs, lists, links, simple tables, bounding boxes, and embedded-image references.

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

## Pricing

from $7.64 / 1,000 document extracteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## PDF to Structured Markdown Converter

Turn public PDF URLs and uploaded PDF files into clean, page-aware Markdown and typed layout metadata.

This **pdf to markdown converter** runs fully inside your Apify Actor container. It reads PDF text and layout locally, identifies likely headings, paragraphs, list items, table rows, links, and embedded-image references, and returns one integration-friendly record per document.

Use it for repeatable RAG ingestion, knowledge-base preparation, document publishing, archive migration, and PDF content analysis without sending document contents to a third-party conversion API.

### What does this Actor do?

For each supplied PDF, the Actor:

1. downloads an anonymous public PDF URL or reads a PDF from the run's default key-value store;
2. validates its size and PDF signature;
3. extracts page text and positioned text items;
4. infers headings, paragraphs, list items, and simple tables;
5. collects PDF link annotations and embedded-image references;
6. creates combined Markdown with optional page markers;
7. emits detailed page and block metadata to the default dataset;
8. optionally saves a `.md` file in the default key-value store.

Each successful document produces one charged `document` result. Failed inputs produce diagnostic dataset rows but are not charged as documents. Fields such as `error`, `warnings`, links, tables, and image references are included in the document record and are not separately charged.

### Who is it for?

#### RAG and AI engineers

Prepare PDFs for chunking, embeddings, vector databases, document Q\&A, and retrieval pipelines while retaining page references.

#### Data and knowledge teams

Normalize document collections into consistent Markdown and JSON-compatible block metadata on scheduled Apify runs.

#### Publishers and technical writers

Move text-first PDFs into editable Markdown while preserving useful document structure and links.

#### Automation developers

Call one Actor from JavaScript, Python, cURL, Apify Tasks, schedules, webhooks, or MCP tools.

### Why use this PDF to Markdown converter?

- **Local conversion:** no external conversion API key is required.
- **Batch input:** combine multiple public URLs and uploaded storage keys in one run.
- **Page-aware output:** Markdown can include `<!-- page: N -->` markers.
- **Typed blocks:** inspect page, type, Markdown, plain text, bounding box, list marker, heading level, table cells, and image reference.
- **Integration-ready:** the full record is available in the default dataset.
- **File-ready:** optional `.md` artifacts are saved to the default key-value store.
- **Bounded failures:** one invalid PDF does not discard successful conversions in the same batch.
- **SSRF protection:** URL inputs must resolve to publicly routable addresses.

### What data is extracted?

| Field | Meaning |
| --- | --- |
| `status` | `succeeded` or `failed` for this input |
| `sourceType` | `url` or `key-value-store` |
| `source` | Original URL or storage key |
| `finalUrl` | URL after redirects, when applicable |
| `fileName` | Sanitized PDF file name |
| `byteSize` | PDF size in bytes |
| `title`, `author` | PDF metadata when available |
| `pageCount` | Total pages in the PDF |
| `processedPageCount` | Pages processed under your safety limit |
| `markdown` | Combined Markdown for processed pages |
| `markdownKey` | Key of the generated `.md` artifact |
| `pages` | Page-level text, Markdown, blocks, links, and image references |
| `headings` | Document-wide heading index with levels and pages |
| `links` | Document-wide external-link index |
| `tables` | Inferred table rows plus Markdown |
| `imageReferences` | Stable references to image paint operations |
| `warnings` | Non-fatal limits or metadata issues |
| `error` | Per-document failure explanation |
| `convertedAt` | ISO 8601 conversion time |

Nullable fields are expected when a PDF does not contain the corresponding metadata.

### Getting started

1. Open the Actor input page.
2. Add one or more direct PDF URLs under **PDF URLs**.
3. Optionally add keys for PDFs already uploaded to the default key-value store.
4. Choose document, page, file-size, and timeout limits.
5. Keep **Include page markers** enabled for page-aware RAG workflows.
6. Keep **Save Markdown files** enabled when you need downloadable artifacts.
7. Start the run.
8. Open **Results** for structured records or **Markdown files** for `.md` artifacts.

A safe first input is:

```json
{
  "pdfUrls": [
    { "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf" }
  ],
  "maxDocuments": 1,
  "maxPagesPerDocument": 20,
  "includePageMarkers": true,
  "saveMarkdownFiles": true
}
```

### Input parameters

| Parameter | Type | Default | Description |
| --- | --- | ---: | --- |
| `pdfUrls` | array | sample PDF | Anonymous public HTTP(S) PDF URLs |
| `pdfFiles` | array | `[]` | Default key-value store keys and optional file names |
| `maxDocuments` | integer | `10` | Unique PDFs to process, from 1 to 100 |
| `maxPagesPerDocument` | integer | `200` | Page safety limit, from 1 to 1,000 |
| `maxFileSizeMb` | integer | `25` | Per-PDF size limit, from 1 to 100 MB |
| `requestTimeoutSecs` | integer | `45` | Per-attempt URL timeout, from 5 to 180 seconds |
| `includePageMarkers` | boolean | `true` | Add page comments to combined Markdown |
| `saveMarkdownFiles` | boolean | `true` | Save generated `.md` artifacts |

At least one item is required across `pdfUrls` and `pdfFiles`. Duplicate URLs and duplicate storage keys are processed once.

### How to convert an uploaded PDF file

Upload the binary PDF to the run's default key-value store with content type `application/pdf`, then reference its key:

```json
{
  "pdfFiles": [
    {
      "key": "quarterly-report",
      "fileName": "quarterly-report.pdf"
    }
  ],
  "saveMarkdownFiles": true
}
```

The storage value must be the binary PDF, not a JSON wrapper or a URL string.

### Output example

A real one-page sample produces the following shape (long arrays shortened):

```json
{
  "status": "succeeded",
  "sourceType": "url",
  "source": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "finalUrl": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf",
  "fileName": "dummy.pdf",
  "contentType": "application/pdf",
  "byteSize": 13264,
  "title": null,
  "author": null,
  "pageCount": 1,
  "processedPageCount": 1,
  "markdown": "<!-- page: 1 -->\n\nDummy PDF file",
  "markdownKey": "markdown-3d7d1b17f7597eed.md",
  "pages": [
    {
      "pageNumber": 1,
      "markdown": "Dummy PDF file",
      "text": "Dummy PDF file",
      "blocks": [
        {
          "type": "paragraph",
          "pageNumber": 1,
          "markdown": "Dummy PDF file",
          "text": "Dummy PDF file"
        }
      ],
      "links": [],
      "imageReferences": []
    }
  ],
  "headings": [],
  "links": [],
  "tables": [],
  "imageReferences": [],
  "warnings": [],
  "error": null
}
```

### How headings, lists, and tables are detected

The converter uses PDF text coordinates and font sizes rather than visual HTML semantics, because PDFs normally do not contain HTML-like heading tags.

- Larger, bounded text lines become inferred Markdown headings.
- Bullet and numbered prefixes become list items.
- Lines with at least three positionally separated cells become simple Markdown table rows.
- Other lines become paragraphs in page reading order.

The original text, approximate bounding box, page number, and inferred type remain available in `pages[].blocks[]` so downstream code can refine decisions.

### Links and image references

PDF link annotations are exported with target URL, page number, and annotation rectangle when available.

Embedded images are represented as stable references such as `page-2-image-1`. The Actor does not export image bytes or perform image OCR. References let downstream pipelines preserve figure positions without claiming that image contents were understood.

### RAG ingestion workflow

A practical recurring workflow is:

1. collect or upload the latest source PDFs;
2. run this Actor on an Apify schedule;
3. split `markdown` by page markers or `pages[].blocks[]`;
4. attach `source`, `pageNumber`, `title`, and block type as chunk metadata;
5. generate embeddings;
6. upsert chunks into your vector database;
7. compare source hashes or file metadata before replacing older chunks.

The Actor produces conversion output; it does not call an embedding model or vector database by itself.

### Publishing workflow

For Markdown publishing:

1. enable `saveMarkdownFiles`;
2. retrieve each `markdownKey` from the run's key-value store;
3. review inferred headings and complex tables;
4. rewrite image-reference anchors for your publishing platform;
5. commit the resulting Markdown to your content repository.

This is especially useful for text-first manuals, reports, policies, and papers.

### How much does it cost to convert PDF documents to Markdown?

Pay-per-event pricing has two parts:

- **Run start:** $0.005 once per run.
- **PDF converted:** BRONZE tier currently $0.012737 per successful document, with lower per-document prices at higher Apify tiers.

Examples at the BRONZE event price:

| Successful PDFs | Estimated Actor charge |
| ---: | ---: |
| 1 | $0.017737 |
| 10 | $0.13237 |
| 100 | $1.2787 |

Failed documents are included in the dataset for diagnosis but do not emit the `document` charge event. Platform compute usage is governed by Apify's applicable plan and the active pricing shown in Console; check the live pricing panel before large runs.

### JavaScript API example

```javascript
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('automation-lab/pdf-to-structured-markdown-converter').call({
  pdfUrls: [{ url: 'https://arxiv.org/pdf/1706.03762' }],
  maxDocuments: 1,
  maxPagesPerDocument: 15,
  includePageMarkers: true,
  saveMarkdownFiles: true,
});

const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items[0].markdown);
```

### Python API example

```python
import os
from apify_client import ApifyClient

client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('automation-lab/pdf-to-structured-markdown-converter').call(run_input={
    'pdfUrls': [{'url': 'https://www.irs.gov/pub/irs-pdf/fw9.pdf'}],
    'maxDocuments': 1,
    'maxPagesPerDocument': 6,
    'saveMarkdownFiles': True,
})

items = client.dataset(run['defaultDatasetId']).list_items().items
print(items[0]['processedPageCount'])
```

### cURL API example

```bash
curl -X POST \
  "https://api.apify.com/v2/acts/automation-lab~pdf-to-structured-markdown-converter/runs?token=$APIFY_TOKEN&waitForFinish=300" \
  -H 'Content-Type: application/json' \
  -d '{
    "pdfUrls": [{"url":"https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"}],
    "maxDocuments": 1,
    "saveMarkdownFiles": true
  }'
```

Use the returned `defaultDatasetId` to fetch structured output from the Dataset API.

### Use with MCP and AI agents

Add the Apify MCP endpoint to Claude Code:

```bash
claude mcp add --transport http apify \
  "https://mcp.apify.com?tools=automation-lab/pdf-to-structured-markdown-converter"
```

#### Claude Desktop

Add this server to Claude Desktop's MCP configuration:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=automation-lab/pdf-to-structured-markdown-converter"
    }
  }
}
```

#### Cursor

Add the same `apify` HTTP server URL in Cursor's MCP settings.

#### VS Code

Add the same `apify` HTTP server URL to your VS Code MCP server configuration.

Example prompts:

- “Convert this public PDF to page-aware Markdown and summarize its headings.”
- “Extract table metadata from the IRS W-9 PDF and return the page numbers.”
- “Prepare this technical paper as Markdown blocks for a RAG pipeline.”

### Scheduling and automation

Save a tested input as an Apify Task, then attach a schedule for recurring document processing. Webhooks can trigger downstream embedding, publishing, or archival jobs after successful runs.

For changing URLs, keep the source list in the Task input. For changing uploaded files, update the default key-value store keys before starting the Task.

### Limits and expected behavior

- Only PDF inputs are supported.
- URLs must be anonymous HTTP(S) endpoints on public network addresses.
- Redirects are limited to five and revalidated against private addresses.
- Transient downloads receive bounded retries; deterministic HTTP errors do not.
- Password-protected and malformed PDFs may fail.
- Scanned image-only PDFs require OCR and are not supported.
- Multi-column reading order is inferred and may need review.
- Borderless or irregular tables may remain paragraphs or separate table rows.
- Image bytes, captions, and OCR text are not extracted.
- `maxPagesPerDocument` truncation is reported in `warnings`.
- One failed document does not stop other valid inputs, but a run with no successful conversion fails visibly.

### Troubleshooting

#### “Input is not a PDF”

Confirm the URL returns a real PDF rather than an HTML login page, consent page, or download landing page. Direct responses must begin with the `%PDF-` signature.

#### “Only publicly routable PDF URLs are allowed”

Private, loopback, link-local, and credential-bearing URLs are blocked. Upload private files to the default key-value store and use `pdfFiles` instead.

#### The output contains little or no text

The source may be a scanned image-only PDF. This Actor does not perform OCR. Use a text-layer PDF or an OCR-focused Automation Lab workflow before conversion.

#### A table is split into paragraphs

PDFs store positioned glyphs, not semantic tables. Use `pages[].blocks[].bbox` and text values to add source-specific post-processing for complex layouts.

#### A large PDF times out

Raise `requestTimeoutSecs` for slow downloads, increase the run timeout when needed, or lower `maxPagesPerDocument`. Do not raise `maxFileSizeMb` beyond what your memory allocation can safely parse.

### Legality and responsible use

Process only PDFs you are authorized to access and transform. Respect copyright, contractual restrictions, privacy obligations, and applicable data-protection law.

Do not use this Actor to bypass authentication, access controls, paywalls, or private networks. URL inputs intentionally reject embedded credentials and non-public destinations.

Review generated Markdown before public redistribution. Structural inference is automated and does not transfer rights in the source document.

### Related Automation Lab Actors

- [Layout-Aware Document Text Extractor](https://apify.com/automation-lab/layout-aware-text-extractor) — use when you need plain text from public PDF, image, and web-page URLs rather than Markdown document records.
- [Bulk Image Format Converter](https://apify.com/automation-lab/bulk-image-format-converter) — convert extracted or separately downloaded images between common formats.
- [HTML Table to Excel Exporter](https://apify.com/automation-lab/html-table-to-excel-exporter) — export native HTML tables to XLSX when the source is a web page rather than a PDF.

### FAQ

#### Does it support batch PDF conversion?

Yes. Supply up to 100 unique URL and key-value-store inputs, bounded by `maxDocuments`.

#### Does it preserve pages?

Yes. Every block has a page number, every page has separate Markdown, and combined Markdown can contain page comments.

#### Does it extract tables?

It conservatively infers simple rows from positioned text columns. Complex layouts are not guaranteed to reconstruct perfectly.

#### Does it extract images?

It emits stable embedded-image references. It does not save image bytes or interpret image content.

#### Does it use AI?

No external model is required. Conversion uses deterministic local PDF parsing and layout heuristics.

#### Can it convert PDF to JPG, Excel, DOCX, or EPUB?

No. Those are separate file-conversion jobs. This Actor specifically converts PDF content to Markdown and structured metadata.

#### Can I process private documents?

Yes, by uploading the binary PDF to the run's default key-value store and referencing its key. Do not expose private files through public URLs.

#### What happens when one PDF fails?

The Actor writes a failed dataset record with a diagnostic message, continues with remaining inputs, and does not charge the document event for that failure.

#### What happens when every PDF fails?

The run fails after diagnostic rows are written, so schedules and webhooks can detect that no useful conversion was produced.

#### Can I use the output with LangChain or LlamaIndex?

Yes. Consume `markdown` directly or create chunks from `pages[].blocks[]`, then attach source and page metadata in your framework.

# Actor input Schema

## `pdfUrls` (type: `array`):

Public anonymous HTTP(S) URLs pointing directly to PDF files.

## `pdfFiles` (type: `array`):

PDF files already uploaded to this run's default key-value store. Enter each storage key and optional file name.

## `maxDocuments` (type: `integer`):

Maximum number of unique input PDFs to process.

## `maxPagesPerDocument` (type: `integer`):

Safety limit for pages parsed from each PDF.

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

Reject PDFs larger than this limit before parsing.

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

Timeout for each public URL request attempt.

## `includePageMarkers` (type: `boolean`):

Insert HTML page comments between pages in the combined Markdown.

## `saveMarkdownFiles` (type: `boolean`):

Save one .md artifact per successful document in the default key-value store.

## Actor input object example

```json
{
  "pdfUrls": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    }
  ],
  "pdfFiles": [],
  "maxDocuments": 10,
  "maxPagesPerDocument": 20,
  "maxFileSizeMb": 25,
  "requestTimeoutSecs": 45,
  "includePageMarkers": true,
  "saveMarkdownFiles": true
}
```

# Actor output Schema

## `dataset` (type: `string`):

Dataset containing one conversion record per input PDF.

## `files` (type: `string`):

Key-value store containing optional .md artifacts.

# 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 = {
    "pdfUrls": [
        {
            "url": "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("automation-lab/pdf-to-structured-markdown-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 = { "pdfUrls": [{ "url": "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("automation-lab/pdf-to-structured-markdown-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 '{
  "pdfUrls": [
    {
      "url": "https://www.w3.org/WAI/ER/tests/xhtml/testfiles/resources/pdf/dummy.pdf"
    }
  ]
}' |
apify call automation-lab/pdf-to-structured-markdown-converter --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automation-lab/pdf-to-structured-markdown-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/KrK5MewsdGz8fkFhG/builds/k4OdVTk71p6bladpe/openapi.json
