Turn your Kaufland digital receipts (Digitale Kassenbons) and REWE eBons into structured, self-hosted data. Find out whether that "sale" sticker is actually a better price than what you already paid.
Unofficial and unaffiliated with Kaufland. This is a personal-data / interoperability tool: it only ever handles your own receipts, and the active ingestion path parses PDFs you export from the app. It never talks to Kaufland's servers.
- Item Price History: press ⌘K, type any item name, and see every price you've paid for it across every receipt: a sparkline plus a date-ordered table. Yesterday's "amazed, I didn't know I could do that" reaction to your own price history is the whole point.
- Price Integrity Check: compares a discounted item's price against the median of what you actually paid for it before, at the same store, and tells you whether the "sale" is real.
- Total Spend Aggregation: a running total of what you actually spend, rolled up across every store you've fed receipts from.
- Grocy Stock Push: one click pushes a receipt's line items into your Grocy stock, so scanning receipts becomes how your pantry inventory stays current.
- Runs entirely on hardware you control. Nothing is uploaded anywhere.
There's no hosted version, and there isn't going to be one anytime soon. That's deliberate, not a missing feature. Grocery receipts are sensitive-ish personal data (what you buy, when, how much you spend), and the only way to avoid becoming a data processor for other people's spending habits is to never hold their data at all. You run it, your receipts stay on your infrastructure, full stop.
For a NAS or any Docker host:
git clone https://github.com/andrecedik/receipt-radar.git
cd receipt-radar
docker compose up -dThis pulls the published multi-arch image (linux/amd64 / linux/arm64)
from GHCR — no build step, no cloning half a toolchain. Open
http://<host>:8000, upload a receipt PDF, done.
127.0.0.1.
See Self-hosting details below for volumes, Grocy env vars, and building locally instead of pulling.
Every item you've ever bought gets its own page: a sparkline of price over time and a table of every observation (date, store, price). Reached via the global ⌘K quick-jump, which searches receipts, items, and pages at once.
Kaufland (like most grocers) prints a "sale" price without telling you what you paid last time. This compares an item's current effective price (after any discount) against the median of your own prior purchases of that item at that store — using only your own history, no external price database — and tells you whether it's a genuine reduction or a sale in name only.
Every parsed receipt rolls up into a running spend total across every store you've fed receipts from. Kaufland and REWE today (see What it can't do yet); the data model and UI don't assume a single retailer.
Set GROCY_URL and GROCY_API_KEY, then push a receipt straight into your
Grocy stock from the Grocy tab. The first time a line item shows up, you
resolve it once: match it to an existing Grocy product, create a new one,
or mark it as permanently skipped (for things that never belong in stock,
like loyalty discounts or Pfand/Leergut deposit returns). Every future
receipt with that exact item name resolves itself automatically after that.
A receipt pushes to Grocy only once every one of its line items has been
resolved.
- Kaufland and REWE only, German only. The parsers handle the printed
formats of the Kaufland digital receipt and the REWE eBon (the
stationary-ebon-<uuid>.pdfdownload from the REWE app or rewe.de); no other retailer is supported yet, and both chains only issue these in Germany. - Only Kaufland receipts from July 2024 onward can be parsed. Older ones are exported by the Kaufland app as its rendered "Receipt Copy" screen, an image-only PDF with no text layer (the switch happened in the second half of June 2024). The parser is text-based (no OCR), so it rejects those with a clear error instead of guessing.
- No automatic sync. The Kaufland app has no public API, and the host that serves digital receipts is certificate-pinned. Receipts have to be exported as PDFs by hand (from the app, or via the browser Upload page) rather than pulled automatically. See Roadmap.
- No authentication on the upload endpoint. Fine on a trusted local network; not fine exposed to the open internet without a reverse proxy in front of it.
- No Home Assistant integration yet. Grocy Stock Push exists; an HA notification hook ("this item you track just went on genuine sale") does not, yet.
- Single-user, single-household. There's no concept of accounts, teams, or multi-tenant anything. It's built to run one instance for one person's own receipts.
- No cross-retailer price comparison. Price Integrity Check only ever compares an item against your own purchase history. It can't tell you whether Edeka down the street is cheaper today. See the bigger vision below for why, and why that's a deliberate sequencing choice, not an oversight.
Coming to this repo (still self-hosted, still yours to run):
- Automated receipt sync: no more manual PDF export, once the app's certificate pinning is worked around
- Home Assistant notification hook for genuine-discount alerts
- Parsers for other German grocers (Edeka, Lidl), extending Total Spend Aggregation beyond Kaufland and REWE
The bigger vision — and honestly, the reason this project exists at all:
grocery prices vary by store and region in ways no single shopper can see on
their own. Cross-retailer price comparison and sale-timing prediction only
become possible with data from many shoppers across many locations. That's a
crowd-data problem a single self-hosted instance structurally can't solve.
It's a different, opt-in system, not a feature that will show up in
docker compose up, and it depends on Total Spend Aggregation actually
being useful to people first. If Price Integrity Check earns its keep for
you, that's the bet this whole project is built on.
volumes:
- ./data:/data # receipts + uploaded PDFs — persists on the hostThis mount is required. Without it, the container still runs, but everything lands on its writable layer instead and is lost the moment the container is removed.
Set in a git-ignored .env file next to docker-compose.yml (loaded
automatically). Never hardcode these:
GROCY_URL=https://your-grocy-instance
GROCY_API_KEY=your-grocy-api-keyOptional. Leave unset and the app runs fine, /api/grocy/* just returns
503.
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d --buildOr a one-off image for a specific platform:
docker build --platform linux/amd64 -t receipt-radar . # or linux/arm64If you're running this on a Mac with iCloud Drive instead of a NAS, receipts can be picked up automatically from a local folder instead of uploaded through the browser:
-
In the Kaufland app: Digitale Kassenbons → open a receipt → als PDF speichern → share to iCloud Drive, into a folder named
digital-receipts. -
On the Mac:
uv run receipt-radar watch # check once, ingest new PDFs, exit uv run receipt-radar watch --no-once # or keep polling in the background
Receipts are cached as one JSON file each under
~/.local/share/receipt-radar/receipts/. Ingestion is idempotent: safe
to re-run watch/ingest freely.
uv sync
uv run pytest # core logic; no PDF or network needed
cd web
npm install
npm run dev # http://localhost:5173, proxies /api to a local `receipt-radar serve`
npm run test
npm run lintRunning without Docker, the CLI itself covers everything the web UI does
and more. Run uv run receipt-radar --help for the full list, or:
uv run receipt-radar ingest ~/path/to/a-receipt.pdf
uv run receipt-radar list # what's stored
uv run receipt-radar export --format csv -o export.csv
uv run receipt-radar summary # monthly rollup (Markdown)
uv run receipt-radar serve --web-dir web # local API server, for `npm run dev` aboveSee web/README.md for the frontend build/preview flow in
more detail.
AGPL-3.0. If you build on this for something that needs a different license, get in touch.