Invoice Data Extractor: PDF & Image to JSON (No LLM) avatar

Invoice Data Extractor: PDF & Image to JSON (No LLM)

Pricing

Pay per event

Go to Apify Store
Invoice Data Extractor: PDF & Image to JSON (No LLM)

Invoice Data Extractor: PDF & Image to JSON (No LLM)

Extract vendor, invoice number, dates, line items, tax, total and currency from invoice PDFs and clean images as JSON. Rule-based with local OCR: no LLM, no API keys, documents never leave the run. Rejects non-invoices and unreadable photos free.

Pricing

Pay per event

Rating

0.0

(0)

Developer

Nick Hellmich

Nick Hellmich

Maintained by Community

Actor stats

0

Bookmarked

2

Total users

1

Monthly active users

2 days ago

Last modified

Categories

Share

Invoice Data Extractor: PDF & Image to JSON (rule-based, no LLM)

Turn invoice PDFs and clean invoice images into structured JSON: vendor, invoice number, dates, line items, subtotal, tax, total, currency, IBAN, with a confidence level on every field and a check that the numbers add up. Extraction is rule-based with local OCR (Tesseract): no LLM, no outside API calls, no API keys, and your documents never leave the run. Results are deterministic (same file, same output) and cost a fraction of a cent of compute per digital invoice.

It also reads receipts, forms, tables and custom fields, but digital invoices are the supported core. Phone photos of receipts and handwritten invoices are best-effort and clearly weaker (numbers below). If you need near-perfect reading of photos or arbitrary layouts, use a vision-LLM service instead.

Who it is for

  • Accounts-payable and bookkeeping pipelines that receive invoice PDFs by email or upload and need structured rows with a "needs review" flag.
  • Developers who want a predictable, cheap, self-contained extractor (no LLM bill, no data sent to third parties) and who will route low-confidence documents to a human.

What is supported, and how well

