E-Rechnung

XRechnung implementieren: Schritt-für-Schritt Anleitung für Entwickler

22. März 202612 Min.

Warum XRechnung implementieren?

Ab 1. Januar 2027 müssen alle deutschen Unternehmen im B2B-Bereich E-Rechnungen versenden können. XRechnung ist der deutsche Standard nach EN 16931 — und das Pflichtformat für Rechnungen an öffentliche Auftraggeber. Wer Software für Rechnungsstellung entwickelt, kommt an XRechnung nicht vorbei.

Dieser Artikel zeigt Ihnen als Entwickler den direkten Weg: XML-Struktur verstehen, Pflichtfelder korrekt befüllen, gegen EN 16931 validieren und per API automatisieren.

1. XML-Struktur: UBL 2.1 vs. CII

XRechnung unterstützt zwei XML-Syntaxen. Beide sind gleichwertig gültig — die Wahl hängt von Ihrem Anwendungsfall ab.

UBL 2.1 (Universal Business Language)

UBL ist der international verbreitetere Standard. Die Rechnungsdaten liegen im Namespace urn:oasis:names:specification:ubl:schema:xsd:Invoice-2. Das Root-Element ist <Invoice>.

<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2"
         xmlns:cac="urn:oasis:names:specification:ubl:schema:xsd:CommonAggregateComponents-2"
         xmlns:cbc="urn:oasis:names:specification:ubl:schema:xsd:CommonBasicComponents-2">
  <cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_3.0</cbc:CustomizationID>
  <cbc:ID>RE-2026-001</cbc:ID>
  <cbc:IssueDate>2026-03-22</cbc:IssueDate>
  <cbc:InvoiceTypeCode>380</cbc:InvoiceTypeCode>
  <cbc:DocumentCurrencyCode>EUR</cbc:DocumentCurrencyCode>
  <cac:AccountingSupplierParty>
    <!-- Rechnungssteller -->
  </cac:AccountingSupplierParty>
  <cac:AccountingCustomerParty>
    <!-- Rechnungsempfänger -->
  </cac:AccountingCustomerParty>
  <cac:LegalMonetaryTotal>
    <!-- Summen -->
  </cac:LegalMonetaryTotal>
  <cac:InvoiceLine>
    <!-- Rechnungspositionen -->
  </cac:InvoiceLine>
</Invoice>

UN/CEFACT CII (Cross-Industry Invoice)

CII ist die UN-Syntax, die auch ZUGFeRD verwendet. Das Root-Element ist <CrossIndustryInvoice>. Die Daten sind hierarchisch in Trade-Blöcke gegliedert.

<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice
    xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
    xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
    xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
  <rsm:ExchangedDocumentContext>
    <ram:GuidelineSpecifiedDocumentContextParameter>
      <ram:ID>urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_3.0</ram:ID>
    </ram:GuidelineSpecifiedDocumentContextParameter>
  </rsm:ExchangedDocumentContext>
  <rsm:ExchangedDocument>
    <ram:ID>RE-2026-001</ram:ID>
    <ram:TypeCode>380</ram:TypeCode>
    <ram:IssueDateTime>
      <udt:DateTimeString format="102">20260322</udt:DateTimeString>
    </ram:IssueDateTime>
  </rsm:ExchangedDocument>
  <rsm:SupplyChainTradeTransaction>
    <!-- Handelspartner, Lieferung, Zahlung -->
  </rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>

UBL oder CII?

UBL eignet sich, wenn Sie international agieren oder PEPPOL nutzen. CII ist die Wahl, wenn Sie auch ZUGFeRD unterstützen wollen — ZUGFeRD verwendet ausschließlich CII.

2. Pflichtfelder nach EN 16931

EN 16931 definiert über 160 semantische Elemente. Nicht alle sind Pflicht. Die folgende Tabelle zeigt die Mindestanforderungen für eine gültige XRechnung:

