Skip to main content

docket — local document AI, invoice & receipt OCR parser with LLMs

Turn scanned invoices, receipts, and contracts into structured, validated JSON using local OCR and vision-language models — and export them as EU e-invoices (XRechnung, ZUGFeRD / Factur-X, Peppol UBL, Facturae).

Apache-2.0 licensed: use it in your own products, commercial or not. Embed it as a Python library, run it as an HTTP service, or call the CLI.

demo

Runs locally with Ollama or on any OpenAI-compatible API (Mistral, OpenAI, Azure, vLLM) · Pydantic schemas · Grounded citations · Zero hallucinations

Quick start: local invoice & receipt parsing

You need Tesseract on PATH (brew install tesseract, apt install tesseract-ocr) and an LLM: 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

Classification, OCR extraction, and validation all run against whichever models your environment or .env names.

{
  "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"
}

The process exits non-zero when validation fails, so it drops into a shell pipeline as-is.

Use it in your application

As a 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):
    print(result.document.vendor_name, result.document.total_amount)
    xml = export_document(result.document, "xrechnung")
else:
    print(result.review_reasons, result.validation_issues)

result.document is the typed schema (Invoice, Receipt, Contract, …); result.field_sources says where on the page each value was read.

As an HTTP service (any language)

docker run -p 8000:8000 -e DOCKET_API_KEY=secret ghcr.io/kazkozdev/docket
# or: pip install "docket-idp[api]" && docket-api --port 8000
curl -H "Authorization: Bearer secret" -F file=@invoice.pdf localhost:8000/process

The OpenAPI spec is in docs/openapi.json — generate a typed client for TypeScript, Java, C#, Go, etc. from it.

As a CLI

docket invoice.pdf                       # JSON on stdout, exit code 2 if invalid
docket invoice.pdf --export xrechnung    # e-invoice XML on stdout
docket --list-formats

Runnable versions of all three, a TypeScript client, a Mistral-backed docker-compose.yml and an exporter plugin are in examples/.

EU e-invoicing and ERP export

Format --export / export_document(…) 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

Need a format that isn't here? Register one in a few lines (register_exporter("my-erp", func, accepts=(Invoice,))), or ship it as its own pip package through the docket.exporters entry point — see examples/exporter_plugin. Always validate generated XML against your recipient's official validator (e.g. KoSIT for XRechnung) before going live.

Structured data extraction with Pydantic schemas

Each document type has a strict Pydantic schema, and the model fills it under a JSON Schema contract rather than being asked nicely for JSON. A result that fails validation is sent back to the model with the error attached.

docket invoice.pdf
python tui.py invoice.pdf        # same pipeline, live per-stage progress
streamlit run app.py             # browser UI with a document preview

European and American number conventions are both parsed, so 1.234,56 and 1,234.56 read as the same amount. Keyword rules cover English and Spanish.

Commercial contracts & CLM analysis

docket eval/golden_dataset/contract_services.txt
{
  "doc_type": "contract",
  "contract_title": "SERVICES AGREEMENT",
  "parties_a": ["Vertex Consulting LLC"],
  "parties_b": ["Meridian Retail Inc."],
  "effective_date": "2026-03-01",
  "expiration_date": "2027-03-01",
  "governing_law": "State of New York",
  "payment_terms": "within 30 days of invoice",
  "auto_renewal": false,
  "key_obligations": [
    "Party A shall deliver monthly infrastructure audits.",
    "Party A shall provide a dedicated support engineer during business hours.",
    "Party B shall pay Party A within 30 days of invoice."
  ]
}

Contracts undergo specialized legal validation: counterparty cross-checking (parties cannot contract with themselves), grounding checks (parties, governing law, payment terms, signatories, and liability caps must appear in the raw document), and automated risk assessment (unlimited liability, auto-renewal traps, notice and cure period bounds).

Human-in-the-loop (HITL) review queue for flagged documents

Low classification confidence, a failed extraction or an error-severity validation issue routes the document to a review queue instead of a database. Each entry gets a stable id, a status, the preserved original, and an audit history of who changed what.

docket eval/golden_dataset/invoice_bad_total.txt
doc_9479a6b321e02ab6de25  pending  invoice
    validation error: total_amount — subtotal + tax + shipping - discount = 270.60,
    total_amount says 500.00

That document prints Amount Due: 500.00 while its own subtotal and tax add up to 270.60. Nothing silently reconciles it.

Embedding docket in an app with its own review UI? Pass process(..., enqueue_review=False) (or set DOCKET_REVIEW_QUEUE_ENABLED=false) and act on result.needs_review / result.review_reasons yourself.

Document AI REST API with FastAPI and async worker

DOCKET_API_KEY=secret docket-api
curl -H "Authorization: Bearer secret" -F file=@invoice.pdf localhost:8000/process

POST /process runs synchronously; POST /jobs queues and returns 202 with a job id for GET /jobs/{id}. GET /review-queue lists what is waiting for a person, and /review-queue/{id}/original returns the document that produced it. Interactive OpenAPI docs at /docs.

Pipeline architecture: hybrid OCR, classification, and validation