InputStatusMeasured accuracy (see below)
Digital invoice PDFs (text layer; German, English, French, Spanish, Dutch, Italian labels; US and European number/date formats)Supported coreabout 90% of checked fields correct, about 5% wrong, about 5% null (blind set #5)
Clean scans and invoice images (screenshots, flatbed scans)Supportedabout 85-93% correct on blind set #5; lower on poor scans
Phone photos of invoicesBest-effort85% on set #5 (small, mostly well-lit samples); 61-80% on other sets
Receipt photos (Malaysian, Indonesian, Portuguese, others)Weak27% to 66% correct depending on country and photo quality
Handwritten invoicesNot reliable44% on set #5 (12-41% on other sets)
Non-invoices (letters, contracts, menus, press releases, random text)Rejected, not charged9 of 9 negatives rejected on set #5

Measured accuracy (blind tests, honest numbers)

Accuracy is measured as the share of checked fields (vendor, invoice number, invoice date, due date, subtotal, tax total, total, currency, line-item count) that exactly match hand-checked ground truth. "Wrong" means a value was returned and it was incorrect; "null" means the field was left empty rather than guessed. Test documents are public documents (public datasets, Wikimedia Commons, public sample invoices); the sets are frozen with SHA-256 hashes before scoring.

Blind set #5 (59 documents, scored once per configuration; 345 checks): 82.0% correct overall (283/345), 5.8% wrong (20), 9.3% null (32), 2.9% rejected as unreadable or not an invoice (10). Real invoices wrongly rejected as "not an invoice": 1 of 50.

Category (set #5)CorrectNotes
Digital PDFs, all kinds (20 docs, 171 checks)90.6% (155)4.7% wrong, 4.7% null
- clean invoices92%
- credit notes100% (8 checks)
- mixed currencies93%
- freight / multi-page / hard layouts94-100%
- e-invoice sample PDFs (ZUGFeRD-style)81%credit notes and some tax blocks missed
Scans (flatbed)93%
Invoice images (screenshots)86%
Photos of invoices85%
Image or scan documents overall (30 docs, 174 checks)73.6%6.9% wrong, 13.8% null, 5.7% rejected
Portuguese phone receipts33%
Malaysian receipt photos66%
Indonesian receipt photos27%
Handwritten44%
Field (set #5, all documents)Correct
currency100%
invoice date88%
vendor82% (30 of 45 exact after normalising)
line-item count82%
due date86%
subtotal80%
invoice number77%
total75%
tax total70%

Digital PDFs only (set #5): currency 100%, due date 100%, total 95%, invoice date 95%, line-item count 95%, vendor 90%, subtotal 85%, invoice number 85%, tax total 75%.

Caveats you should know before relying on these numbers

  • Set #5 was frozen before scoring and not tuned on. Sets #2, #3 and #4 were also blind when first scored, but were then used for development, so their numbers are optimistic and are not quoted as headline results. Rule changes in this and earlier versions were tuned on development sets.
  • The digital PDFs in set #5 come from public invoice generators and sample sets whose template families overlap with ones used during development. Expect lower accuracy on your own unusual layouts; on earlier blind sets, clean digital invoices scored 92-96%, mixed-currency invoices 80-93%, and multi-page invoices ranged from 56% to 100%.
  • Small samples: set #5 has 59 documents. A single document moves a category by several points.
  • Photos and scans: OCR errors are the main failure. The Actor leaves a field empty or lowers its confidence rather than returning an unconfirmed OCR total, but some wrong values still get through (7% of checks on image documents).
  • Tax total and invoice number are the weakest fields on every set. Always use the confidence and warnings fields and verify before posting to your books.
  • A document is charged when something was extracted, even if some fields are wrong or null.

What it does well

  • Clean digital invoices and credit notes with a text layer: totals, dates, currency, vendor, line items.
  • Currency inference from evidence (ISO code, unambiguous symbol, country, tax vocabulary); null instead of a guess when the evidence is missing or conflicting. Ambiguous numeric dates (06/11/2023 with no locale evidence) are left null too.
  • Self-checks: line total = quantity x price, subtotal + tax = total, line-item sum, IBAN checksum. Failed checks add warnings.
  • Rejects non-invoices and unreadable photos without charging.
  • Fast and cheap: a digital invoice takes well under a second of compute; an OCR page takes roughly 5-7 seconds.

What it does poorly

  • Phone photos of receipts (especially Indonesian and Malaysian thermal receipts, crumpled or angled): often missing vendor, tax or total.
  • Handwritten invoices.
  • Unusual layouts: vendor name can be wrong (a customer name or address chosen), invoice number can be a different reference, and the tax total is the most frequently missed field.
  • Multi-column and highly styled invoices with line items in nested tables: line-item count can be off.
  • Right-to-left and CJK documents have not been tested and should not be expected to work.

Rejection, empty input and the free example

  • Not an invoice or receipt (letters, contracts, menus, lists of invoices, press releases, plain text; automatic type mode only): the item has error.code = "DOCUMENT_TYPE_NOT_RECOGNISED", null fields, and is not charged. Set documentTypeCheck to warn or off, or pick documentType yourself, to extract anyway.
  • Image too low quality (blurred, tiny or unreadable photos): error.code = "IMAGE_TOO_LOW_QUALITY" with a plain message ("image too low quality to read reliably; try a higher-resolution scan"), not charged. Set imageQualityCheck to warn or off to see what the OCR makes of it anyway.
  • Empty input: the run finishes successfully with the status message "No input given: add document URLs/uploads, paste text, give a dataset ID, or tick 'Use the built-in sample'. Nothing was processed or charged." and an empty dataset.
  • Built-in example: leave "Use the built-in sample invoice" ticked (the default in the Console prefill) and press Start. It runs a bundled sample invoice in about 6 seconds and is free.

Quick start

  1. Leave "Use the built-in sample invoice" ticked and press Start to see the output (free).
  2. Untick it, add files/URLs (or paste text), and run.

Input

FieldDescription
fileUrlsUploaded files or public http(s) URLs (PDF, PNG/JPG/TIFF, DOCX, HTML, XLSX, CSV, TXT). 50 MB/file, 100 docs/run.
documentsAdvanced: {"url": "..."}, {"base64": "...", "filename": "inv.pdf"} or {"keyValueStoreId": "...", "key": "..."}.
textPasted text or Markdown.
datasetIdPick a dataset (for example a Document to Markdown run) whose items have a markdown/text field.
documentTypeauto (default), invoice, receipt, form, table, generic.
fieldSchemaYour own fields, see below.
outputModedocument (nested, default), flat (one row per document), lineItems, tableRows.
dateOrder, defaultCurrencyResolve 03/04/2026 and documents with no currency symbol.
documentTypeCheckreject (default: in auto mode, non-invoices return DOCUMENT_TYPE_NOT_RECOGNISED, not charged), warn, or off.
strictOcrTotalsDefault on: a lone, uncorroborated low-confidence OCR total becomes null.
imageQualityCheckreject (default: unreadable photos return IMAGE_TOO_LOW_QUALITY, not charged), warn, or off.
enableOcr, ocrLanguageOCR for scans and images (eng, deu, fra, spa, ita, por, nld, combine with +, or auto to detect).
maxPages, includeTables, includeKeyValues, includeTextLimits and extras.

Custom fields (label / regex heuristics)

{
"fileUrls": ["https://example.com/purchase-order.pdf"],
"documentType": "generic",
"fieldSchema": [
{"name": "poNumber", "labels": ["PO No", "Purchase Order"], "type": "string"},
{"name": "shipDate", "labels": ["Ship date", "Delivery date"], "type": "date"},
{"name": "orderRef", "regex": "REF-(\\d+)", "type": "integer"},
{"name": "emails", "type": "email", "regex": "[\\w.+-]+@[\\w-]+\\.[\\w.]+", "regexGroup": 0, "multiple": true},
{"name": "incoterms", "labels": ["Incoterms"], "required": true}
]
}

labels finds the value next to or under a label. regex runs on the document text (regexGroup picks the group). Types: string, number, integer, currency, date (returned as YYYY-MM-DD), boolean, email, phone, percent, list. A missing required field adds a warning and is listed in missingRequired.

Output (outputMode document)

{
"source": "built-in sample invoice",
"ok": true,
"documentType": "invoice",
"pageCount": 1,
"ocrPages": 0,
"fields": {
"invoiceNumber": "BW-2026-0187", "invoiceDate": "2026-10-02", "dueDate": "2026-11-01",
"vendor": "Brightwave Design Studio LLC", "customer": "Cedar & Pine Outfitters",
"subtotal": 4266.0, "taxTotal": 0.0, "total": 4266.0, "currency": "USD", "purchaseOrder": "PO-55120"
},
"confidence": {"invoiceNumber": "high", "total": "high", "vendor": "medium"},
"lineItems": [
{"description": "Website design (hours)", "quantity": 24.0, "unitPrice": 95.0, "amount": 2280.0, "amountCheck": true}
],
"validation": {"lineItemsSum": 4266.0, "lineItemsMatchSubtotalOrTotal": true, "subtotalPlusTaxEqualsTotal": true},
"warnings": [],
"charged": false
}

Integrations

Email attachment → Google Sheets row (Zapier)

  1. Gmail trigger: New Attachment.
  2. Apify action Run Actor: Actor steady_kahikatea/document-data-extractor, input:
    {"fileUrls": ["{{attachment URL}}"], "outputMode": "flat", "documentType": "invoice"}
  3. Apify action Get Dataset Items (use the run's default dataset).
  4. Google Sheets action Create Spreadsheet Row: map vendor, invoiceNumber, invoiceDate, currency, total, taxTotal, warnings.

Use outputMode: "lineItems" instead to add one Sheets row per invoice line.

Make

Gmail → Watch emails (attachments on) → Apify → Run an Actor (or HTTP → Make a request to https://api.apify.com/v2/acts/steady_kahikatea~document-data-extractor/run-sync-get-dataset-items?token=YOUR_TOKEN with body {"documents":[{"base64":"{{base64(attachment.data)}}","filename":"{{attachment.fileName}}"}],"outputMode":"flat"}) → Google Sheets → Add a row. The response is the list of rows.

n8n

Gmail Trigger (download attachments) → Code node to base64 the binary → HTTP Request POST https://api.apify.com/v2/acts/steady_kahikatea~document-data-extractor/run-sync-get-dataset-items (header Authorization: Bearer <token>) with JSON body {"documents":[{"base64":"={{$binary.attachment_0.data}}","filename":"={{$binary.attachment_0.fileName}}"}],"outputMode":"flat"} → Google Sheets Append row. Or use the Apify node's Run Actor and get dataset operation with a public file URL.

Python / LangChain

from apify_client import ApifyClient
client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("steady_kahikatea/document-data-extractor").call(run_input={
"fileUrls": ["https://example.com/invoice.pdf"],
"documentType": "invoice",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["fields"]["vendor"], item["fields"]["total"], item["warnings"])

As a LangChain tool (pip install langchain-apify):

from langchain_apify import ApifyActorsTool
extract = ApifyActorsTool("steady_kahikatea/document-data-extractor")
result = extract.invoke({"run_input": {"fileUrls": ["https://example.com/invoice.pdf"], "outputMode": "flat"}})

Chain it after Document to Markdown: pass that run's dataset in datasetId, or paste its Markdown into text.

API (cURL)

curl -X POST "https://api.apify.com/v2/acts/steady_kahikatea~document-data-extractor/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{"fileUrls":["https://example.com/invoice.pdf"],"outputMode":"flat"}'

Pricing (pay per event)

You pay only for documents that produced extracted data. Rejected, unreadable, not-an-invoice, blocked, failed and empty documents and the built-in sample are free.

EventFree / no discountBronze (-10%)Silver (-20%)Gold (-30%)
Document extracted (first 5 pages included)$0.012$0.0108$0.0096$0.0084
OCR page (each scanned page or image that needed OCR)$0.008$0.0072$0.0064$0.0056
Extra page (each page beyond 5)$0.003$0.0027$0.0024$0.0021

Examples (no discount): a digital invoice PDF = $0.012; a one-page invoice photo or scan = $0.012 + $0.008 = $0.020; a 1,000-invoice digital batch = $12. Platform usage is included in the event prices.

Limits and safety

  • Public http(s) URLs only. Private/internal addresses, localhost, cloud metadata addresses and credentials in URLs are blocked, on every redirect hop.
  • 50 MB per file, 100 documents per run, 500 pages per document, 4 minutes of processing per document.
  • DOCX headers and footers are not read. Password-protected PDFs are not supported.
  • Documents are processed in memory only. Contents are never written to logs.
  • Not tax, legal or accounting advice: verify extracted amounts before posting them to your books.

Open-source alternative

invoice2data (MIT) is a free library for template-based invoice extraction that you host yourself. This Actor needs no templates, adds OCR, forms, tables and custom fields, and runs without hosting.