AI invoice data extraction
From a PDF or photo of a supplier invoice to a structured record you can reconcile — no templates, no per-supplier setup.
LedgerSync sends each uploaded invoice to Google’s Gemini model with a fixed response schema. The model has to answer in that shape, so the result is a typed record — supplier, invoice number, dates, amounts, line items — rather than free text that still needs parsing. The record then goes straight into reconciliation against your ERP records.
What you can upload
- Invoices as PDF or image — PDF, JPEG, PNG, WebP, TIFF, HEIC/HEIF, BMP and GIF. Each file can be up to 50 MB (the default limit), and a single upload can carry up to 40 files.
- Structured files — CSV, XLSX/XLS, XML, JSON, YAML and plain text are converted to text first (one CSV block per spreadsheet sheet) and extracted the same way.
- Multi-invoice packs — one PDF containing many invoices is split into one document per invoice before extraction. See multi-invoice PDFs below.
The declared file type is never trusted on its own: for PDF, JPEG, PNG, WebP, TIFF, BMP and GIF the first bytes of the file are checked against the format’s signature, and a mismatch is rejected before anything is stored.
What gets extracted
Extraction runs with a strict JSON response schema (temperature 0.2), so every document comes back in the same shape. These are the fields.
Header
| Field | Notes |
|---|---|
supplier_name, supplier_id | Supplier name and the supplier code if the invoice prints one |
invoice_number | Document-type prefixes such as “Invoice” or “Rechnung” are stripped from the value |
invoice_date | Normalised to ISO YYYY-MM-DD |
subtotal, tax_amount, total_amount | Net, tax and gross amounts |
tax_rate | Percentage, when printed |
currency | ISO 4217 code |
discount_amount, discount_percent | Early-payment or trade discounts (Skonto, Rabatt) |
delivery_terms | Incoterms: EXW, FCA, FAS, FOB, CFR, CIF, CPT, CIP, DAP, DPU, DDP |
confidence | The model’s own 0–1 confidence for the extraction |
Line items
| Field | Notes |
|---|---|
item_code | SKU, article or style code |
description | Line text as printed |
quantity, unit_price, total_price | A missing line total is computed as quantity × unit price |
color | Colour / Farbe column when present |
extra_fields | Any other per-line column, as key/value pairs |
Everything else on the page
| Field | Notes |
|---|---|
additional_fields | Every other labelled header field on the document, in any language, as key/value pairs |
payment_terms, freight_amount, carrier, delivery_recipient, iln | Logistics and payment details common on DACH B2B invoices |
tax_category, is_intra_community_exemption | Set when the document carries a reverse-charge or intra-Community supply statement |
The raw JSON is kept with the document, so anything the model read but the cards don’t show is still there to cross-check against the original file.
German, EU VAT and Indian GST labels, without configuration
Label knowledge lives in small “lexicon packs” that are merged at runtime. They tell both the model and the matching engine which printed labels mean which field, and which printed phrases change the tax treatment.
| Pack | Covers |
|---|---|
| Core (English) | Invoice No., Invoice Number, Invoice #, Bill No., Document No.; VAT No., Tax ID, EIN, ABN; Incoterms |
| DACH (German) | RECHNUNG, Rechnungsnummer, Belegnummer, Beleg-Nr.; Belegdatum, Rechnungsdatum; Zwischensumme, Warenwert, Nettowert; MwSt, USt, Umsatzsteuer; Rechnungsbetrag, Gesamtbetrag; Skonto, Rabatt; USt.-IdNr., UID |
| EU VAT | “Steuerfreie innergemeinschaftliche Lieferung”, “Intra-Community supply”, “Reverse charge”, “0% Reverse Charge” → tax category set, 0% rate, exemption flag |
| India GST | CGST, SGST, IGST, GST; HSN / SAC codes; GSTIN |
Adding a country is a new pack file plus one registry line; nothing per customer or per supplier has to be configured.
Multi-invoice PDFs
Suppliers often send one PDF with twenty invoices and a cover sheet. Before extraction, every page’s text is scanned for an invoice-number label followed by a value — Invoice No. 9000120481, RECHNUNG 2/984712/3349120, Belegnummer SF-551249. Each such page starts a new invoice; pages without a label attach to the invoice before them; cover or summary pages before the first label are dropped. The parts are named pack-01.pdf, pack-02.pdf, … and processed as separate documents, so you get one row per invoice, not one row per file.
A PDF with a single invoice is ingested whole, and if a file’s text can’t be read the original is ingested unchanged rather than lost. How the split works, with a worked example.
Arithmetic checks after extraction
An extracted record is self-checked before it is scored: line totals (minus any discount) should add up to the subtotal, subtotal × tax rate should give the tax, and subtotal + tax should give the total, each within 0.5% or 0.02 in the invoice’s currency. A document that fails a check is never silently “fully matched” — it lands in the review queue with the exact figures that disagree.
[{ "field": "arithmetic.totalAmount", "extracted": 358, "erp": 348 }]A 0% intra-Community or reverse-charge invoice does not raise a tax alert.
What happens to a document
| Status | Meaning |
|---|---|
PENDING | Stored and queued for the background worker |
PROCESSING | Being read by the model and reconciled |
PROCESSED | Extraction and match result are on the document |
FAILED | Unreadable file or extraction error — the message is kept; “Try again” requeues it |
Extraction and reconciliation run off the request path in a worker that lives in the same deployable as the app, so an upload returns immediately and the row updates when the work is done.
Where the document content goes
Ready to see it on your own invoices?