# P2M AI Feeder – PDF to Markdown Converter (`gtgyani206/p2m-aifeeder`) Actor

Convert digital and scanned PDFs into clean Markdown, structured JSON, and deterministic chunks for AI agents and RAG pipelines. Supports OCR, tables, image descriptions, batch processing, and downloadable artifacts.

- **URL**: https://apify.com/gtgyani206/p2m-aifeeder.md
- **Developed by:** [Gyanendra Thakur](https://apify.com/gtgyani206) (community)
- **Categories:** AI, Integrations, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.00 / 1,000 pdf page 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/platform/actors/running/actors-in-store#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

### What does P2M AI Feeder do?

**P2M AI Feeder converts uploaded PDFs and public PDF URLs into clean Markdown, deterministic chunks, optional image descriptions, and optional structured Docling JSON.** Its fast defaults read native PDF text and use fast table extraction. Opt-in local OCR handles scans and sufficiently large bitmap text, while a bundled local vision model can turn photos, diagrams, figures, and charts into searchable descriptions for AI agents, retrieval-augmented generation (RAG), search indexing, and document-analysis pipelines.

The Actor performs OCR, image understanding, and table extraction locally with [Docling](https://docling-project.github.io/docling/). It does not send document content to an external OCR, LLM, or vision API. Running it on Apify adds API access, schedules, integrations, run monitoring, and durable run-scoped storage.

### Why use P2M AI Feeder?

- Convert native-text PDFs, scanned pages, embedded screenshots, diagrams, charts, tables, and multi-column layouts through one input contract.
- Replace unhelpful `<!-- image -->` markers with locally generated descriptions of detected pictures.
- Enable local OCR for scans and text-bearing bitmap regions when the native text layer is insufficient.
- Consume chunks directly from the default dataset or retrieve complete artifacts from the key-value store.
- Preserve heading context in each chunk for better retrieval and agent grounding.
- Process large PDFs in bounded page ranges while reusing one isolated Docling worker instead of reloading its models for every range.
- Persist each completed page range immediately, preserve usable Docling partial output, and report failed ranges without discarding the rest of the PDF.
- Automatically retry a timed-out enrichment range without image descriptions and then without table structure before recording a page-level failure.
- Isolate per-document errors by default, or stop immediately with fail-fast mode.
- Protect the downloader with file-size limits, PDF signature checks, bounded retries, redirect validation, and blocking of private network targets.

### How to convert PDF files to AI-ready Markdown

1. Open the Actor's **Input** tab.
2. Upload one or more PDF files, or paste public HTTP(S) PDF URLs.
3. Leave **OCR** and **Describe images** off for ordinary digital PDFs. Enable OCR for scans and image-based text; enable image descriptions only when semantic visual content is required.
4. Enable **Force full-page OCR** only when a scan has a broken or misleading hidden text layer.
5. Adjust chunk size and overlap for your model's context window.
6. Click **Start**.
7. Read document and chunk rows in the dataset, or open the complete files from the run output.

No browser, proxy, or external API key is required.

### Input

See the **Input** tab for all configuration options. A typical API input is:

```json
{
  "sources": ["https://example.com/sample-report.pdf"],
  "doOcr": false,
  "forceFullPageOcr": false,
  "describeImages": false,
  "imageDescriptionPrompt": "Describe the image for retrieval. Extract important readable text and explain the main visual structure. Do not guess.",
  "extractTables": true,
  "tableMode": "fast",
  "pageBatchSize": 6,
  "chunkSizeChars": 6000,
  "chunkOverlapChars": 500,
  "includeMarkdownInDataset": false,
  "includeChunksInDataset": true,
  "exportDoclingJson": false,
  "maxDoclingJsonPages": 50,
  "maxPages": 250,
  "maxFileSizeMb": 50,
  "doclingTimeoutSeconds": 540,
  "workerTimeoutSeconds": 600,
  "failFast": false
}
```

`chunkOverlapChars` must be smaller than `chunkSizeChars`. `workerTimeoutSeconds` must be at least 30 seconds greater than `doclingTimeoutSeconds`, giving Docling time to serialize cooperative partial output before the subprocess is stopped. The older `documentTimeoutSeconds` input remains a compatibility alias for the hard worker timeout.

Full Markdown is always stored as a file; `includeMarkdownInDataset` only controls whether it is duplicated into the document dataset row.

#### OCR and image descriptions are different

- `doOcr` recognizes readable characters in scanned pages and sufficiently large bitmap regions. The conservative area threshold avoids sending tiny icons and decorations through CPU OCR.
- `forceFullPageOcr` replaces the native text layer with OCR on every page. It is useful for scans with a bad hidden text layer, but it is slower and can reduce accuracy on normal text PDFs.
- `describeImages` uses the bundled **SmolVLM-256M** model to explain a picture's visible content and relationships. It is disabled by default because CPU inference can dominate runtime. Small pictures are skipped, descriptions are capped at 100 new tokens, and the generated description is included in Markdown (and Docling JSON when that export is enabled) instead of returning only an image placeholder.
- `imageDescriptionPrompt` lets you focus descriptions on labels, numbers, workflow relationships, chart values, or other domain-specific details.

OCR and vision descriptions are machine-generated. Verify critical numeric, medical, legal, or financial information against the original document.

### Output

The dataset starts with a document summary and, when enabled, follows it with chunk rows:

```json
[
  {
    "type": "document",
    "documentId": "a1b2c3d4e5f607182930",
    "fileName": "sample-report.pdf",
    "status": "partial",
    "pageCount": 24,
    "convertedPageCount": 22,
    "imageCount": 6,
    "describedImageCount": 6,
    "chunkCount": 9,
    "failedPageRanges": ["13-18"],
    "warnings": [
      {
        "code": "PAGE_RANGE_FAILED",
        "message": "Pages 13-18 could not be converted after bounded attempts."
      }
    ],
    "markdownUrl": "https://api.apify.com/..."
  },
  {
    "type": "chunk",
    "documentId": "a1b2c3d4e5f607182930",
    "chunkIndex": 0,
    "headings": ["# Sample report", "## Findings"],
    "text": "# Sample report\n\n## Findings\n\nExample content..."
  }
]
```

You can download the dataset in various formats such as JSON, HTML, CSV, or Excel. Complete Markdown, chunk JSON, durable page-range Markdown, and optional per-range Docling JSON use the Apify-compatible `document-` key prefix. The `OUTPUT` key is updated after every processed range and contains the current manifest, part URLs, warnings, and completed-page progress.

### Data table

| Field | Type | Description |
| --- | --- | --- |
| `type` | string | `document`, `chunk`, or `error` row |
| `documentId` | string | Stable SHA-256-derived identifier for the PDF bytes |
| `status` | string | `succeeded`, `partial`, or `failed` |
| `pageCount` | integer | Source PDF page count |
| `convertedPageCount` | integer | Pages that produced usable output |
| `imageCount` | integer | Picture elements detected by Docling |
| `describedImageCount` | integer | Pictures enriched with a local SmolVLM description |
| `chunkIndex` | integer | Zero-based chunk position |
| `headings` | array | Active Markdown heading hierarchy |
| `text` | string | Agent-ready Markdown chunk |
| `markdownUrl` | string | URL of the complete Markdown artifact |
| `chunksUrl` | string | URL of the complete chunk JSON artifact |
| `doclingJsonUrl` | string or null | URL of structured Docling JSON when enabled |
| `warnings` | array | Non-fatal timeout and controlled-degradation notices |
| `failedPageRanges` | array | Page ranges unavailable after bounded fallback attempts |
| `error` | string | Sanitized per-document failure information |

### How much does it cost to convert PDFs?

This Actor uses transparent **pay-per-event pricing** based on the conversion work that produces usable output:

| Event | Price | When it is charged |
| --- | ---: | --- |
| Actor start | $0.0025 | Once per memory-scaled Actor start event |
| PDF page processed | $0.008 per page | After a successfully converted page batch has been saved as Markdown |
| Image described | $0.015 per image | Only for a non-empty local vision-model description saved in the Markdown |

Dataset document and chunk rows are not the billing unit, so changing `chunkSizeChars` or enabling chunk rows does not increase the conversion price. A 10-page PDF with no described images costs approximately **$0.08 plus the memory-scaled start charge**. Enabling image descriptions adds $0.015 for each image that receives a saved description.

The Actor checks the user's maximum run charge after every persisted page batch. If the limit is reached, it stops before starting more conversion work and keeps all completed page-range artifacts available. For predictable spend, test a representative PDF first and leave `describeImages` disabled for text-only collections. The Apify Console estimate is authoritative for the memory-scaled start charge and any future pricing-tier discounts.

### Tips and advanced options

- Keep image descriptions disabled for text-only PDFs.
- Keep OCR disabled when PDFs have reliable embedded text; enable it for scans or text-bearing images.
- Use forced full-page OCR only for scans or broken hidden text layers.
- Customize the image prompt for diagrams, charts, product screenshots, or scientific figures.
- Use fast table mode for simple, high-volume documents.
- Set `extractTables` to `false` for plain-text documents that do not need row-and-column reconstruction.
- Enable accurate table mode only for documents whose complex tables justify the extra CPU time.
- Use smaller `pageBatchSize` values (2–4) for scanned or unusually slow PDFs. The default of 6 is intended for ordinary digital or mixed documents.
- Enable Docling JSON only when downstream consumers need the full layout/provenance model. When disabled, the Actor skips building and transferring that structure. It is also skipped when the PDF exceeds `maxDoclingJsonPages`; Markdown and chunks continue normally.
- Keep full Markdown out of the dataset for large PDFs and follow `markdownUrl` instead.
- Disable chunk dataset rows when another service will retrieve `chunksUrl`.
- Use asynchronous Actor runs for large PDFs; synchronous API callers may time out before Docling finishes.
- `doclingTimeoutSeconds` is a cooperative limit for one page range. `workerTimeoutSeconds` is the larger hard boundary that can terminate image enrichment or another stage that does not stop cooperatively. Download requests are separately capped at 120 seconds.
- The Actor logs a heartbeat every 30 seconds and reports converter construction versus conversion/export timing for each page range. Docling initializes most models lazily, so the first range can include model startup.
- Multiple PDFs and ranges remain sequential to protect memory. The persistent worker retains a compatible Docling converter between ranges; after a hard timeout, the next attempt starts a clean worker.
- CPU inference threads follow `APIFY_DEDICATED_CPUS` when available. On Apify, 8 GB currently corresponds to 2 CPU cores, matching this Actor's default memory allocation.

For a fast diagnostic run, use one small PDF with OCR, image descriptions, and Docling JSON disabled:

```json
{
  "sources": ["https://example.com/sample.pdf"],
  "doOcr": false,
  "forceFullPageOcr": false,
  "describeImages": false,
  "tableMode": "fast",
  "exportDoclingJson": false,
  "includeMarkdownInDataset": false,
  "includeChunksInDataset": false,
  "maxPages": 20,
  "pageBatchSize": 6,
  "doclingTimeoutSeconds": 240,
  "workerTimeoutSeconds": 300
}
```

Then enable OCR, accurate tables, Docling JSON, and image descriptions one at a time and compare each document row's `durationMs`.

### Local development

Use the Apify CLI so local storage and Actor environment variables match the platform:

```bash
apify run
```

Place local input at `storage/key_value_stores/default/INPUT.json`. Local run output remains under `storage/` and is not synchronized to Apify Console.

### FAQ, disclaimers, and support

#### Can it read password-protected PDFs?

Not currently. Remove the password before uploading the file.

#### Can it read private intranet URLs?

No. Remote downloads intentionally reject private, loopback, link-local, reserved, and other non-public IP addresses. Upload the PDF through the Input tab instead.

#### Is document content sent to an LLM?

No external service receives the document. Docling, EasyOCR, and SmolVLM run inside the Actor container. The required model files are downloaded while the Actor image is built and the runtime is configured for offline inference. The resulting content is written only to the run's Apify storages.

#### Are image and chart descriptions exact?

No vision model can guarantee exact interpretation of every image. EasyOCR extracts readable text, while SmolVLM supplies semantic descriptions. Native PDF tables continue through Docling's structured table extractor. For charts, always verify important values against the source image.

Only process documents you have the legal right to access and transform. PDFs may contain personal, confidential, copyrighted, or regulated information; you are responsible for lawful input, retention, sharing, and downstream use. Review your Apify storage access settings before processing sensitive material.

Use the Actor's **Issues** tab for bugs, conversion samples, or feature requests, and the **API** tab for programmatic integration.

# Actor input Schema

## `sources` (type: `array`):

Upload one or more PDFs, or provide publicly reachable HTTP(S) PDF URLs.

## `doOcr` (type: `boolean`):

Use local EasyOCR for scanned pages and text inside sufficiently large bitmap images. CPU-intensive; enable it for scans or image-based text.

## `forceFullPageOcr` (type: `boolean`):

Replace the PDF text layer with OCR for every page. Enable only for scans or PDFs with a broken hidden text layer because it is slower and can reduce native-text accuracy.

## `describeImages` (type: `boolean`):

Use the bundled local SmolVLM model to turn photos, diagrams, figures, and charts into searchable Markdown descriptions. This is CPU-intensive and disabled by default. No external AI API is used.

## `imageDescriptionPrompt` (type: `string`):

Instructions for the local vision model. Ask for the visual details most useful to your downstream agent or RAG pipeline.

## `extractTables` (type: `boolean`):

Reconstruct table rows and columns. Disable this for plain-text documents or as a last-resort fallback when table analysis is too expensive.

## `tableMode` (type: `string`):

Fast mode is the practical default. Accurate mode is slower and intended for complex tables.

## `pageBatchSize` (type: `integer`):

Convert and persist this many pages at a time. Smaller batches isolate slow pages and preserve more progress; larger batches have less overhead.

## `chunkSizeChars` (type: `integer`):

Approximate maximum number of characters per agent-ready chunk.

## `chunkOverlapChars` (type: `integer`):

Approximate content overlap retained between adjacent chunks.

## `includeMarkdownInDataset` (type: `boolean`):

Embed full Markdown in document rows for small PDFs; the key-value-store copy is always created.

## `includeChunksInDataset` (type: `boolean`):

Push one dataset row per chunk so agents can consume content directly.

## `exportDoclingJson` (type: `boolean`):

Build and store Docling's lossless document representation with layout and provenance metadata. Disabled by default to avoid serializing a large structure that many runs do not use.

## `maxDoclingJsonPages` (type: `integer`):

Skip structured Docling JSON when a PDF exceeds this page count, even if exportDoclingJson is enabled. Markdown and chunks are still produced.

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

Reject an individual PDF when its page count exceeds this limit.

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

Reject an individual PDF when its downloaded size exceeds this limit.

## `doclingTimeoutSeconds` (type: `integer`):

Cooperative Docling processing limit for one page batch. Docling may return usable partial output when this limit is reached.

## `workerTimeoutSeconds` (type: `integer`):

Hard subprocess limit for one page batch. Must be at least 30 seconds greater than the Docling timeout so partial output has time to serialize.

## `documentTimeoutSeconds` (type: `integer`):

Deprecated compatibility alias used only when workerTimeoutSeconds is absent. New inputs should use the separate Docling and worker batch timeouts.

## `failFast` (type: `boolean`):

When disabled, report a failed document and continue processing the remaining PDFs.

## Actor input object example

```json
{
  "doOcr": false,
  "forceFullPageOcr": false,
  "describeImages": false,
  "imageDescriptionPrompt": "Describe the image for retrieval. Extract important readable text and explain the main visual structure. Do not guess.",
  "extractTables": true,
  "tableMode": "fast",
  "pageBatchSize": 6,
  "chunkSizeChars": 6000,
  "chunkOverlapChars": 500,
  "includeMarkdownInDataset": false,
  "includeChunksInDataset": true,
  "exportDoclingJson": false,
  "maxDoclingJsonPages": 50,
  "maxPages": 250,
  "maxFileSizeMb": 50,
  "doclingTimeoutSeconds": 540,
  "workerTimeoutSeconds": 600,
  "failFast": false
}
```

# Actor output Schema

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

Dataset containing succeeded or partial document summaries, chunks, and isolated document errors.

## `documentFiles` (type: `string`):

Key-value-store collection containing merged Markdown, chunk JSON, durable page-range Markdown, and optional per-range Docling JSON.

## `manifest` (type: `string`):

Progress summary persisted after every page range, including part URLs, warnings, and final document outcomes.

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("gtgyani206/p2m-aifeeder").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 = {}

# Run the Actor and wait for it to finish
run = client.actor("gtgyani206/p2m-aifeeder").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print("💾 Check your data here: https://console.apify.com/storage/datasets/" + run["defaultDatasetId"])
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call gtgyani206/p2m-aifeeder --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "command": "npx",
            "args": [
                "mcp-remote",
                "https://mcp.apify.com/?tools=gtgyani206/p2m-aifeeder",
                "--header",
                "Authorization: Bearer <YOUR_API_TOKEN>"
            ]
        }
    }
}

```

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/acts/9DVReaUZ0iBNFdxm3/builds/00Ay6FKjcKsPrivgc/openapi.json
