Beanbeaver turns bank statements and grocery receipts into your Beancount ledger.
Two modes
- Import credit card and chequing statements into Beancount
- Parse scanned grocery receipts into itemized expenses.
You can use either mode on its own, but using both brings the synergy of semi-automatic matching bank statements and grocery receipts.
Output: Itemized Beancount Record
bb-tui is the ratatui dashboard that is becoming the primary UI. It starts the
OCR container and the receipt upload server for you, shows the scanned/approved
receipt queues, and drives review, imports, and matching from one screen.
pixi run bb-tuiThe legacy bb CLI below still works but is being deprecated in favor of the TUI.
Recommended: Pixi
pixi install
pixi run maturin-develop
pixi run bb --helpStandard Python editable install:
python -m pip install --upgrade pip
python -m pip install -e ".[dev,test]"
maturin develop
python -m pip install -e ".[dev,test]"
bb --helpThe Rust/PyO3 extension is required for receipt parsing and matching.
bb import # auto-detects type (prompts if ambiguous)It scans your default Downloads folder and matches the bank.
By default Beanbeaver runs receipt OCR in-process — no container needed. Download the model weights once:
bb fetch-models
This fetches the PP-OCRv5 ONNX models (~100 MB) into a per-user cache; from then
on bb serve and the TUI run OCR natively. Force a backend any time with
OCR_BACKEND=native|container.
Higher line-item accuracy (optional container): the PaddleOCR
container (https://github.com/Endle/beanbeaver-ocr) reaches somewhat higher
itemization accuracy. Run it and set OCR_BACKEND=container:
docker run --name beanbeaver-ocr -p 8001:8000 ghcr.io/endle/beanbeaver-ocr:latest
# Or podman on Linux
podman run --replace --name beanbeaver-ocr --network=slirp4netns -p 8001:8000 ghcr.io/endle/beanbeaver-ocr:latest
If the receipt is on the mobile, we can run
bb serve
Then we use iOS shortcut or other tools to sent the receipt to this endpoint:
curl -X POST "http://<LAN_IP>:8080/beanbeaver" -F file=@receipt.jpg
The server always saves a draft stage into the receipt's own directory
(receipts/<receipt-dir>/stages/) for later manual review.
On success the endpoint returns a JSON body that the iOS Shortcut can surface as a notification:
{
"status": "success",
"summary": "Loblaws · 2026-05-16 · $32.70 · 8 items",
"parsed": {
"merchant": "Loblaws",
"date": "2026-05-16",
"date_is_placeholder": false,
"total": "32.70",
"subtotal": "29.10",
"tax": "3.60",
"item_count": 8,
"warnings": []
},
"draft_filename": "review_stage_1.receipt.json"
}On failure the body carries an error_code (ocr_unreachable, ocr_error, parse_failed, internal_error) and a human-readable summary you can show directly on the phone so you know whether to reshoot. Keep bb serve bound to localhost or your LAN — the response includes parsed merchant/date/amount.
bb edit
It appends a review stage to the receipt's stages/ directory, promoting the
receipt from scanned to approved. Files never move between status trees —
status is derived from the latest stage file.
There are also helpers
bb list-approved
bb list-scanned
bb edit
bb re-edit
Here comes the fun part.
bb match
It will match beancount records (from credit card statements) with approved receipts.
Notes:
- Each receipt chain lives in one directory:
receipts/<receipt-dir>/withsource/,ocr/,stages/, andrendered/subdirectories. - Status comes from the latest file under
stages/: scanned means OCR+parser succeeded but the draft is unreviewed and may contain errors; approved means the draft has been reviewed and edited by a human; matched means it has been paired with a ledger transaction. bb editrequires an interactive TTY.
Recommended local commands:
pixi run lint
pixi run test
pixi run test-e2e-cachedCore CI now targets Linux, macOS, and Windows for lint and non-E2E tests. Container-backed OCR flows remain Linux-first in practice.