Skip to content
NDNicolas Desjardins, back to overview
Production Live & Open Source01 / Phase 1 · Shipped

Invoice reader

Invoice Extraction Service

Turns PDFs, scans and forwarded emails into clean invoice data your accounting system can import. If one AI provider is slow or down, it switches to another on its own, and every total is re-added to the cent before anything comes out.

The problem it solves

Accounts-payable teams receive invoices as OCR dumps, forwarded email threads, till receipts, and foreign-language PDFs. A raw LLM call returns prose, fenced code blocks, or a JSON object with the wrong shape, and a single provider outage stops the whole intake queue.

What was measured

  • Measured
  • Target
  • 21 / 21

    fully correct, primary model, dated benchmark

    measured
  • 10

    provider failure classes covered offline

    measured
  • 0

    network calls permitted in the test suite

    measured
  • $0.00

    marginal cost per request on the default chain

    measured
How it worksfor engineers and technical reviewers

In technical terms

An asynchronous FastAPI microservice that turns messy, unstructured documents into a guaranteed, schema-validated invoice record. Resilience comes from routing across models, not vendors: a free primary model cascades to a second provider on rate limits, timeouts, or schema exceptions, at zero marginal cost.

Architecture

Requests are validated by Pydantic v2 before any model is called. The dispatcher walks an ordered model chain with strict json_schema constrained decoding at temperature 0. Each attempt runs under a hard timeout and translates SDK exceptions into one error taxonomy. Raw output is stripped of fences and reasoning preambles, parsed, then normalised by validators that recompute line totals to the cent and map currency symbols to ISO-4217. The service also looks after its own chain: when a free model is withdrawn, it re-tests the free catalogue with seven sample documents and fills the gap using a published ordering rule.

Stack

  • Python 3.12
  • FastAPI
  • Pydantic v2
  • OpenRouter
  • httpx
  • Pytest
  • GitHub Actions
  • Render

Proof points

  • Ten failure classes exercised offline in the cascade tests: 429, 503, 404, timeout, unexpected exception, invalid JSON, schema mismatch, empty response, in-band upstream error, and truncated output.
  • The test suite cannot reach the network by construction. Every outbound path is closed in conftest, and a test that reaches for one fails immediately.
  • Dated benchmark snapshot (7 October 2026): 3 passes of 7 documents per model, every call saved as JSONL so the figures can be re-derived without calling a model.
  • Every figure on the project page is checked against the recordings by a test. A sentence that is not derived from the data fails the build.
  • Logging hygiene: the model and timing are logged, never the vendor name or invoice number above DEBUG.

Request pipeline

  1. 01

    Ingest

    ExtractRequest, 10 to 50k chars, 1 MB body ceiling refused before read

  2. 02

    Harden

    Prompt injection defanging, request-id middleware, call budget with Retry-After

  3. 03

    Primary model

    Strict json_schema, temperature 0, hard timeout

  4. 04branch

    Fallback

    Different provider, triggered only on a failed call

  5. 05

    Recover

    Strip fences, drop reasoning preamble, decode JSON

  6. 06

    Validate

    InvoiceData.model_validate, recalc totals, ISO-4217