
LedgerLou Journal
Das Herzstück von LedgerLou: alle Buchungen landen hier als unveränderliche Einträge im Hauptbuch. Per REST API oder via MCP — jede Buchung folgt denselben GoBD-Regeln und ist vollständig nachvollziehbar.
API ReferenceKernkonzepte
LedgerLou ordnet das Hauptbuch in drei Ebenen:
Journal -> Intents -> Buchungszeilen
Das Journal ist die Gesamtsicht. Darin liegen viele Intents. Jeder Intent gruppiert eine oder mehrere Buchungszeilen, die gemeinsam einen Buchungsvorgang bilden.
| Konzept | Was es ist | Persistenz |
|---|---|---|
| Journal | Append-only Gesamtschau aller Intents eines Mandanten | ledger_events (GoBD-konform, hash-gekettet) |
| Intent | Fachliche Klammer für genau einen Buchungsvorgang innerhalb des Journals | Kein eigener Datensatz — als intent_id in ledger_events, documents, bank_transactions u. a. |
| Buchungszeile | Einzelne Soll/Haben-Zeile innerhalb eines Intents | ledger_events (1+ Zeilen pro Intent) |
Praktisch bedeutet das:
- Das Journal listet alle Intents chronologisch.
- Ein Intent gruppiert die Zeilen, die gemeinsam zu einem Vorgang gehören.
- Eine Buchung kann aus einer oder mehreren Buchungszeilen bestehen, je nach Split und Steuerlogik.
Journal
Das Journal ist die ledger_events-Tabelle — der unveränderliche Kern von LedgerLou. Jede bestätigte Buchung schreibt hier append-only Zeilen:
- Append-only — DB-Trigger verhindern UPDATE und DELETE (§ 146 AO / GoBD)
- Hash-Kette — jede Zeile enthält einen
audit_hashüber die eigenen Felder plus den Hash der Vorgängerzeile — Manipulation wäre sofort erkennbar - Lückenlose Nummerierung —
journal_numberist eine pro-Mandant monoton steigende Sequenz ohne Lücken - Intent-Gruppierung — Split-Buchungen schreiben mehrere Zeilen mit derselben
intent_id
GET /v1/journal gibt diese Zeilen angereichert mit Kontonamen, verknüpften Dokumenten und Source-Metadaten zurück.
Intent
Ein Intent ist eine UUID, die einen Buchungsvorgang als Einheit kennzeichnet. Er entsteht im Moment der Buchungsbestätigung und verknüpft alle Zeilen dieses Vorgangs — bei Split-Buchungen können das mehrere ledger_events-Zeilen sein, die dennoch dieselbe intent_id tragen.
Gleichzeitig ist der Intent das gemeinsame Bindeglied aller Module: Banktransaktionen und Dokumente bekommen nach ihrer Verarbeitung eine intent_id zugewiesen, die auf die zugehörigen Buchungszeilen zeigt.
Ein Intent kann nicht gelöscht werden. Fehler werden durch ein Storno korrigiert: dabei entsteht ein neuer Intent mit vertauschten Konten, der über reverses_intent_id auf den ursprünglichen verweist — beide Einträge bleiben im Journal sichtbar. Ohne expliziten Korrekturmodus wird das Storno mit heutigem Datum in der aktuellen offenen Periode gebucht; die Ursprungsperiode bleibt unverändert.
Buchungszeilen und Buchungsvorgang
Ein Buchungsvorgang erzeugt einen Intent und schreibt eine oder mehrere Buchungszeilen ins Journal. Jede Buchung muss balanciert sein (Soll = Haben) — der Validator lehnt unausgeglichene Buchungen ab, bevor irgendetwas in die Datenbank geschrieben wird.
Buchungen entstehen auf drei Wegen:
| Quelle | Weg |
|---|---|
| Direkt via API | POST /v1/bookings — rohe Journalzeilen explizit übergeben, sofort gebucht |
| Bank-Abgleich | Buchung entsteht beim Zuordnen einer Banktransaktion zu einem offenen Posten |
| Storno | Gegenbuchung mit vertauschten Konten unter einer neuen Intent-ID |
Intent-Lebenszyklus
Ein Intent ist unveränderlich. Sobald seine Zeilen im Journal stehen, werden sie nie mehr modifiziert oder gelöscht — auch nicht, wenn nachgelagerte Module wie der Bank-Abgleich ihre Zuordnung später wieder auflösen. Korrekturen entstehen ausschließlich durch neue Intents, die über reverses_intent_id auf den Ursprung verweisen.
Drei typische Szenarien zeigen, wie sich das im Zusammenspiel mit dem Bank-Abgleich konkret verhält:
Eine Rechnung wurde bereits manuell oder per API gebucht (Intent A, zwei Zeilen). Später trifft der Zahlungseingang auf dem Bankkonto ein und wird per Bank-Abgleich auf Intent A gezogen.
- 1Intent A existiert bereits im Journal — die beiden Zeilen sind unveränderlich.
- 2Beim Zuordnen wird ausschließlich auf der Banktransaktion ein Verweis gesetzt. Intent A wird nicht angefasst.
- 3Beim Rückgängigmachen des Abgleichs wird dieser Verweis auf der Banktransaktion wieder geleert. Intent A bleibt unberührt, es entsteht kein Storno.
Für eine offene Forderung (Intent A) wird während des Bank-Abgleichs eine neue Ausgleichsbuchung erzeugt. Diese lebt als eigener Intent B im Journal und trägt einen Settlement-Verweis auf Intent A.
- 1Der Abgleich erzeugt Intent B mit Herkunft „Bank-Abgleich” und einem Settlement-Verweis auf Intent A.
- 2Die Banktransaktion wird mit Intent B verknüpft. Intent A bleibt exakt wie zuvor — nur noch als ausgeglichen markiert.
- 3Beim Rückgängigmachen entsteht ein Storno-Intent C, der Intent B mit vertauschten Konten spiegelt. Intent A wird wieder als offen geführt, ohne jemals modifiziert worden zu sein.
Intent A (etwa eine Rechnung) wurde bereits durch eine Ausgleichsbuchung B aus dem Bank-Abgleich beglichen. Jetzt stellt sich heraus, dass Intent A falsch war und storniert werden muss.
- 1Zuerst wird die abhängige Ausgleichsbuchung B automatisch storniert — es entsteht Intent C, der B spiegelt.
- 2Anschließend wird Intent A storniert — es entsteht Intent D, der A spiegelt und per Storno-Verweis auf A zeigt.
- 3Alle vier Intents (A, B, C, D) bleiben im Journal sichtbar. Verknüpfte Banktransaktionen werden als offen markiert und können neu zugeordnet werden.
Reversal-Logik
Ein Storno ist in LedgerLou immer ein neuer Buchungs-Intent. Die Originalzeilen bleiben unverändert stehen, die Stornozeilen spiegeln Soll und Haben, übernehmen Audit-relevante Felder wie FX-Block, external_reference, custom_metadata und tax_code, und tragen reverses_intent_id auf den stornierten Intent.
Für das Buchungsdatum gibt es zwei Modi:
| Modus | REST posting_mode | MCP posting_mode | Wirkung |
|---|---|---|---|
| Aktuelle Periode | current_period oder weglassen | current_period oder weglassen | Storno wird mit heutigem Datum in der aktuellen offenen Periode gebucht. Das ist der Default für API und MCP, damit bestehende Integrationen stabil bleiben. |
| Ursprungsperiode | original_period | original_period | Storno wird auf das Buchungsdatum der Originalbuchung gesetzt. Das ist für Jahresabschluss- oder Periodenkorrekturen sinnvoll, solange die Ursprungsperiode noch offen ist. |
original_period ist ein expliziter Korrekturmodus. LedgerLou prüft vor dem Schreiben, ob die Zielperiode offen ist. Ist sie soft-locked oder hard-locked, wird die Stornierung mit PERIOD_LOCKED abgelehnt. Dann muss die Periode bewusst wieder geöffnet werden oder die Korrektur bleibt in der aktuellen Periode.
Bei bereits abgeglichenen Intents kehrt LedgerLou zuerst abhängige Settlement-Intents um und löst die Bankverknüpfung. Gruppierte Bankabgleiche bleiben geschützt: Wenn ein Settlement noch andere aktive Intents ausgleicht, muss zuerst die Gruppenzuordnung aufgehoben werden.
curl -X POST https://api.ledgerlou.de/v1/journal/reverse \
-H "Authorization: Bearer $LEDGERLOU_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"intent_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "Falsche Kontierung",
"posting_mode": "original_period"
}'
Für MCP gilt dasselbe.
{
"intent_id": "3fa85f64-5717-4562-b3fc-2c963f66afa6",
"reason": "Falsche Kontierung",
"posting_mode": "original_period"
}
Metadaten & Externe Referenzen
Jede Buchung kann optional zwei Intent-Level-Felder mitgeben, die den Vorgang mit externen Systemen verknüpfen oder zusätzlichen Kontext liefern:
| Eigenschaft | external_reference | custom_metadata |
|---|---|---|
| Typ | string (max. 500 Zeichen) | Flaches JSON-Objekt (string | number | boolean | null als Werte) |
| Pflicht | Nein | Nein |
| Unveränderlich | Ja (GoBD) | Ja (GoBD) |
| Im Audit-Hash | Ja | Ja (deterministische Sortierung) |
| Filterbar | GET /v1/journal?externalReference=... | — |
| Storno-Verhalten | Wird identisch vom Original übernommen | Wird identisch vom Original übernommen |
Typische Anwendungsfälle:
- Rechnungsnummer als Referenz —
external_reference: "RE-2025-0042"verknüpft die Buchung mit der Rechnung im ERP. Per?externalReference=RE-2025-0042ist der zugehörige Journal-Eintrag sofort auffindbar. - Kostenstelle und Projekt —
custom_metadata: { "cost_center": "CC-100", "project": "alpha" }ordnet die Buchung organisatorisch zu, ohne das Kontenrahmenmodell anpassen zu müssen. - Herkunfts-Tags für Automatisierungen —
custom_metadata: { "source_system": "shopify", "order_id": "ORD-9981" }gibt MCP-Agents und ETL-Pipelines einen strukturierten Rückkanal.
Fremdwährungsbuchungen
LedgerLou führt die Bücher in EUR. Buchungen in Fremdwährung (z. B. USD, GBP, CHF) werden nativ im EUR-Betrag gebucht und tragen zusätzlich einen FX-Block mit den Originaldaten:
| Feld | Bedeutung |
|---|---|
currency | ISO 4217 Währungscode (z. B. USD) |
foreign_amount | Brutto-Betrag in Fremdwährung |
rate | Umrechnungskurs Fremdwährung → EUR (ECB-Konvention) |
rate_date | Stichtag des Kurses |
rate_source | Quelle (z. B. ECB, manual, bank) |
Wichtige Regeln:
- Der Aufrufer gibt den Kurs explizit an — LedgerLou ruft keine externen Kursquellen ab.
- Der EUR-Betrag in
linesmuss zum FX-Block passen:foreign_amount × rate ≈ Summe der Soll-Beträge(Toleranz: max. 1 Cent oder 0,01 %). - Bei Split-Buchungen wird der Fremdwährungsbetrag proportional auf die Zeilen verteilt (Largest-Remainder-Verfahren, 4 Dezimalstellen).
- Stornobuchungen übernehmen den FX-Block identisch aus dem Original — es wird kein neuer Kurs berechnet.
- EUR-Buchungen setzen keinen FX-Block (
fx: nulloder Feld weglassen). - Eröffnungsbilanzwerte (
POST /v1/bookings/opening-balances) unterstützen kein FX.
Workflow
POST /v1/journal/reverse auf Intent-Ebene storniert. Standardmäßig entsteht ein neuer Storno-Intent in der aktuellen offenen Periode; mit posting_mode: “original_period” kann eine offene Ursprungsperiode korrigiert werden. Kein stilles Überschreiben — jede Korrektur ist im Audit-Trail sichtbar.