Ausgangslage: Die Rechnung existiert schon, nur im falschen Format
Viele Unternehmen schreiben ihre Rechnungen seit Jahren in Word oder Excel und verschicken sie als PDF. Mit der Pflicht zum Versand elektronischer Rechnungen ändert sich nicht, was auf der Rechnung steht — nur die Form, in der der Käufer sie annimmt. Der naheliegende Weg ist deshalb nicht der Austausch des Rechnungsprozesses, sondern eine Strecke dahinter: PDF lesen, als [[en-16931|EN-16931]]-Daten nachbauen, prüfen, [[zugferd|ZUGFeRD]] oder [[xrechnung|XRechnung]] erzeugen.
Das funktioniert, weil PDFs aus Vorlagen eine Textebene haben und gleichförmig sind. Reguläre Ausdrücke lesen Belegnummer, Datum, Positionen und Summen zuverlässig — ohne Sprachmodell, ohne Kosten je Beleg, ohne externen Dienst. Die eigentliche Arbeit liegt woanders: beim Abbilden dessen, was gelesen wurde, auf die Norm. Dieser Guide beschreibt, woran das in der Praxis scheitert.
Was auf dem PDF nicht steht
Bevor die erste E-Rechnung entsteht, fehlen vier Angaben, die eine klassische Rechnung nie führen musste:
| Angabe | Woher sie kommt | Regel |
|---|---|---|
| Bankverbindung des Rechnungsstellers | Stammdaten je Abteilung | BR-DE-1 verlangt Zahlungsangaben (BG-16) |
| Ansprechpartner mit Telefon und E-Mail | Stammdaten | BR-DE-2, für XRechnung zusätzlich BR-DE-6 |
| Mailadresse des Käufers | Kundentabelle | ohne sie kein Versand |
| Leitweg-ID / Käuferreferenz | Kundentabelle | BT-10, BR-DE-15 — für Deutschland in beiden Formaten |
🔴 Die Kundentabelle ist nicht optional. Weder die Mailadresse noch die [[leitweg-id|Leitweg-ID]] sind auf einem Eingangsbeleg gedruckt — beides wurde nie gebraucht. Ein Käufer ohne Eintrag muss den Beleg anhalten, nicht durchwinken: lieber eine Rückmeldung an die Abteilung als eine unzustellbare E-Rechnung.
Die sieben Abbildungsfehler
Diese Fehler zeigen sich nicht beim Lesen des PDFs und nicht in der Schemaprüfung des eigenen Codes. Sie zeigen sich erst, wenn die Daten gegen die echte API laufen.
1. Der Belegtyp heißt credit_note, nicht creditnote
Ein Aufzählungswert mit einem Buchstaben daneben ist ein Schemafehler, kein
Geschäftsregel-Verstoß. Die gültigen Werte sind invoice, credit_note,
proforma, correction.
2. Die Fälligkeit ist Pflicht
Steht auf dem Beleg kein Fälligkeitsdatum, ist das Belegdatum die richtige
Ersatzangabe — nicht das Weglassen. Ein fehlendes dueDate wird abgewiesen.
3. Das Land der Länderregeln muss genannt werden
Deutsche Zusatzfelder wie die Käuferreferenz leben in einer eigenen Gruppe, und
die braucht das Länderkennzeichen: countrySpecific.countryCode: "DE". Fehlt
es, wird die Gruppe still verworfen — und mit ihr die Leitweg-ID.
4. Es gibt kein paymentMeans
Die Bankverbindung gehört an den Rechnungssteller (seller.bankAccount), nicht in
ein eigenes Zahlungsmittel-Objekt. Wer aus einer anderen Norm-Implementierung
kommt, sucht das Feld an der falschen Stelle.
5. BR-DE-2: Der Ansprechpartner fehlt
Auf dem Briefkopf steht er meistens. Im Datensatz fehlt er, weil er in keinem
Rechnungsformular je ein Feld war. Name plus Telefon oder E-Mail — als
seller.contact.
6. BR-DE-1: Bankverbindung allein reicht nicht
Die Zahlungsangaben brauchen zusätzlich eine Zahlungsart:
paymentMethods: [{ type: "bank_transfer" }]. IBAN und BIC ohne Zahlungsart
sind unvollständig. Und ein Zahlungsbedingungs-Element ohne Text verstößt gegen
PEPPOL-EN16931-R008 — paymentTerms.description darf nicht leer sein.
7. BR-27: Negative Einzelpreise werden abgewiesen
Auf dem PDF steht die Gutschrift mit Minusbeträgen. In EN 16931 trägt der
Belegtyp das Vorzeichen: type: "credit_note", alle Beträge positiv, Menge
und Einzelpreis positiv. Wer die Zahlen vom PDF durchreicht, produziert einen
Beleg, der strukturell sauber aussieht und an BR-27 scheitert.
Nachrechnen, bevor die API es tut
Die Positionen einer Bestandsrechnung ergeben nicht immer den ausgewiesenen Nettobetrag — Rundung, Anlagen, handverbesserte Summen. Eine Strecke, die den Beleg nicht nachrechnet, schickt den Fehler weiter und bekommt ihn als BR-CO-10 zurück. Drei Prüfungen mit einer Toleranz von zwei Cent reichen: Positionen gegen Netto, Netto plus Steuer gegen Brutto, Steuerbetrag gegen Steuersatz.
Ein vierter Vergleich kostet nichts und fängt einen anderen Fehler: Trägt der Beleg eine andere USt-IdNr. als die Abteilung, die ihn geschickt hat, ging die Mail an das falsche Postfach.
Validieren heißt zwei Dinge
Die [[validierung|Validierung]] liefert zwei Antworten, und sie sind nicht dasselbe:
validbeantwortet die Objektregeln — Pflichtfelder, Summen, Formate der Kennungen.results[]enthält je Zielformat das erzeugte XML, geprüft gegen XSD und [[schematron|Schematron]] — also die Regeln, an denen ein Empfänger die Rechnung tatsächlich abweist.
Eine Freigabe sollte beides verlangen. Wer nur valid prüft, sieht
Formatverstöße erst beim Erzeugen — oder gar nicht.
Zwei Belege in einer Datei
Aus der Praxis: eine PDF-Datei, zwei Rechnungen, eine je Seite. Eine Strecke,
die „ein PDF = eine Rechnung" annimmt, verarbeitet die erste und verliert die
zweite ohne Fehlermeldung. Deshalb seitenweise lesen und jede Seite einzeln durch
den Parser schicken; Seiten ohne Beleg — Anlagen, AGB — liefern nichts und
stören nicht. Ein Parser, der ein Feld nicht findet, gibt null zurück und rät
nicht: Was fehlt, fällt in der nachgelagerten Prüfung auf und wird beim Namen
genannt.
Was nicht durchgeht, geht zurück
Fällt ein Beleg durch, darf nichts beim Käufer ankommen. Stattdessen eine Zeile im Log, das Quell-PDF unverändert im Archiv und eine Mail zurück an die Abteilung, die den Beleg geschickt hat — mit Dateiname, Belegnummer und jedem Grund im Klartext. Korrigieren, erneut schicken. Der Käufer merkt davon nichts.
Ein Detail dazu: der Hash, den die API mit dem erzeugten Dokument zurückgibt, ist der Hash der Ausgabe-Bytes. Ein PDF trägt einen Erzeugungszeitpunkt, also ändert sich dieser Wert bei jedem Aufruf — als Dubletten-Kennung im Dateinamen taugt er nicht. Ein eigener Fingerabdruck über die Rechnungsdaten mit stabiler Schlüsselreihenfolge schon: derselbe Beleg zweimal empfangen ergibt denselben Dateinamen, derselbe Beleg mit geändertem Inhalt einen anderen.
Fertige Strecke
Der beschriebene Aufbau — ein Postfach je Abteilung, regelbasiertes Lesen,
Stammdaten plus Kundentabelle, drei Prüfstufen, Fehlerpfad zurück an den
Absender — steht als n8n-Workflow-Template bereit:
PDF-Rechnungen aus dem Postfach → ZUGFeRD / XRechnung.
Das Ergebnis ist ein [[pdf-a-3|PDF/A-3]] mit eingebettetem factur-x.xml
(Profil EN 16931) oder eine XRechnung — je nachdem, was der Käufer annimmt.