Keine Buchung ohne Beleg.
Deutsch · English below ↓
Belegabgleich gleicht deinen Kontoauszug gegen deine Rechnungen ab — so siehst du auf einen Blick, welcher Buchung noch ein Beleg fehlt — und schließt die Lücke in Minuten. Kostenlos, Open Source und 100 % lokal: nichts verlässt je deinen Browser.
Gemacht für Freelancer, Kleinunternehmer, kleine GmbHs und Vereins-Schatzmeister, die ihre Vorbuchhaltung selbst machen und ihre Finanzunterlagen nicht in eine Cloud-SaaS hochladen wollen.
Du gibst ihm zwei Dinge:
- deinen Sparkassen-Kontoauszug oder deine Kreditkartenabrechnung (PDF) und
- einen Ordner mit Rechnungen / Belegen (PDFs).
Dann:
- Findet es jede Buchung auf dem Auszug, zu der noch kein passender Beleg vorliegt.
- Ordnet es jede erkannte Rechnung automatisch der richtigen Buchung zu.
- Benennt es deine Belege in ein einheitliches, sauberes Schema um
(z. B.
2026-03-14_Hetzner_12,90EUR.pdf). - Verlinkt für die Lücken direkt die Rechnungsseite des Anbieters, damit du den fehlenden Beleg mit einem Klick holst — und exportiert den ganzen Bericht als CSV.
Für jede Buchung, die es nicht zuordnen kann, nennt es dir den Anbieter und wo du die Rechnung herunterlädst — du behältst die Kontrolle, es übernimmt nur das mühsame Abgleichen.
- Nichts wird hochgeladen. Es gibt kein Backend. Auszüge und Rechnungen werden über die File System Access API im Browser gelesen und verlassen deinen Rechner nie.
- Keine Cloud-KI — niemals. Abgleich und Rechnungs-Extraktion sind standardmäßig vollständig deterministisch und brauchen gar keine KI. Für knifflige, unbekannte Anbieter kannst du optional ein lokales Ollama-Modell aktivieren — es läuft auf deinem eigenen Rechner, also wird auch dann nichts in eine Cloud gesendet.
- Anonyme, cookielose, selbst gehostete Statistik. Nutzungsdaten laufen über
unsere eigene Rybbit-Instanz (kein Drittanbieter, keine Cookies) und nur auf der
Live-Seite. Sie können niemals Dokumentinhalte, Anbieternamen, Beträge oder
Kontodaten enthalten — im Code erzwungen (
packages/analytics), nicht nur per Richtlinie. - Lies den Quellcode. Die Abgleich-Logik sind ~700 Zeilen puren, getesteten Codes.
Die Anbieterliste liegt in einer einzigen, von der Community pflegbaren Datei:
providers.json. Ein neuer Eintrag sorgt dafür, dass
Belegabgleich den Anbieter auf dem Auszug erkennt und dich direkt zu seiner
Rechnungsseite führt. Kein Code nötig — einfach JSON bearbeiten und einen PR
öffnen.
Ein Eintrag sieht so aus:
{
"name": "Hetzner",
"aliases": ["hetzner"],
"invoiceUrl": "https://accounts.hetzner.com/invoice"
}-
name— der saubere Markenname, der in der UI angezeigt wird. -
aliases— kleingeschriebene Teilstrings, die gegen den (oft kryptischen) Händlertext auf dem Auszug gematcht werden, z. B."CLOUDFLARE.COUS"→cloudflare. Beliebig viele;*als Platzhalter ("google*ads"). -
invoiceUrl— die generische Rechnungs-/Beleg-Seite des Anbieters. Leer (""), wenn es keine stabile öffentliche URL gibt. Blättert der Anbieter seine Rechnungsliste nach Zeitraum, dürfen die Platzhalter{YYYY},{MM}und{YYYY-MM}darin stehen — sie werden mit dem Datum der Buchung gefüllt, damit der Link im richtigen Jahr landet statt auf Seite 1 von allem:{ "name": "Amazon", "aliases": ["amazon"], "invoiceUrl": "https://www.amazon.de/your-orders/orders?timeFilter=year-{YYYY}" } -
invoiceUrls(optional) — für Anbieter, die über mehrere Portale abrechnen. Der Auszug verrät meist nicht, welches es war, also zeigt der Beleg-Dialog einen Button pro Portal:{ "name": "Google", "aliases": ["google"], "invoiceUrl": "https://payments.google.com/paymentsinfo", "invoiceUrls": [ { "label": "Payments Center", "url": "https://payments.google.com/paymentsinfo" }, { "label": "Cloud Console", "url": "https://console.cloud.google.com/billing" } ] }invoiceUrlbleibt daneben als Einzel-Link bestehen.
⚠️ Niemals kontospezifische URLs oder IDs committen — nur die generische Rechnungsseite. Deine eigene Billing-ID (z. B.console.cloud.google.com/billing/<ID>/invoices) gehört nicht hierher: trage sie lokal in deinem Fork ein oder setze ein Lesezeichen.
Eine neue Bank? Implementiere das BankParser-Interface in packages/parsers
(siehe sparkasse.ts) und registriere es. Die Engine sieht nur ein normalisiertes
Charge[] und bleibt damit komplett bank-agnostisch.
bun install
bun test # Engine- + Parser-Tests
bun run dev # Web-App starten (packages/web)Statistik: auf der Live-Seite gehen anonyme, cookielose Nutzungsdaten an unser
selbst gehostetes Rybbit. Überall sonst — localhost, Previews, Forks — ist die
Statistik aus. Für eigene Deployments mit VITE_RYBBIT_SITE_ID /
VITE_RYBBIT_HOST überschreiben.
packages/
core/ @kah/core reine, isomorphe Engine: canon, Extraktion, Abgleich
parsers/ @kah/parsers pluggable Bank-Parser (vorerst nur Sparkasse)
analytics/ @kah/analytics datenschutzfreundliches Rybbit-Tracking (keine sensiblen Daten)
web/ @kah/web Svelte-App: Parsing im Browser, Abgleich, Umbenennen, CSV-Export
site/ Generator für die statischen Seiten (siehe unten)
public/site.css globales Stylesheet für Seiten *und* Tool
providers.json community-pflegbare Anbieter-Aliase + Rechnungs-Links
Die Website ist eine Multi-Page-Anwendung. Das Tool liegt unter /app/ und ist
der einzige Einstiegspunkt, der ein JS-Bundle lädt; alle übrigen Seiten werden zur
Bauzeit aus packages/web/site/*.mjs als fertiges HTML erzeugt (kein Bundle, kein
Framework zur Laufzeit — nur site.css und ein kleines site.js fürs Menü).
/ Landing
/fuer-freelancer/ Zielgruppenseiten
/fuer-kleinunternehmen/
/fuer-vereine/
/sparkasse-kontoauszug-belege/
/wissen/ + /wissen/<slug>/ Erklärtexte
/datenschutz/ Datenschutzerklärung
/haftungsausschluss/ Gewährleistung & Haftung
/app/ das Tool (noindex)
Offen beim Deployment: Needle Cloud ersetzt beim Ausliefern den
<title>undog:titlejeder Seite durch die im Projekt hinterlegten Werte (erkennbar am eingefügten<!-- Made with Needle -->). Im Build stehen die richtigen, seitenindividuellen Titel — ausgeliefert wird auf allen Seiten derselbe. Das lässt sich nicht im Repo beheben (derno-unfurl-Input der Deploy-Action betrifft nur Webhook-Nachrichten); es braucht eine Änderung an den Projekteinstellungen in Needle Cloud.
Inhalte werden in site/pages.mjs (Landing, Zielgruppen, Recht) und
site/articles.mjs (Wissen) gepflegt, Layout und Meta-Tags in site/layout.mjs,
wiederkehrende Abschnitte in site/partials.mjs. site/build.mjs schreibt daraus
die HTML-Dateien, sitemap.xml und robots.txt. Diese Dateien sind generiert
und git-ignoriert — bearbeite die site/*.mjs, nicht das erzeugte HTML. Beim
Ändern eines Textes lädt der Dev-Server neu; eine neue Seite braucht einen
Neustart, weil die Einstiegspunkte Teil der Vite-Konfiguration sind.
No booking without a receipt.
Belegabgleich matches your bank statement against your invoices, so you can see at a glance which booking is still missing a Beleg (receipt) — and fix it in minutes. Free, open source, and 100 % local: nothing ever leaves your browser.
Built for German freelancers, Kleinunternehmer, small GmbHs and Vereins- Schatzmeister who do their own pre-accounting and don't want to upload financial documents to a cloud SaaS.
You give it two things:
- your Sparkasse Kontoauszug (account statement) or Kreditkartenabrechnung (credit-card statement) as PDF, and
- a folder of Rechnungen / Belege (invoices/receipts) as PDFs.
It then:
- Finds every booking on the statement that has no matching Beleg yet.
- Assigns each invoice it recognizes to the right booking automatically.
- Renames your Belege into one clean, consistent scheme
(e.g.
2026-03-14_Hetzner_12,90EUR.pdf). - Links out to the vendor's billing page for the gaps, so you can grab the missing invoice in one click — and exports the whole report as CSV.
For each booking it can't place, it tells you the vendor and where to download the invoice — you stay in control, it just does the tedious matching.
- Nothing is uploaded. There is no backend. Statements and invoices are read in your browser via the File System Access API and never leave your machine.
- No cloud AI — ever. Matching and invoice extraction are fully deterministic by default and need no AI at all. For tricky, unknown vendors you can optionally enable a local Ollama model — it runs on your own machine, so even then nothing is sent to any cloud.
- Anonymous, cookieless, self-hosted analytics. Usage stats run through our
own Rybbit instance (no third party, no cookies) and only on the live site.
They can never carry document content, vendor names, amounts or account data —
enforced in code (
packages/analytics), not just policy. - Read the source. The matching engine is ~700 lines of pure, tested code.
The vendor list lives in one community-editable file:
providers.json. Adding a vendor means Belegabgleich can
recognize it on a statement and point you straight to its invoice page. No
coding required — just edit JSON and open a PR.
Each entry looks like this:
{
"name": "Hetzner",
"aliases": ["hetzner"],
"invoiceUrl": "https://accounts.hetzner.com/invoice"
}-
name— the clean brand name shown in the UI. -
aliases— lowercase substrings matched against the (often noisy) merchant text on the statement, e.g."CLOUDFLARE.COUS"→cloudflare. Add as many as needed; use*as a wildcard ("google*ads"). -
invoiceUrl— the vendor's generic billing/receipts page. Leave it as""if there's no stable public URL. When the vendor paginates its invoice list by period, the placeholders{YYYY},{MM}and{YYYY-MM}may appear in it — they are filled from the booking's date, so the link lands in the right year instead of on page one of everything:{ "name": "Amazon", "aliases": ["amazon"], "invoiceUrl": "https://www.amazon.de/your-orders/orders?timeFilter=year-{YYYY}" } -
invoiceUrls(optional) — for vendors that bill through several portals. The statement rarely says which one was charged, so the Beleg dialog renders one button per portal:{ "name": "Google", "aliases": ["google"], "invoiceUrl": "https://payments.google.com/paymentsinfo", "invoiceUrls": [ { "label": "Payments Center", "url": "https://payments.google.com/paymentsinfo" }, { "label": "Cloud Console", "url": "https://console.cloud.google.com/billing" } ] }invoiceUrlstays alongside it as the single-link fallback.
⚠️ Never commit account-specific URLs or IDs — only the generic billing page. Your own billing id (e.g.console.cloud.google.com/billing/<ID>/invoices) does not belong here: keep it in your local fork or a bookmark.
Adding a new bank? Implement the BankParser interface in packages/parsers
(see sparkasse.ts) and register it. The engine only ever sees a normalized
Charge[], so it stays completely bank-agnostic.
bun install
bun test # run the engine + parser tests
bun run dev # start the web app (packages/web)Analytics: on the live site, anonymous cookieless stats go to our self-hosted
Rybbit. Everywhere else — localhost, previews, forks — analytics is off.
Override with VITE_RYBBIT_SITE_ID / VITE_RYBBIT_HOST for your own deployment.
packages/
core/ @kah/core pure, isomorphic engine: canon, extraction, matching
parsers/ @kah/parsers pluggable bank parsers (Sparkasse only, for now)
analytics/ @kah/analytics privacy-first Rybbit usage tracking (no sensitive data)
web/ @kah/web Svelte app: in-browser parsing, matching, rename, CSV export
providers.json community-editable vendor aliases + invoice-download links