Idempotenz
Doppelte Buchungen vermeiden: Wiederholungen, Stapel und Dubletten beim KI-Agenten
Doppelte Buchungen entstehen bei KI-Agenten meist durch Wiederholungen: Eine Antwort bleibt aus, der Agent sendet erneut – und bucht ein zweites Mal. In der Buchhaltung von jahresabschluss.io verhindern das drei Mechanismen: Schreibende Einzelaufrufe tragen einen Idempotenzschlüssel (eine UUID), mit dem eine Wiederholung den ersten Vorgang zurückliefert statt einen neuen anzulegen; der Stapelimport import_journal_batch überspringt Zeilen, die schon importiert wurden; und Eingangsrechnungen und Belege durchlaufen eine Dublettenprüfung. Wer die Regeln kennt, vermeidet die eine Falle, die bleibt: Eine neue UUID erzeugt einen zweiten Vorgang.
Stand: 2026-10-19
Die Grundregel: dieselbe UUID für dieselbe Anfrage
Buchungen, Rechnungen, Zahlungen, Geschäftspartner und viele weitere Schreibaufrufe nehmen ein Feld idempotency_key an, der Rechnungsversand das Feld dispatch_request_id. Erzeugen Sie die UUID einmal je Vorgang und verwenden Sie sie bei jeder Wiederholung nach Zeitüberschreitung oder Netzwerkfehler unverändert, mit denselben Daten. Dann liefert die Software den ursprünglichen Vorgang zurück, gekennzeichnet mit idempotent: true. Kommt derselbe Schlüssel mit anderen Daten, lehnt die Software ihn ab – bei Einzelbuchungen mit 409 –, statt etwas Neues anzulegen.
Der MCP-Server sagt das jedem Agenten ausdrücklich: Wiederholungen nach Zeitüberschreitung oder Netzwerkfehler verwenden dieselbe UUID und dieselben Daten – eine neue UUID erzeugt einen zweiten Vorgang. Auch eine Wiederholung des Rechnungsversands mit derselben dispatch_request_id verschickt nicht ein zweites Mal und zählt nicht doppelt auf die Versandgrenze.
Stapel: alles oder nichts – oder Zeile für Zeile
import_journal_batch – alles oder nichts
Bis 5.000 Buchungszeilen als ein Stapel. Zeilen, die zeichengleich schon importiert sind, werden übersprungen; ein Wiederholungslauf ist deshalb gefahrlos. Ein Aufruf darf höchstens 8 Sekunden rechnen: batch_timeout heißt, es wurde nichts gebucht – den Stapel geteilt erneut senden, bewährt sind bis 700 Buchungszeilen je Aufruf. Eine Zeile außerhalb des Geschäftsjahres oder in einem festgeschriebenen Zeitraum bricht den ganzen Stapel ab.
book_incoming_invoices_batch und record_invoice_payments_batch – Zeile für Zeile
Bis 50 Rechnungen bzw. Zahlungen nacheinander, nicht atomar. Jede Zeile braucht ihren eigenen idempotency_key und hat ihr eigenes Ergebnis: ok, idempotent, fehler oder nicht_verarbeitet. Nach einem Abbruch senden Sie denselben Stapel unverändert erneut; schon gebuchte Zeilen kommen als idempotent zurück. stop_on_error hält nach dem ersten Fehler an.
Eine Feinheit beim Stapelimport: Zwei wirklich gleiche Zeilen in derselben Datei werden beide gebucht – die zweite Kopie in der Datei gleicht nur gegen eine zweite schon gebuchte Kopie ab. Und stornierte Buchungen zählen nicht als Bestand; eine bewusst zurückgenommene Zeile lässt sich erneut importieren.
Probelauf vor dem Import
Mit dry_run: true läuft der Stapel durch alle Prüfungen, ohne zu buchen. Die Antwort zeigt die Buchungszeilen, ihre Wirkung auf die Konten und welche Zeilen als Dublette übersprungen würden (Beispielwerte, SKR03):
curl -X POST "https://api.jahresabschluss.io/functions/v1/bookkeeping-api/v1/journal-entries/batch" \
-H "Authorization: Bearer $JAB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"dry_run": true,
"batch_name": "Privatentnahmen Oktober",
"rows": [
{ "booking_date": "2026-10-05", "debit_account": "1800", "credit_account": "1200",
"amount": 500.00, "description": "Privatentnahme", "document_no": "PE-10-01" }
]
}'Sieht der Bericht gut aus, senden Sie denselben Körper ohne dry_run. Den Probelauf kennen auch book_incoming_invoice – dort läuft die Dublettenprüfung mit und meldet einen Verdacht schon vor dem Buchen – sowie die Zahlungswerkzeuge und beide Rechnungs- und Zahlungsstapel.
Dublettenprüfung bei Rechnungen und Belegen
- book_incoming_invoice: Grundlage der Prüfung ist die Rechnungsnummer des Lieferanten. Bei einem Verdacht antwortet die API mit 409 possible_duplicate und nennt die vorhandene Buchung (invoice_duplicate_candidate_id). Gebucht wird nur, wenn Sie bestätigen, dass es eine andere Rechnung ist – dann mit trotz_dublette: true und genau dieser Kennung.
- upload_receipt bzw. POST /v1/receipts: Dieselbe Datei erneut einzuliefern ergibt denselben Beleg (outcome existing). Steht dieselbe Rechnung mit gleicher Nummer, gleichem Partner und gleichem Betrag schon in den Büchern, wird die Kopie nicht gespeichert (outcome duplicate).
- confirm_receipt: Dublettenhinweise der Belegprüfung lassen sich über die API nicht übersteuern. Ist es eine Dublette, legt der Agent den Beleg mit dismiss_receipt und Begründung ab.
- import_bank_statement: Beim Einlesen eines Kontoauszugs prüft die App Konto, Salden und Dubletten. Ein schon gebuchter Bankumsatz wird nicht ein zweites Mal gebucht, sondern mit book_bank_transaction und reason korrigiert – Storno und Neubuchung in einem Schritt.
Zu viele Anfragen: 429 richtig behandeln
Je Schlüssel gelten 120 Anfragen pro Minute. Darüber antwortet die API mit 429, dem Kopf Retry-After und dem Feld retry_after_seconds. Warten, dann denselben Aufruf mit derselben UUID wiederholen – etwa so in Python:
import os, time, uuid, requests
API = "https://api.jahresabschluss.io/functions/v1/bookkeeping-api/v1"
KOPF = {"Authorization": f"Bearer {os.environ['JAB_API_KEY']}"}
def buchen(daten: dict, versuche: int = 5):
daten = {**daten, "idempotency_key": daten.get("idempotency_key") or str(uuid.uuid4())}
for _ in range(versuche):
try:
r = requests.post(f"{API}/journal-entries", json=daten, headers=KOPF, timeout=60)
except requests.RequestException:
time.sleep(5) # gleiche UUID, gleiche Daten
continue
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "60")))
continue
return r.json() # bei Wiederholung: idempotent = true
raise RuntimeError("Nicht bestätigt – Stand mit list_journal_entries prüfen")Mehrere Agenten mit demselben Schlüssel teilen sich dessen Budget; jeder Schlüssel hat sein eigenes, und je Gesellschaft sind bis zu 10 aktive Schlüssel möglich. Für eingelieferte Belege gilt zusätzlich eine Grenze von 200 Anfragen je Gesellschaft in 24 Stunden.
Steuern: nie automatisch wiederholen
Eine Übermittlung an das Finanzamt ist nach außen wirksam und nicht rückholbar; sie wird nie automatisch wiederholt. Über den Agenten entsteht keine Übermittlung, sondern ein vorbereiteter Vorgang mit Prüfwert (prepare_tax_filing) und ein Freigabe-Link, über den Sie in der App übermitteln. Bevor Sie etwas erneut anstoßen, zeigen list_tax_filings und get_tax_operation den Stand mit Transferticket. Prüfen und Vorbereiten sind auf 30 Aufrufe je Stunde und Schlüssel begrenzt.
Wenn doch doppelt gebucht wurde
- Finden: list_journal_entries mit belegfeld1 zeigt alle Buchungen mit derselben Belegnummer; mit since_entry_no grenzen Sie auf den letzten Lauf ein.
- Einzelne Buchung: storno_journal_entry mit Begründung.
- Ganzer Stapel: storno_journal_batch nimmt alle noch nicht stornierten Buchungen eines Imports in einer Transaktion zurück – vorher Stapel und Anzahl bestätigen.
- Doppelte Eingangsrechnung: cancel_incoming_invoice; ist sie schon bezahlt, zuerst storno_payment.
- Doppelte Ausgangsrechnung: cancel_outgoing_invoice stellt eine Stornorechnung mit eigener Nummer aus – nicht rückholbar, deshalb nur nach Ihrer Bestätigung.
Grenzen
Der Idempotenzschlüssel schützt nur, wenn der Agent ihn bei der Wiederholung auch wiederverwendet. Für einzelne Buchungen mit create_journal_entry beschreibt das Werkzeug keine inhaltliche Dublettenprüfung wie bei Eingangsrechnungen – zwei Aufrufe mit verschiedenen UUIDs ergeben zwei Buchungen. Beim Stapelimport werden zwei gleiche Zeilen derselben Datei beide gebucht. Und ob zwei Rechnungen wirklich dieselbe sind, entscheidet am Ende ein Mensch.
Häufige Fragen
Was passiert, wenn mein Agent nach einem Timeout erneut sendet?
Mit derselben UUID im idempotency_key und denselben Daten liefert die Software den ersten Vorgang zurück, gekennzeichnet mit idempotent: true – es entsteht nichts doppelt. Mit einer neuen UUID entsteht ein zweiter Vorgang.
Bucht import_journal_batch doppelt, wenn ich dieselbe Datei zweimal sende?
Nein. Zeilen, die zeichengleich schon importiert sind, werden übersprungen. Nur zwei wirklich gleiche Zeilen innerhalb derselben Datei werden beide gebucht.
Was bedeutet batch_timeout?
Der Stapel war für einen Aufruf zu groß und wurde nach 8 Sekunden vollständig zurückgerollt; es wurde nichts gebucht. Teilen Sie ihn auf, bewährt sind bis 700 Buchungszeilen je Aufruf, und senden Sie die Teile erneut.
Welche Stapel sind atomar?
import_journal_batch bucht alles oder nichts. book_incoming_invoices_batch und record_invoice_payments_batch arbeiten Zeile für Zeile, jede mit eigenem Idempotenzschlüssel und eigenem Ergebnis.
Kann ich eine Dublettenwarnung übergehen?
Bei Eingangsrechnungen ja, nach Ihrer Bestätigung mit trotz_dublette: true und der genannten invoice_duplicate_candidate_id. Bei Belegen in der Belegprüfung lässt sich ein Dublettenhinweis über die API nicht übersteuern.
Was tue ich bei einer 429-Antwort?
So lange warten, wie Retry-After angibt, und denselben Aufruf mit derselben UUID wiederholen. Je Schlüssel gelten 120 Anfragen pro Minute.
Vom selben Anbieter: Jahresabschluss nach HGB – der komplette Leitfaden auf jahresabschluss.io.