Text comes from the cheapest source that works: a PDF text layer if there is one, Tesseract for scans, and a Vision-Language Model (VLM) only when OCR confidence is low or a cheap text model judges the scan unusable. Classification tries keyword rules, then a TF-IDF model, then an LLM — each tier runs only because the last was not confident. Extraction fills a Pydantic schema and cites, for every number, the verbatim line it was read from.

Validation is completely deterministic and never calls a model: it checks arithmetic, date ranges, IBAN mod-97 (ISO 7064 across Europe & Brazil), VAT check digits (all 27 EU member states, UK, Switzerland, Norway), national tax IDs (US EIN, Canadian BN, Brazilian CNPJ/CPF), and asserts that each cited line exists and contains the number claimed. Detailed flow in ARCHITECTURE.md.

document → text layer / OCR / VLM → classify → extract + cite → validate → JSON or review

Configuration

Option Default What it does
DOCKET_LLM_PROVIDER ollama ollama, or openai for any OpenAI-compatible API
DOCKET_LLM_BASE_URL https://api.openai.com/v1 Endpoint when provider is openai (e.g. https://api.mistral.ai/v1)
DOCKET_LLM_API_KEY unset API key when provider is openai
DOCKET_TEXT_MODEL deepseek-v4.1-flash:cloud Model for classification and extraction
DOCKET_VISION_MODEL deepseek-v4.1-flash:cloud Model for transcribing scans
OLLAMA_HOST http://localhost:11434 Where Ollama is listening
DOCKET_OCR_LANG eng Tesseract languages, e.g. eng+deu+fra+spa+ita
DOCKET_ENABLE_THINKING false Reasoning tokens; off is markedly faster for this task
DOCKET_MIN_CONFIDENCE 0.55 Classification confidence below which a document goes to review
DOCKET_TFIDF_CONFIDENCE_FLOOR 0.65 TF-IDF confidence needed to skip the LLM tier
DOCKET_OCR_QUALITY_CHECK true Ask a cheap model whether a scan is usable before extracting
DOCKET_VISION_TIMEOUT_S 300 Vision call timeout
DOCKET_MAX_FILE_BYTES 20971520 Upload limit for the API
DOCKET_MAX_PDF_PAGES 100 Page ceiling per document
DOCKET_API_KEY unset Bearer token; the API refuses requests without it when set
DOCKET_REVIEW_QUEUE_ENABLED true Write flagged documents to the file-based review queue
DOCKET_REVIEW_QUEUE data/review_queue.jsonl Review journal path

Full list in src/docket/config.py; LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY enable optional tracing.

Requirements

  • Python 3.10+
  • macOS or Linux
  • Tesseract OCR on PATH
  • An LLM: a running Ollama with one text and one vision model pulled, or an OpenAI-compatible API (Mistral La Plateforme keeps data in the EU)

Limitations

  • Four document types: invoice, receipt, contract, boarding pass.
  • Keyword rules and the TF-IDF corpus cover English and Spanish only.
  • The vision model has been observed altering digits to make a page reconcile — a printed 450.00 read three times out of three as 480.00. No fix for that is in this repo.
  • Line items carry no source citations, so the cited-source check does not cover them.
  • The review queue is one file: durable on one node, not across hosts.
  • Windows is untested; CI runs Linux only. 3.6–9.5 s per document, more when a page needs the vision model.
Manual installation, Docker, development setup

Install options

pip install docket-idp            # library + CLI
pip install "docket-idp[api]"     # + HTTP service (docket-api)
pip install "docket-idp[all]"     # + Streamlit UI, terminal UI, Langfuse tracing

From 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

On macOS, double-clicking start.command creates the venv, starts Ollama if needed, frees ports 8000/8501 and opens the UI.

Docker

docker run -p 8000:8000 -v docket-data:/app/data ghcr.io/kazkozdev/docket
# or build it yourself: docker build -t docket .

The image ships Tesseract with the main EU language packs and talks to Ollama on the host by default; set the DOCKET_LLM_* variables to use a hosted API instead.

Development

pytest                                # none need a running LLM
python eval/run_eval.py               # accuracy, P/R/F1, latency on the golden set
python eval/benchmark_methods.py      # the rules vs TF-IDF vs LLM comparison

Release files for docket-idp 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for docket-idp 0.1.0
File Size Uploaded
docket_idp-0.1.0.tar.gz 134.6 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for docket-idp 0.1.0
File Interpreter ABI Platform
docket_idp-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 233.5 kB

Release files / docket_idp-0.1.0.tar.gz

Download URL docket_idp-0.1.0.tar.gz
Size 134.6 kB
Tags Source
SHA-256 checksum
How to use checksums
126d18e7e83bbbbc8dacd09d4de5687d033aa498070304dfa8cd91dd74b99480
BLAKE2b-256 checksum
How to use checksums
7ac054b90d5e10fdbacee7436f589e965c54c5092eeb6faca223fff4239e2207
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release files / docket_idp-0.1.0-py3-none-any.whl

Download URL docket_idp-0.1.0-py3-none-any.whl
Size 99.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
deb94c23ca304a520471f9300e3deb9665b3f8a0db9405acf1409a9048241c43
BLAKE2b-256 checksum
How to use checksums
92df943b9de9fce187823e5b490258801ac49c85c621dcd09cd45c7e21ecee3d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 21, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page