BT-Nr.FeldBeschreibungBeispielwert
BT-1RechnungsnummerEindeutige Kennung der RechnungRE-2026-001
BT-2RechnungsdatumAusstellungsdatum2026-03-22
BT-3RechnungsartUNTDID 1001 Code380 (Rechnung)
BT-5WährungISO 4217 WährungscodeEUR
BT-27VerkäufernameName des RechnungsstellersMuster GmbH
BT-44KäufernameName des RechnungsempfängersKunde AG
BT-109Zahlbarer BetragGesamtbetrag inkl. MwSt.1190.00
BT-112NettobetragGesamtbetrag exkl. MwSt.1000.00
BT-115Fälliger BetragZu zahlender Betrag1190.00

Zusätzliche Pflichtfelder für XRechnung (BR-DE)

Die deutschen Geschäftsregeln (BR-DE) erweitern die EN 16931-Pflichtfelder:

  • Leitweg-ID (BT-10): Pflicht bei B2G-Rechnungen. Format: 04011000-12345-67
  • Bankverbindung (BG-17): IBAN des Verkäufers bei Überweisung
  • E-Mail-Adresse (BT-43): Kontaktadresse des Verkäufers
  • Zahlungsbedingungen (BT-20): Freitext für Zahlungskonditionen

3. XRechnung implementieren mit der E-Invoice API

Statt XML manuell zu bauen, können Sie die E-Invoice API nutzen. Sie senden Rechnungsdaten als JSON — die API erzeugt valides XRechnung-XML.

API starten (Docker)

docker run -p 8000:8000 ghcr.io/jenslaufer/e-invoice:latest

XRechnung generieren (Python)

Dieses Beispiel erstellt eine vollständige XRechnung im UBL-Format:

import requests

invoice_data = {
    "invoice_number": "RE-2026-001",
    "issue_date": "2026-03-22",
    "due_date": "2026-04-21",
    "currency_code": "EUR",
    "invoice_type_code": "380",
    "buyer_reference": "04011000-12345-67",  # Leitweg-ID
    "seller": {
        "name": "Muster GmbH",
        "street": "Musterstraße 1",
        "city": "Berlin",
        "postal_code": "10115",
        "country_code": "DE",
        "tax_id": "DE123456789",
        "contact": {
            "email": "rechnung@muster.de"
        },
        "bank_account": {
            "iban": "DE89370400440532013000"
        }
    },
    "buyer": {
        "name": "Bundesministerium für Beispiele",
        "street": "Regierungsstraße 10",
        "city": "Berlin",
        "postal_code": "10117",
        "country_code": "DE"
    },
    "lines": [
        {
            "description": "Softwareentwicklung",
            "quantity": 10,
            "unit_code": "HUR",
            "unit_price": 100.00,
            "tax_category": "S",
            "tax_percent": 19.0
        }
    ],
    "payment_terms": "Zahlbar innerhalb von 30 Tagen"
}

# XRechnung als UBL generieren
response = requests.post(
    "http://localhost:8000/api/v1/invoices/xml",
    json=invoice_data,
    params={"syntax": "ubl"}
)

if response.status_code == 200:
    with open("rechnung.xml", "wb") as f:
        f.write(response.content)
    print("XRechnung erstellt: rechnung.xml")
else:
    print(f"Fehler: {response.json()}")

CII-Syntax verwenden

Für CII-Output ändern Sie nur den Parameter:

response = requests.post(
    "http://localhost:8000/api/v1/invoices/xml",
    json=invoice_data,
    params={"syntax": "cii"}
)

4. Validierung gegen EN 16931

Eine XRechnung muss fünf Validierungsschichten bestehen. Jede Schicht prüft unterschiedliche Aspekte:

SchichtPrüfungWerkzeug
1. SchemaXML-Struktur und DatentypenXSD-Validierung
2. EN 16931Europäische Geschäftsregeln (BR-*)Schematron
3. XRechnung CIUSDeutsche Geschäftsregeln (BR-DE-*)Schematron
4. BerechnungSummen, Steuern, RundungArithmetische Prüfung
5. SemantikCodelisten, Referenzen, KonsistenzGeschäftslogik

