Invoice, Receipt & PDF Table to JSON/Markdown OCR
Pricing
$15.00 / 1,000 pdf document parseds
Invoice, Receipt & PDF Table to JSON/Markdown OCR
Extracts structured JSON tables and clean LLM Markdown from PDF invoices, bank statements, and financial reports using IBM Docling.
Pricing
$15.00 / 1,000 pdf document parseds
Rating
0.0
(0)
Developer
Panix AI LAB
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
18 hours ago
Last modified
Categories
Share
Invoice, Receipt & PDF Table to JSON / Markdown OCR
Turn PDF invoices, receipts, bank statements and scanned documents into structured JSON tables and clean, LLM-ready Markdown. The Actor reads born-digital PDFs directly from their text layer and runs OCR only on pages that need it: scanned pages, photos and image files (JPG, PNG, TIFF, BMP, WEBP). Table structure is reconstructed with IBM Docling (layout analysis + TableFormer), and OCR uses RapidOCR (PP-OCRv6). All models are bundled in the Actor, so you need no external API keys and no document leaves the run for a third-party AI service.
What does it do?
For each document URL you provide, the Actor:
- Downloads the file over a validated http(s) connection and checks that it really is a PDF or an image.
- Looks at every PDF page: does it have a real embedded text layer, or is it a scan?
- Parses digital pages without OCR (fast and cheap) and runs full-page OCR on scanned pages and images.
- Detects tables, including borderless ones, and exports every table as JSON (
headersplus row objects). - Exports the whole document as Markdown that keeps headings, reading order and tables.
- Saves one result per document to the dataset, with page counts, warnings and a clear status.
Use cases
- Invoice OCR and invoice data extraction: line items, totals, invoice numbers and due dates to JSON for accounting, ERP or Google Sheets.
- Receipt OCR: photos or scans of receipts to text and tables for expense tools.
- Bank statement parser: transaction tables from PDF statements to JSON or CSV-ready rows.
- Financial reports and price lists: multi-page PDF table extraction.
- Scanned PDF to Markdown or text: archives, contracts and letters without a text layer.
- RAG and AI agents: Markdown for vector databases (Pinecone, Qdrant, Supabase) and MCP agents.
- No-code automation: call it from n8n, Make or Zapier and map
tables_jsoninto your workflow.
Supported inputs
- PDF (born-digital, scanned, or mixed), and images: JPG, PNG, TIFF (multi-page included), BMP, WEBP.
- Each URL must be a direct, public download link that returns the file itself (not an HTML preview page). Signed links such as S3 pre-signed URLs work.
- Only
http://andhttps://links to public hosts are accepted. Local files, private network addresses and links with embedded credentials are rejected. - Password-protected PDFs are not supported; they come back as
failedwith a clear message.
Input
| Field | Type | Default | Description |
|---|---|---|---|
documentUrls | array of strings | required | Direct links to PDFs or images. Duplicates are removed. Objects like {"url": "..."} are accepted too. |
ocrMode | auto / always / never | auto | How OCR is used, see below. |
extractTablesAsJson | boolean | true | Export tables as JSON in tables_json. Tables are always included in the Markdown. |
maxPages | integer (1-2000) | 100 | Parse at most this many pages per document, starting from page 1. |
maxFileSizeMb | integer (1-200) | 50 | Larger files are rejected before parsing. |
maxDocuments | integer (1-1000) | 100 | Process at most this many URLs per run; extra URLs are ignored with a warning. |
documentTimeoutSecs | integer (30-3600) | 300 | Time limit per document; pages finished so far are returned as a partial result. |
Example input:
{"documentUrls": ["https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"],"ocrMode": "auto","extractTablesAsJson": true,"maxPages": 100,"maxFileSizeMb": 50,"maxDocuments": 100,"documentTimeoutSecs": 300}
OCR modes
auto(recommended): every PDF page is checked for an embedded text layer. Pages with enough real text are parsed directly, without OCR, and images or logos inside them are never OCR'd. Pages with little or no text (scans, photos, image-only pages) get full-page OCR. In a mixed PDF, only the scanned pages are OCR'd: the document is processed in runs of consecutive pages of the same kind and the results are merged back in page order. Pages that already carry an invisible OCR text layer count as digital. Image files always get OCR.always: full-page OCR on every page, even if a text layer exists. Use it when the embedded text is broken or garbled.never: no OCR at all. Scanned pages come back empty (with a warning) and image files fail with a message asking forautooralways.
Output
One dataset item per document. You can download the dataset as JSON, CSV, Excel or XML, or open the Overview, Tables and Markdown views in the Output tab.
| Field | Description |
|---|---|
source_url | The URL from your input. |
file_name | File name taken from the URL. |
document_type | pdf or image (null if the file could not be downloaded or recognised). |
status | success, partial_success (some pages missing, see warnings), failed or skipped (see error_message). |
error_message | Why the document failed or was skipped; null on success. |
warnings | Non-fatal notes, for example pages cut by maxPages or a timeout. |
ocr_mode | The OCR mode used. |
pages_total | Pages (or image frames) in the file. |
pages_processed | Pages parsed in this run. |
pages_digital | Pages parsed from the text layer, without OCR. |
pages_ocr | Pages parsed with OCR. |
table_count | Number of tables in tables_json. |
tables_json | Tables with table_index, page_number, headers, rows, row_count, column_count. |
full_markdown | Markdown of the processed pages. |
markdown_url | Set only for very large documents: link to the Markdown file in the key-value store (then full_markdown is null). |
tables_url | Set only when the tables alone are too large for a dataset item: link to the tables JSON (then tables_json is empty). |
markdown_length | Characters in the Markdown. |
processing_time_secs | Seconds spent on this document. |
processed_at | UTC timestamp. |
Headers are always unique (duplicates become Amount, Amount_2; empty ones become column_3) so no cell is lost, and empty cells are null. Tables without a header row use 0, 1, 2 as column names.
Sample item (shortened):
{"source_url": "https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf","file_name": "wordpress-pdf-invoice-plugin-sample.pdf","document_type": "pdf","status": "success","error_message": null,"warnings": [],"ocr_mode": "auto","pages_total": 1,"pages_processed": 1,"pages_digital": 1,"pages_ocr": 0,"table_count": 3,"tables_json": [{"table_index": 1,"page_number": 1,"headers": ["Hrs/Qty", "Service", "Rate/Price", "Adjust", "Sub Total"],"rows": [{"Hrs/Qty": "1.00","Service": "Web Design This is a sample description...","Rate/Price": "$85.00","Adjust": "0.00%","Sub Total": "$85.00"}],"row_count": 1,"column_count": 5}],"full_markdown": "## From:\nDEMO - Sliced Invoices ...\n\n| Hrs/Qty | Service | Rate/Price | Adjust | Sub Total |\n|---|---|---|---|---|\n| 1.00 | Web Design ... | $85.00 | 0.00% | $85.00 |\n...","markdown_url": null,"tables_url": null,"markdown_length": 909,"processing_time_secs": 6.1,"processed_at": "2026-10-20T08:00:00.000000+00:00"}
A failed document looks like this, and never stops the rest of the run:
{"source_url": "https://example.com/invoice","status": "failed","error_message": "The URL returned an HTML page, not a file. Use a direct download link to the PDF or image.","pages_processed": 0}
Pricing
The Actor uses pay-per-event pricing, charged per page, so you pay for what was actually parsed:
page-parsed: each page read from its embedded text layer (digital PDF pages).page-ocr: each page that needed OCR (scanned pages, photos, image files). OCR takes much more compute, so this event costs more than a digital page.
The current prices are shown on the Actor's Pricing tab. What you are not charged for:
- Documents that fail or are skipped.
- Pages beyond
maxPages, or pages that were not processed because of a timeout. - Failed page ranges inside a partial result.
The Actor respects your maximum charge per run. Before each document it checks how many pages still fit in your limit, parses only those, and marks the rest as skipped or partial_success, so a run never goes over the amount you set.
Limits and performance
- Default memory is 4 GB, which is enough for long documents because pages are converted in chunks of 10. More memory also gives more CPU, which speeds up OCR.
- Digital pages take a few seconds each; OCR pages take longer. The first page of a run also loads the models.
- The Actor watches the run timeout. When it gets close, it stops starting new work and saves what it has, so the run still finishes successfully. Documents that were not started are listed as
skipped. For big batches, raise the run timeout or split the input. - OCR uses the multilingual PP-OCRv6 model (best for Latin-script and Chinese text). Handwriting and very low-resolution photos are recognised less reliably.
- A dataset item can hold about 9 MB. For bigger documents the Markdown (and if needed the tables) is saved to the run's key-value store and linked from
markdown_url/tables_url.
Use it via API or integrations
Run the Actor and get the results in one call (replace <YOUR_API_TOKEN>):
curl -X POST "https://api.apify.com/v2/acts/panix~invoice-table-json-extractor/run-sync-get-dataset-items?token=<YOUR_API_TOKEN>" \-H "Content-Type: application/json" \-d '{"documentUrls": ["https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"], "ocrMode": "auto"}'
Python with the apify-client package:
from apify_client import ApifyClientclient = ApifyClient("<YOUR_API_TOKEN>")run = client.actor("panix/invoice-table-json-extractor").call(run_input={"documentUrls": ["https://slicedinvoices.com/pdf/wordpress-pdf-invoice-plugin-sample.pdf"]})for item in client.dataset(run["defaultDatasetId"]).iterate_items():print(item["status"], item["table_count"], item["pages_ocr"])
It also works with the Apify integrations for n8n, Make, Zapier, Google Drive, Slack and webhooks, and as a tool for AI agents through the Apify MCP server.
Data and privacy
- Documents are downloaded only from the URLs you provide and processed inside your Actor run. All AI models run locally in the container; nothing is sent to an external AI or OCR service.
- The extracted content (Markdown and tables) is stored in your run's dataset and key-value store and follows your Apify data retention settings. Invoices and statements often contain personal data such as names, addresses and account numbers; you are responsible for having the right to process them.
- Logs show only the host and path of each URL, never query strings, so signed-URL tokens stay out of the log.
FAQ
Does it work with scanned PDFs?
Yes. In auto mode, pages without a text layer are OCR'd automatically, and in mixed PDFs only those pages are OCR'd and billed as page-ocr.
Why does an OCR page cost more than a digital page? OCR runs a text detection and recognition model on the full page image, which takes several times more compute than reading the existing text layer.
My document came back as partial_success. Why?
Some pages were not processed: the per-document timeout, the run timeout or your charge limit was reached, or Docling could not read some pages. The warnings field says which. You are only charged for the pages that were returned.
What about password-protected PDFs?
They are not supported; the item is failed with a message asking you to remove the password.
Which languages does OCR support? The PP-OCRv6 model handles Latin-script languages (English, German, French, Spanish and more) and Chinese well. Other scripts may give weaker results.
Can I send Google Drive or Dropbox links? Only if the link downloads the file directly. Preview pages return HTML and fail with a clear message.
How do I process hundreds of documents?
Pass them all in documentUrls (up to maxDocuments) and give the run a longer timeout. Each document is independent: one broken file does not affect the others.
Support
Found a document that does not parse well, or need another feature? Open an issue in the Actor's Issues tab and include the URL (or a similar sample) and the run ID.