# Digital PDF to Markdown — Ruled Tables & Page Citations (`keyed_gusto/pdf-to-markdown-ruled-tables`) Actor

Extract text and page-local ruled tables from authorized digital PDFs without personal data. Optional page-cited chunks. No OCR or cross-page table merging.

- **URL**: https://apify.com/keyed_gusto/pdf-to-markdown-ruled-tables.md
- **Developed by:** [Cristian Blanco](https://apify.com/keyed_gusto) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$1.00 / 1,000 processing units

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

## Digital PDF to Markdown — Ruled Tables & Page Citations

Extract text and **page-local ruled table segments** from authorized digital PDFs.
Optional character-based chunks include their source page numbers. No OCR.

### Accepted documents

Public HTTP(S) PDFs with a text layer: product manuals, technical specifications,
instructions, or synthetic examples, **without personal or sensitive data**.
You must be authorized to process every file and explicitly confirm this scope.
Do not submit invoices, statements, health/legal records, personal-finance documents,
personal data, private/signed URLs, or documents requiring authentication.
The Actor is not a personal-data detector; the confirmation does not inspect the file.

### Output

One dataset item per converted or rejected PDF, in input order. If the spending or
run-time limit stops the batch, remaining documents have no dataset item; see
the run status and PILOT_PROGRESS in the run key-value store.

- status: ok, rejected, or error.
- document_id and url identify the input.
- markdown contains  markers.
- tables contains page, bbox and rows for every ruled table segment.
- tables_found counts segments, not merged logical tables.
- chunks contains text and exact source pages.
- pages_without_text lists skipped pages. Blank pages and pages without extractable
  text are skipped; this is not a scan classifier.
- processing_units is the expected billable unit count; zero for rejections.

Tables on different pages are **never automatically merged**, even if their
headings match. In Markdown, the first row of each segment is rendered as its
header; table rows are also supplied as JSON without inferring a semantic header.

### Limitations

- Ruled tables only. Borderless tables remain plain text; row/column structure
  is not promised.
- No reliable reconstruction of merged cells or arbitrary reading order.
- Two-column reading order is heuristic. No visual or semantic guarantee.
- Large blocks can be split by the chunk character limit.
- Synthetic test results are regression evidence, not real-world accuracy claims.

### Price

Configure one event: **processing-unit**, $0.001 per unit at the base price.

A successful document uses **5 document units + 1 unit per processed text page**.
For example, 10 processed pages use 15 units = **$0.015** at the base price.
Check the actual Store price and any plan discounts before running.

Rejected documents, failed downloads, wholly textless PDFs and skipped textless
pages have no processing-unit charge. The document's complete required unit
count is checked against the remaining spending limit before saving its output.

Apify's billing and dataset writes are separate operations. Output is saved
before billing so you can access it if the run stops. A platform/network interruption
between these operations can leave accessible output incompletely billed.
The Actor stops on an unexpected receipt and never retries that run's work or
billing after resurrection. Existing results remain available.

All other events, including automatic start/dataset events, must be disabled or
priced at zero. Platform subscription charges are separate from this event price.

### Input

```json
{
  "pdfUrls": ["https://example.com/authorized-technical-manual.pdf"],
  "confirmNoPersonalData": true,
  "maxPagesPerDocument": 150,
  "chunkSize": 1500
}
```

Replace the example URL with an authorized public PDF.

### Limits

- 1–10 URLs per run; HTTP 80 or HTTPS 443 only.
- 50 MiB per PDF, 150 pages by default, maximum 200.
- 60 seconds per document including download and parsing.
- 240 seconds for document processing per batch, excluding platform startup,
  storage and billing requests.
- Linux subprocess address-space limit: 768 MiB; output JSON maximum 6 MiB.
- chunkSize: 0 or 200–20,000 characters.
- Encrypted PDFs and OCR are unsupported.
- Private networks, credentials, unsupported URL schemes, HTTP compression and
  HTTPS-to-HTTP redirects are rejected.

### Data handling

The PDF bytes are processed in a bounded subprocess and are not intentionally
saved as a source file by this code. The input (including URLs), converted
content, run records and outputs can be retained by **Apify** in the run's
dataset and key-value store according to the account settings.
Finishing a run is **not immediate deletion**. Named storage may be kept
indefinitely. Manage access and retention in Apify Console.

No document text or full URLs are intentionally written to application logs.
Platform-generated logs and metadata follow Apify's behavior.
The operator must not describe this service as zero-retention or certified
free of personal data.

### Local use

Python 3.12 is the tested environment. No Apify account is needed:

```sh
python -m pip install -r requirements-dev.txt
python -m unittest discover -s tests -p test_regressions.py -v
python -m src.local path/to/authorized.pdf --chunks 1500
```

The local command does not bill anyone.
See piloto/PILOTO.md for deployment and the remaining cloud check.

# Changelog

This Actor's version history is a separate document: https://apify.com/keyed_gusto/pdf-to-markdown-ruled-tables/changelog.md

# Actor input Schema

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

Provide authorized public PDF URLs. The prefilled PDF contains a synthetic demonstration table.

## `confirmNoPersonalData` (type: `boolean`):

Confirm authorization and absence of personal or sensitive data. Only the exact prefilled synthetic demo can run without this confirmation.

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

Reject the whole PDF if this limit is exceeded. Hard maximum 200.

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

0 disables chunks; otherwise choose 200–20,000. Oversized blocks may be split. Source page numbers are retained.

## Actor input object example

```json
{
  "pdfUrls": [
    "https://api.apify.com/v2/key-value-stores/0hVpqGeBcTeDluekI/records/tabla_sintetica.pdf?signature=SHB0BuX7vZ8BEJII2vkD"
  ],
  "confirmNoPersonalData": false,
  "maxPagesPerDocument": 150,
  "chunkSize": 0
}
```

# Actor output Schema

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

No description

# 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": [
        "https://api.apify.com/v2/key-value-stores/0hVpqGeBcTeDluekI/records/tabla_sintetica.pdf?signature=SHB0BuX7vZ8BEJII2vkD"
    ],
    "confirmNoPersonalData": false
};

// Run the Actor and wait for it to finish
const run = await client.actor("keyed_gusto/pdf-to-markdown-ruled-tables").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": ["https://api.apify.com/v2/key-value-stores/0hVpqGeBcTeDluekI/records/tabla_sintetica.pdf?signature=SHB0BuX7vZ8BEJII2vkD"],
    "confirmNoPersonalData": False,
}

# Run the Actor and wait for it to finish
run = client.actor("keyed_gusto/pdf-to-markdown-ruled-tables").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": [
    "https://api.apify.com/v2/key-value-stores/0hVpqGeBcTeDluekI/records/tabla_sintetica.pdf?signature=SHB0BuX7vZ8BEJII2vkD"
  ],
  "confirmNoPersonalData": false
}' |
apify call keyed_gusto/pdf-to-markdown-ruled-tables --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,keyed_gusto/pdf-to-markdown-ruled-tables"
        }
    }
}
```

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/MLpHCVye2z4diS5wK/builds/wq2HD4hKHknx5GlA2/openapi.json