Validierung per API

# XRechnung validieren
with open("rechnung.xml", "rb") as f:
    xml_content = f.read()

response = requests.post(
    "http://localhost:8000/api/v1/invoices/validate",
    files={"file": ("rechnung.xml", xml_content, "application/xml")}
)

result = response.json()
print(f"Gültig: {result['valid']}")

if not result["valid"]:
    for error in result["errors"]:
        print(f"  [{error['rule']}] {error['message']}")

Eine typische Validierungsantwort bei Fehlern:

{
  "valid": false,
  "errors": [
    {
      "rule": "BR-DE-1",
      "message": "Eine XRechnung muss eine Leitweg-ID enthalten (BT-10).",
      "severity": "error",
      "location": "/Invoice"
    }
  ],
  "warnings": []
}

Validierung in die CI/CD-Pipeline integrieren

Validieren Sie generierte Rechnungen automatisch in Ihrer Pipeline:

# validate_invoices.py
import sys
import requests
import glob

api_url = "http://localhost:8000/api/v1/invoices/validate"
errors_found = False

for xml_file in glob.glob("output/*.xml"):
    with open(xml_file, "rb") as f:
        resp = requests.post(
            api_url,
            files={"file": (xml_file, f, "application/xml")}
        )
    result = resp.json()
    if not result["valid"]:
        errors_found = True
        print(f"FEHLER in {xml_file}:")
        for err in result["errors"]:
            print(f"  [{err['rule']}] {err['message']}")
    else:
        print(f"OK: {xml_file}")

sys.exit(1 if errors_found else 0)

5. Häufige Fehler und wie Sie sie vermeiden

Diese Fehler treten bei der EN 16931 Implementierung am häufigsten auf:

Fehler 1: Falsche CustomizationID

Die CustomizationID muss exakt zur XRechnung-Version passen. Ein Tippfehler führt zur Ablehnung.

<!-- Falsch -->
<cbc:CustomizationID>urn:cen.eu:en16931:2017</cbc:CustomizationID>

<!-- Richtig (XRechnung 3.0) -->
<cbc:CustomizationID>urn:cen.eu:en16931:2017#compliant#urn:xoev-de:kosit:standard:xrechnung_3.0</cbc:CustomizationID>

Fehler 2: Steuerberechnung mit Rundungsfehlern

EN 16931 verlangt kaufmännische Rundung auf 2 Dezimalstellen. Fließkomma-Arithmetik führt zu Abweichungen.

# Falsch: Fließkomma-Fehler
tax = 100.05 * 0.19  # 19.0095000000000027...

# Richtig: Decimal verwenden
from decimal import Decimal, ROUND_HALF_UP
tax = (Decimal("100.05") * Decimal("0.19")).quantize(
    Decimal("0.01"), rounding=ROUND_HALF_UP
)  # Decimal('19.01')

Fehler 3: Fehlende Leitweg-ID bei B2G

Die Geschäftsregel BR-DE-1 verlangt eine Leitweg-ID (BT-10) für alle Rechnungen an öffentliche Auftraggeber. Ohne Leitweg-ID wird die Rechnung abgelehnt.

Fehler 4: Datumsformat in CII

CII verwendet ein anderes Datumsformat als UBL. Häufiger Fehler: ISO-8601 statt CCYYMMDD.

<!-- Falsch in CII -->
<udt:DateTimeString format="102">2026-03-22</udt:DateTimeString>

<!-- Richtig in CII -->
<udt:DateTimeString format="102">20260322</udt:DateTimeString>

Fehler 5: Summen stimmen nicht überein

EN 16931 enthält arithmetische Regeln (BR-CO-*). Die Summen müssen exakt passen:

  • BT-106 (Summe Nettobetrag Positionen) = Summe aller BT-131
  • BT-109 (Rechnungsbetrag ohne MwSt.) = BT-106 + BT-108 (Zuschlag) − BT-107 (Abzug)
  • BT-112 (Rechnungsbetrag mit MwSt.) = BT-109 + BT-110 (MwSt.-Betrag)
  • BT-115 (Fälliger Betrag) = BT-112 − BT-113 (bereits gezahlt)

