Zum Inhalt springen
GuideEN 16931XRechnung

Ein Cent zu viel macht die E-Rechnung ungültig, 99 Cent zu viel Steuer nicht

Wir haben dieselbe Rechnung in Varianten an unsere API geschickt. Ein Cent Abweichung in der Endsumme: abgelehnt. 99 Cent zu viel Umsatzsteuer: gültig. Warum die Norm so prüft und was das für deinen Code heißt.

Patrick Jerominek

Patrick Jerominek

Co-founder of xhub.io

9. November 20265 Min Lesezeit
Ein Cent zu viel macht die E-Rechnung ungültig, 99 Cent zu viel Steuer nicht

Wir haben eine kleine Rechnung über 916,30 € als XRechnung an POST /api/v1/invoice/de/xrechnung/generate geschickt, einmal korrekt und dann mit gezielten Fehlern. Das Ergebnis:

VarianteWas wir geändert habenAntwort
korrektnichts200, gültig
Endsumme +0,01BT-112 = 916.31 statt 916.30422, BR-CO-15
Steuer +0,01BT-110 = 146.31, Endsumme passend dazu200, gültig
Steuer +0,99BT-110 = 147.29, Endsumme passend dazu200, gültig
Steuer +1,00BT-110 = 147.30, Endsumme passend dazu422, BR-CO-17 und BR-S-09

Ein Cent in der Summe reicht für die Ablehnung, 99 Cent in der Steuer nicht. Das ist kein Fehler unserer API, so prüft die Norm. Wer das nicht weiß, verlässt sich auf eine Prüfung, die genau den teureren Fehler durchlässt.


Die Rechnung

Zwei Positionen, 19 % Umsatzsteuer. Im Video klappt sie in die Felder der europäischen Norm EN 16931 um:

FeldBedeutungWert
BT-131Nettobetrag je Position480.00 (Montage), 290.00 (Material)
BT-109Gesamtbetrag ohne Umsatzsteuer770.00
BT-110Umsatzsteuer gesamt146.30
BT-112Gesamtbetrag mit Umsatzsteuer916.30
BT-115Fälliger Zahlbetrag916.30

Dazu kommen Rechnungsnummer (BT-1), Datum (BT-2), Rechnungsart (BT-3), Währung (BT-5) und die Artikelnamen (BT-153). Getestet haben wir XRechnung in UBL-Syntax. Firmen, USt-IdNrn. und IBAN in unseren Anfragen sind Beispieldaten.

Warum der eine Cent scheitert

Die Summenregeln der Norm sind exakt. BR-CO-15 verlangt BT-112 = BT-109 + BT-110, ohne Spielraum. 770.00 + 146.30 ergibt 916.30, nicht 916.31. Die API antwortet mit HTTP 422 (gekürzt um filename, mimeType, hash, payloadHash und warnings):

json
1{
2 "success": false,
3 "format": "xrechnung",
4 "data": "…",
5 "complianceErrors": [
6 {
7 "code": "SPEC_RULE_FAILED",
8 "rule": "BR-CO-15",
9 "message": "[BR-CO-15]-Invoice total amount with VAT (BT-112) = Invoice total amount without VAT (BT-109) + Invoice total VAT amount (BT-110).",
10 "field": "/Q{urn:oasis:names:specification:ubl:schema:xsd:Invoice-2}Invoice[1]"
11 }
12 ]
13}

data enthält trotzdem das erzeugte XML. So siehst du, was die Prüfung abgelehnt hat. Verschicken solltest du es nicht.

Dasselbe gilt für die Nachbarregeln: BR-CO-13 (BT-109 = Summe der BT-131 − Abzüge + Zuschläge) und BR-CO-16 (BT-115 = BT-112 − bereits gezahlt + Rundung).

Warum 99 Cent durchgehen

Die Steuer prüfen andere Regeln, BR-CO-17 und für den Normalsatz BR-S-09. BR-CO-17 lautet im Text: Steuerbetrag je Kategorie = Bemessungsgrundlage × Steuersatz, gerundet auf zwei Nachkommastellen. Die offizielle Schematron der Norm prüft aber großzügiger. Sinngemäß steht dort:

text
1|Steuerbetrag| − 1 < gerundet(Bemessungsgrundlage × Satz) < |Steuerbetrag| + 1

