Inspiration
On 1 September 2026 France moved B2B invoices onto approved e-invoicing platforms. Accounting teams still get leftover PDFs: a clean file with data already inside, a bakery PDF with a wrong total, a US supplier who is not on the French system. We did not want another “upload PDF, AI guesses XML” wrapper. The law already says those three documents are different jobs. The inspiration was a named buyer — NordSeine Distribution — staring at a mixed inbox and needing a copilot, not a new government platform.
What it does
Mandate Inbox is an AP copilot for that leftover pile. It is not a Plateforme Agréée and it does not send anything to the tax authority. Same mailbox, three paths: Clean French e-invoice (Factur-X) — structured XML is already attached inside the PDF. We trust it and skip picture-reading. French SME PDF (still legal to email until 2027) — Nutrient reads the PDF. A rules check asks whether lines + VAT equal the printed total. On the bakery bill, lines are 150+8.25=158.25 but the PDF says 180. A human fixes the number in Nutrient’s viewer, then we emit EN 16931 Comfort XML. Foreign supplier PDF (US/DE) — this is not a French e-invoice. We build an e-reporting packet and show the 10-day clock. A dated audit log records what we trusted, what we extracted, and who signed off.
How we built it
Python + FastAPI. Three labelled fixtures (not a training set): Atelier Dupont, Boulangerie Martin, Acme Industrial. PDFs are generated on startup. Nutrient Document Web Services does the document work: Data Extraction API (/extraction/extract) with an invoice schema, citations, and confidence when a pdf_live_ key is set. DWS Viewer — our backend mints POST /viewer/sessions; the browser loads Nutrient Web SDK on the app-provided PDF. We do not paste dashboard Copy-token JWTs into the app. Classification and math gates are deterministic Python. Comfort XML is emitted after rules pass (or after a clerk edit). Output is sealed with a SHA-256 desk seal for the demo. Hosted on Google Cloud Run from GitHub, with keys as runtime env vars.
Challenges we ran into
Nutrient’s getting-started path wants a TypeScript sample and a dashboard Copy token. That token expires and is bound to an uploaded file. Our product already had the PDFs, so we had to use app-provided documents and mint sessions from FastAPI. Session create returns 201 Created, not 200 — we treated a working call as failure until we fixed that. A sleeping/blocked server could not reach api.nutrient.io, so extract silently fell back to fixtures and the viewer showed 403. Cloud Run’s first build failed because GitHub had no Dockerfile yet. Secrets in the console do nothing until a new revision is serving and the env names are exactly NUTRIENT_API_KEY / NUTRIENT_VIEWER_API_KEY.
Accomplishments that we're proud of
One inbox that refuses to treat three legal documents as the same converter. A math gate that blocks the bakery’s 180 until a human looks. Nutrient extract + Viewer as the load-bearing document path, not a logo. A public Cloud Run URL a judge can click without our laptop. Honesty in the UI: we are not a PA, and we skip OCR when XML is already there.
What we learned
Sponsor storage/SDKs have to do a real job. “We trained a model” would have competed with Nutrient, not used it. Judges need HTTPS, not localhost. Dashboard hello-world (upload PDF, copy token, Vite app) is not the same as a product session. Small protocol details (201 vs 200, env var names) decide whether the live demo looks “Nutrient live” or “mock.”
What's next for Mandate Inbox
Wire Nutrient DWS digital sign on the Comfort PDF (today’s seal is SHA-256). Optional Foxit eSign for the human handoff Nutrient already asks for. Persist audit state beyond process memory. Still not becoming a PA — a real approved platform would transmit; we prepare the file.
Built With
- cloud
- docker
- document-ai
- e-invoicing
- en16931
- factur-x
- fastapi
- google-cloud-run
- httpx
- javascript
- nutrient
- nutrient-dws
- nutrient-web-sdk
- pydantic
- pypdf
- python
- reportlab
- rest-api
- uvicorn
Log in or sign up for Devpost to join the conversation.