Turn scanned invoices, receipts, and contracts into structured, validated JSON using OCR and LLMs, then export them as EU e-invoices (XRechnung, ZUGFeRD / Factur-X, Peppol UBL, Facturae). Use it as a Python library, an HTTP service, or a CLI. Apache-2.0, so commercial use is fine.
Ollama or any OpenAI-compatible API (Mistral, OpenAI, Azure, vLLM) · Pydantic schemas · Every value cited to its source line · Deterministic validation
Requirements: Python 3.10+, Tesseract on PATH (brew install tesseract / apt install tesseract-ocr), and an LLM, meaning either Ollama with a text and a vision model pulled, or an OpenAI-compatible API key (see Configuration).
pip install docket-idp
docket invoice.pdf # JSON on stdout, exit code 2 if validation fails
docket invoice.pdf --export xrechnung # e-invoice XML on stdout{
"doc_type": "invoice",
"invoice_number": "FAC-2026-0042",
"issue_date": "2026-03-15",
"vendor_name": "Talleres Montjuïc S.A.",
"subtotal": 1234.56,
"tax_amount": 259.26,
"total_amount": 1493.82,
"currency": "EUR"
}Python library
from docket import Invoice, export_document, process
result = process("invoice.pdf", enqueue_review=False) # your app owns the review flow
if result.is_valid and isinstance(result.document, Invoice):
xml = export_document(result.document, "xrechnung")
else:
print(result.review_reasons, result.validation_issues)result.document is the typed schema (Invoice, Receipt, Contract, …), and result.field_sources gives the page and quote each value was read from.
HTTP service (any language)
docker run -p 8000:8000 -e DOCKET_API_KEY=secret ghcr.io/kazkozdev/docket
curl -H "Authorization: Bearer secret" -F file=@invoice.pdf localhost:8000/processPOST /process is synchronous. POST /jobs returns 202 and a job id to poll at GET /jobs/{id}, and an Idempotency-Key header makes retries safe. /review-queue serves flagged documents. Generate a typed client from docs/openapi.json; interactive docs are at /docs. Without Docker: pip install "docket-idp[api]" && docket-api.
examples/ has runnable scripts, a TypeScript client, a Mistral-backed docker-compose.yml and plugin packages.
Built in: invoice, receipt, contract, purchase order, bank statement, acceptance act, waybill, boarding pass. To add your own, write a Pydantic model and register it:
from datetime import date
from docket import CitedDocument, register_document_type
class DeliveryNote(CitedDocument): # CitedDocument adds page/quote citations
note_number: str
supplier_name: str
delivery_date: date
register_document_type(
"delivery_note", DeliveryNote,
description="Delivery note / Lieferschein listing goods handed over", # read by the LLM classifier
keywords=["delivery note", "lieferschein"], # free rules tier
)Registered types are classified, extracted, citation-checked and exported like the built-in ones. add_validator("invoice", fn) adds your own rules to any type, and the docket.document_types entry point lets a separate package ship types. See examples/custom_document_type.py.
| Format | Name |
|---|---|
| XRechnung (CII) | xrechnung |
| ZUGFeRD 2.2 / Factur-X, EN 16931 | zugferd |
| UBL 2.1 / Peppol BIS Billing 3.0 | ubl |
| Facturae 3.2.2 (Spain) | facturae |
| SAP IDoc / journal CSV | sap-idoc, sap-csv |
| Xero, QuickBooks | xero-csv, xero-json, quickbooks-iif, quickbooks-json |
Add your own with register_exporter("my-erp", func, accepts=(Invoice,)) or the docket.exporters entry point. docket --list-formats shows everything available. Validate generated XML with the recipient's official validator (e.g. KoSIT for XRechnung) before going live.
document → text layer / OCR / VLM → classify → extract + cite → validate → JSON or review
- Text comes from the cheapest source that works: the PDF text layer, then Tesseract, then a vision model, which is used only when OCR confidence is low or a cheap text model judges the scan unusable.
- Classification tries keyword rules, then TF-IDF, then an LLM. Each tier runs only when the one before it was unsure. Rules and TF-IDF cover English, Spanish, German, French, Italian, Dutch and Portuguese; any other language falls through to the LLM.
- Extraction fills a Pydantic schema under a JSON Schema contract and cites the verbatim line for every value. Output that fails the schema goes back to the model with the error attached.
- Validation never calls a model. It checks arithmetic, dates, IBAN mod-97, VAT check digits (all 27 EU states, UK, CH, NO), national tax IDs, and that every cited line exists and contains the claimed value. Contracts also get counterparty, grounding and risk checks (unlimited liability, auto-renewal, notice periods).
- Review: low confidence, failed extraction or a validation error sends the document to a review queue that keeps the original and an audit history. Nothing is silently reconciled. An invoice whose
Amount Due: 500.00disagrees with its own 270.60 subtotal and tax is flagged, not fixed.
Also included: cross-document matching (invoice ↔ PO, three-way PO/waybill/invoice, invoice ↔ contract, receipt ↔ bank transactions) and a heuristic stamp, signature and alteration check (docket file.pdf --forensics). Details are in ARCHITECTURE.md.
Set in the environment or .env. .env.example and config.py have the full list.
| Option | Default | What it does |
|---|---|---|
DOCKET_LLM_PROVIDER |
ollama |
ollama, or openai for any OpenAI-compatible API |
DOCKET_LLM_BASE_URL / DOCKET_LLM_API_KEY |
OpenAI / unset | Endpoint and key for openai, e.g. https://api.mistral.ai/v1 (EU-hosted) |
DOCKET_TEXT_MODEL / DOCKET_VISION_MODEL |
deepseek-v4.1-flash:cloud |
Models for extraction and for reading scans |
OLLAMA_HOST |
http://localhost:11434 |
Where Ollama is listening |
DOCKET_OCR_LANG |
eng |
Tesseract languages, e.g. eng+deu+fra+spa+ita |
DOCKET_MIN_CONFIDENCE |
0.55 |
Classification confidence below which a document goes to review |
DOCKET_REVIEW_QUEUE_ENABLED |
true |
Write flagged documents to the file-based review queue |
DOCKET_API_KEY |
unset | Bearer token the HTTP API requires when set |
- The TF-IDF tier is trained on a small embedded corpus (about 20 phrases per type), so it only answers when confident and leaves the rest to the LLM.
--forensicsis a pixel heuristic, not a trained vision model. It finds colored stamps and seals and handwriting in colored or black ink, but never reports black stamps, which it can't tell apart from logos or table graphics. Its confidence scores come from geometry and aren't calibrated probabilities.- The vision model has been observed changing digits so that a page reconciles (a printed
450.00read as480.00three times out of three). There is no fix for that in this repo. - Line items carry no source citations, so the citation check doesn't cover them.
- The review queue is a single file: durable on one node, not across hosts.
- Windows is untested. A document takes 3.6–9.5 s, longer when a page needs the vision model.
Install options, source setup, development
pip install docket-idp # library + CLI
pip install "docket-idp[api]" # + HTTP service (docket-api)
pip install "docket-idp[all]" # + Langfuse tracingFrom source:
git clone https://github.com/KazKozDev/docket.git
cd docket && python3 -m venv .venv && source .venv/bin/activate
pip install -e ".[dev]" && cp .env.example .env
streamlit run examples/streamlit_demo.py # demo UI: document preview + per-stage resultsOn macOS, double-clicking start.command sets everything up and opens the UI.
pytest # no test needs a running LLM
python eval/run_eval.py # accuracy, P/R/F1, latency on the golden set
python eval/benchmark_methods.py # rules vs TF-IDF vs LLM comparison