Fehler 6: Falsche Unit-Codes

Mengeneinheiten müssen UN/ECE Recommendation 20 entsprechen. Häufige Codes:

CodeBedeutung
HURStunde
DAYTag
C62Stück (Einheit)
KGMKilogramm
MTRMeter
LTRLiter

Tipp: Validieren Sie jede generierte Rechnung vor dem Versand. Die E-Invoice API prüft alle 5 Schichten in einem Aufruf — Schema, EN 16931, BR-DE, Berechnung und Semantik.

6. Komplettes Beispiel: XRechnung von A bis Z

Das folgende Beispiel zeigt den vollständigen Workflow — Rechnung erstellen, validieren und speichern:

import requests
from pathlib import Path

API_BASE = "http://localhost:8000/api/v1"

def create_xrechnung():
    """Erstellt eine XRechnung und validiert sie."""
    invoice = {
        "invoice_number": "RE-2026-042",
        "issue_date": "2026-03-22",
        "due_date": "2026-04-21",
        "currency_code": "EUR",
        "invoice_type_code": "380",
        "buyer_reference": "04011000-12345-67",
        "seller": {
            "name": "DevCorp GmbH",
            "street": "Entwicklerweg 42",
            "city": "München",
            "postal_code": "80331",
            "country_code": "DE",
            "tax_id": "DE987654321",
            "contact": {"email": "billing@devcorp.de"},
            "bank_account": {"iban": "DE89370400440532013000"}
        },
        "buyer": {
            "name": "Stadt München",
            "street": "Marienplatz 8",
            "city": "München",
            "postal_code": "80331",
            "country_code": "DE"
        },
        "lines": [
            {
                "description": "API-Entwicklung",
                "quantity": 80,
                "unit_code": "HUR",
                "unit_price": 120.00,
                "tax_category": "S",
                "tax_percent": 19.0
            },
            {
                "description": "Code Review",
                "quantity": 20,
                "unit_code": "HUR",
                "unit_price": 95.00,
                "tax_category": "S",
                "tax_percent": 19.0
            }
        ],
        "payment_terms": "Zahlbar innerhalb von 30 Tagen netto"
    }

    # 1. XML generieren
    resp = requests.post(
        f"{API_BASE}/invoices/xml",
        json=invoice,
        params={"syntax": "ubl"}
    )
    resp.raise_for_status()
    xml_bytes = resp.content

    # 2. Validieren
    val = requests.post(
        f"{API_BASE}/invoices/validate",
        files={"file": ("invoice.xml", xml_bytes, "application/xml")}
    )
    result = val.json()

    if not result["valid"]:
        for err in result["errors"]:
            print(f"Fehler: [{err['rule']}] {err['message']}")
        return False

    # 3. Speichern
    output = Path("output/RE-2026-042.xml")
    output.parent.mkdir(exist_ok=True)
    output.write_bytes(xml_bytes)
    print(f"Valide XRechnung gespeichert: {output}")
    return True

if __name__ == "__main__":
    create_xrechnung()

7. Nächste Schritte

Sie haben die Grundlagen der XRechnung-Implementierung kennengelernt. Für den produktiven Einsatz empfehlen wir:

  • Branchenerweiterungen prüfen: Bau, Gesundheit, Logistik und Automotive haben zusätzliche Anforderungen.
  • ZUGFeRD parallel einrichten: Viele B2B-Empfänger bevorzugen das Hybridformat.
  • Fristen beachten: Die E-Rechnungspflicht 2027 betrifft alle B2B-Unternehmen.

Das könnte Sie auch interessieren

Bereit für den nächsten Schritt?

Sprechen Sie mit uns über Ihre Anforderungen — unverbindlich und kostenlos.

Gespräch vereinbaren