Pull structured data — line items, subtotal, tax, total — out of receipt and invoice images with no OCR preprocessing step, and keep every raw document and every extraction as an auditable trail in Backblaze B2.
This sample runs Donut
(naver-clova-ix/donut-base-finetuned-cord-v2) locally to read each document
image end-to-end and emit JSON directly — no Tesseract, no cloud OCR, no second
API key. B2 is the single store for both the raw uploaded images
(raw-documents/) and the derived structured-data artifacts
(extracted/<year>/<month>/<doc-id>.json plus per-run JSONL manifests), all via
the S3-compatible API. It's built for accounting teams, expense platforms, and
logistics operators that want a continuous ingest → parse → store → serve
loop where bulk write volume accumulates in B2 as it scales.
What you get out of the box:
- OCR-free document extraction with Donut, running on-device (CPU / Apple MPS / CUDA autodetected)
- A scoped Documents library with the full create → parse → review → delete lifecycle
- Human-in-the-loop correction for the fields the model can't infer (merchant, date)
- A pipeline dashboard (documents ingested, parsed, coverage %, recent extractions)
- The reusable B2 scaffolding from the starter kit: full-bucket file explorer + generic upload
- FastAPI backend with strict layered architecture and structural tests
- Agent-optimized docs — your AI coding agent can read the repo and start contributing immediately
Dashboard — pipeline metrics (documents ingested, parsed, parse coverage), a 7-day ingest-activity chart, and a recent-extractions table.
Documents — the receipt/invoice library scoped to the app's raw-documents/ and extracted/ prefixes, with submitter, type, total, and parse status per row.
Document detail — the original receipt image beside the Donut-extracted fields (merchant, date, totals) and line items, with re-parse and human-in-the-loop correction.
Raw JSON — the exact structured output the OCR-free Donut model emits for the document, straight from the model with no post-processing.
Every artifact in the pipeline lives in one bucket over the S3-compatible API:
the raw image you upload, the normalized JSON Donut produces, and a JSONL
manifest per parse run for incremental downstream processing. There is no
database — a document's doc_id is a deterministic hash of its raw object key,
so raw-documents/ and extracted/ correlate directly in B2. As you parse more
documents the extraction corpus grows as durable, queryable object storage.
raw-documents/<submitter>/<type>/<timestamp>-<filename> # uploaded images (create)
extracted/<year>/<month>/<doc-id>.json # normalized + raw extraction (run/edit)
manifests/<run-id>.jsonl # one JSONL object per parse run
The Donut model is contained in the repo layer (services/api/app/repo/donut_model.py),
mirroring how boto3 is contained in repo/b2_client.py — the service/ and
runtime/ layers never import transformers or torch directly. See
ARCHITECTURE.md.
You need: Node.js >= 20, pnpm >= 9, Python >= 3.11, and a free Backblaze B2 account. No second API key — the Donut weights download once from the public Hugging Face hub (keyless) and cache locally.
1. Install frontend dependencies
pnpm install2. Set up the backend (core + the Donut ML stack)
cd services/api
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt -r requirements-ml.txt
cd ../..The heavy Donut stack (torch + transformers) lives in requirements-ml.txt,
separate from the fast-installing requirements.txt. The first parse downloads
~0.8 GB of weights once, then reuses the cache. Model imports are lazy, so the
app boots and the test suite runs without the ML stack installed.
Device selection is automatic: the model runs on the first available of
CUDA → Apple MPS → CPU, defaulting to CPU. On MPS, if Donut's generate hits an
unsupported op it falls back to CPU for that call. CPU inference of one receipt
takes ~10–30 s but always completes.
3. Add your B2 credentials
cp .env.example .envOpen .env and, from the Backblaze B2 dashboard:
- Create a bucket and set
B2_BUCKET_NAMEandB2_REGION(e.g.us-west-004). The S3 endpoint is derived from the region — no endpoint URL to paste. - Create an application key with
Read and Writepermission and setB2_APPLICATION_KEY_ID(keyID) andB2_APPLICATION_KEY(applicationKey, shown only once). - Optionally set
B2_PUBLIC_URL_BASEif the bucket is public.
Walkthroughs: creating a bucket · creating app keys.
4. Run it
pnpm devFrontend at localhost:3000, API at localhost:8000. Go to Documents, add a
receipt image, and click Parse.
- Add a document — on
/documents, drop a receipt/invoice image, set the submitter ID and type (receipt/invoice). It's stored underraw-documents/. - Parse — click Parse on the detail page (or "Parse all unparsed" on the
list). Donut reads the image and writes
extracted/…json+ a run manifest. - Review & correct — Donut's CORD model reliably reads line items and totals but not the merchant or date; fill those in via "Correct fields". The corrected JSON is written back to B2.
- Serve — every extraction is a JSON object in B2 for downstream tools.
- Document Ingest — upload receipt/invoice images to B2
- Donut Extraction — OCR-free structured-data extraction (the marquee feature)
- Document Review — human-in-the-loop correction + the structured-data store
- Dashboard — pipeline metrics and recent extractions
- File Upload — generic drag-and-drop upload (kept from starter)
- File Browser — full-bucket explorer (kept from starter)
- Metadata Extraction — image/PDF metadata for uploads
- Design System — tokens, primitives, error/empty states. Live preview at
/design.
- TypeScript, Next.js 16, React 19, Tailwind v4, shadcn/ui, Recharts
- TanStack Query — caching, dedup, retry for every fetch
- Python 3.11+, FastAPI, boto3, Pydantic v2, Pillow
- Donut (
transformers+torch) — OCR-free document understanding, on-device - Backblaze B2 (S3-compatible object storage)
- pnpm workspaces (monorepo)
| Command | What it does |
|---|---|
pnpm dev |
Start frontend + backend |
pnpm dev:web |
Frontend only |
pnpm dev:api |
Backend only |
pnpm build |
Build frontend |
pnpm lint |
Lint frontend |
pnpm lint:api |
Lint backend (ruff) |
pnpm test:api |
Run backend tests (Donut monkeypatched — no torch/model needed) |
pnpm check:structure |
Verify layering rules |
pnpm test:e2e |
Playwright e2e tests (run pnpm --filter @donut-receipt-parser/web exec playwright install chromium once first) |
Verify the real model end-to-end (downloads weights):
cd services/api && RUN_DONUT_REAL=1 .venv/bin/python -m pytest tests/test_donut_real.py -v -s| Doc | Purpose |
|---|---|
| AGENTS.md | Agent table of contents — start here |
| ARCHITECTURE.md | System layout, layering, data flows |
| docs/features/ | Feature docs |
| docs/app-workflows.md | User journeys |
| docs/dev-workflows.md | Engineering workflows and testing |
| docs/SECURITY.md | Security principles |
| docs/RELIABILITY.md | Reliability expectations |
MIT License - see LICENSE for details.
Manage Backblaze B2 from your terminal using natural language (list/search, audits, stale or large file detection, security checks, safe cleanup).
Repo: https://github.com/backblaze-b2-samples/claude-skill-b2-cloud-storage