Der angegebene Steuerbetrag darf also um bis zu 99 Cent vom berechneten abweichen. Bei 770.00 × 19 % sind 146.30 richtig. Gemessen: 145.31 und 147.29 bestehen die Prüfung, 145.30 und 147.30 scheitern an BR-CO-17 und BR-S-09. Rundet ein System die Steuer je Position und addiert dann, weicht es je Position um höchstens einen halben Cent ab. Bei kleinen Rechnungen bleibt das weit innerhalb der Toleranz.

Gültig heißt hier nicht richtig. Wer mehr Umsatzsteuer ausweist, als er schuldet, schuldet nach § 14c Abs. 1 UStG auch den Mehrbetrag, bis er die Rechnung berichtigt. Die Validierung bewahrt dich davor nicht. Rechne die Steuer selbst, je Steuersatz aus der Bemessungsgrundlage, und behandle eine bestandene Prüfung nicht als Beleg, dass der Betrag stimmt:

ts
1// je Steuersatz, nicht je Position
2const net = round2(sum(linesWithRate.map((l) => l.netAmount)));
3const tax = round2((net * rate) / 100);
4const total = round2(net + tax);

Was eine Anzahlung ändert

Ein zweiter Fall: Der Kunde hat 300 € angezahlt. Wir haben dieselbe Rechnung mit prepaidAmount: 300 geschickt. Ergebnis, gültig:

FeldWert
BT-109 Gesamtbetrag ohne Umsatzsteuer770.00
BT-110 Umsatzsteuer146.30
BT-112 Gesamtbetrag mit Umsatzsteuer916.30
BT-113 Bereits gezahlt300.00
BT-115 Fälliger Zahlbetrag616.30

Die Anzahlung senkt nur den Zahlbetrag. Bemessungsgrundlage und Steuer bleiben, denn der Kunde hat früher gezahlt, nicht weniger. Zieht man die 300 € stattdessen von der Nettosumme ab, lehnt die API schon vor der Erzeugung ab: Subtotal (470) does not match sum of items (770). Difference: 300. Die zweite Meldung derselben Antwort zeigt, dass auch die Bemessungsgrundlage eine Toleranz hat: BR-S-08 … requires 469 < sum < 471.

Zwei Arten von 422

Unser allererster Lauf scheiterte an etwas anderem. Die Käuferreferenz BT-10, die jede XRechnung nach BR-DE-15 braucht, stand in unserem JSON an der falschen Stelle: oben statt in countrySpecific. Bei öffentlichen Auftraggebern steht dort die Leitweg-ID, sonst etwa die Bestellnummer des Kunden. Die Antwort, ebenfalls HTTP 422, sah anders aus (gekürzt):

json
1{
2 "success": false,
3 "data": "",
4 "errors": [
5 {
6 "code": "DE_REF_002",
7 "rule": "BR-DE-15",
8 "message": "Käuferreferenz (BT-10) ist für XRechnung Pflicht (BR-DE-15). Bitte eine Käuferreferenz oder Leitweg-ID (B2G) angeben.",
9 "field": "countrySpecific.buyerReference"
10 }
11 ]
12}

Hier hat die API gar nicht erst erzeugt. errors zeigt mit field auf das Feld in deinem JSON. Bei complianceErrors wurde das Dokument erzeugt und danach geprüft, field ist ein XPath ins XML. Für die Fehlerbehandlung heißt das: Bei errors korrigierst du die Eingabe, bei complianceErrors meistens deine Rechenlogik.

Der zweite Lauf scheiterte an BR-DE-1: Eine XRechnung braucht Zahlungsangaben (BG-16), und wir hatten weder Bankverbindung noch Zahlungsart geschickt. Gültig wurde sie mit seller.bankAccount.iban und countrySpecific.paymentMeansCode: "58" (SEPA-Überweisung).

Selbst ausprobieren

Im Playground kannst du ohne eigenen Schlüssel eine Rechnung erzeugen und die Antwort ansehen. Die Feldnamen stehen in der Feldreferenz, den Einstieg mit eigenem Schlüssel beschreibt der Quickstart.

Artikel teilen

Ähnliche Artikel

Bereit, E-Rechnungen zu meistern?

Starte in unter 5 Minuten mit Invoice-api.xhub. Keine Kreditkarte erforderlich.