Zum Inhalt springen

E-Rechnungen validieren: KoSIT-Validator und veraPDF in der CI

Eine E-Rechnung mit Formatfehler ist keine E-Rechnung. So prüfst du jede Rechnung mit den offiziellen Validatoren, lokal und automatisch in deiner CI.

Anleitung15.10.2026 · ca. 11 Minuten

Kurz gesagt

Quittio prüft jede Rechnung in sieben Stufen. Fünf davon laufen ohne Installation in PHP. Die beiden offiziellen Validatoren, der KoSIT-Validator (Schematron-Regeln von EN 16931, XRechnung und Peppol) und veraPDF (PDF/A-3b), lädst du mit php artisan quittio:schemas:download bzw. per Docker und schaltest sie in validation.levels ein. In der CI laufen sie als eigene Testgruppe.

Warum validieren?

Das BMF-Schreiben vom 15.10.2025 teilt Fehler in E-Rechnungen in drei Klassen. Die wichtigste Folge: Ein Formatfehler führt dazu, dass die Rechnung nicht mehr als E-Rechnung gilt, sondern als sonstige Rechnung. Ab dem Ende der Übergangsfristen ist das für inländische B2B-Rechnungen nicht mehr zulässig.

KlasseBeispielePrüfstufen in QuittioFolge
Formatfehler (Rz. 6a)XML entspricht nicht dem Schema, Pflichtfeld des Profils fehlt, PDF ist kein PDF/AL1, L2, L4, L6keine E-Rechnung
Geschäftsregelfehler (Rz. 6b)Summen passen nicht zusammen, Leitweg-ID fehltL3, L5E-Rechnung bleibt bestehen, sollte korrigiert werden
Inhaltsfehler (Rz. 35a)umsatzsteuerliche Pflichtangabe fehlt oder ist falschDE-USTG-…, L7E-Rechnung bleibt bestehen, ggf. Berichtigung nötig

Quittio ordnet seine Befunde diesen Klassen zu, wenn du das brauchst:

InvoiceService.php
use Quittio\Country\DE\CountryDE;

$classes = (new CountryDE)->errorClassification($result);
// ['format' => [...], 'business_rule' => [...], 'content' => [...]]

Die sieben Prüfstufen

StufePrüftLäuft mit
L1Struktur und Datenformate: Länder- und Währungscodes, NachkommastellenPHP
L2Pflicht- und erlaubte Felder des ProfilsPHP
L3Geschäftsregeln: EN 16931, XRechnung, Peppol, UStGPHP
L4XML gegen das XSD (CII D16B, UBL 2.1)PHP (ext-dom)
L5Offizielle Schematron-Regeln von EN 16931, XRechnung und PeppolKoSIT-Validator (Java)
L6PDF/A-3b-KonformitätveraPDF (lokal oder Docker)
L7Plausibilität: IBAN, BIC, USt-IdNr., Leitweg-ID, optional VIESPHP

Ohne weitere Einrichtung laufen die Stufen in PHP und das XSD (['php', 'xsd']). Das fängt die meisten Fehler ab. Die offiziellen Regeln sind aber XSLT 2.0, das PHP nicht ausführen kann. Für die letzte Sicherheit brauchst du deshalb den KoSIT-Validator und veraPDF.

Den KoSIT-Validator einrichten

Die KoSIT ist die Stelle, die XRechnung herausgibt. Ihr Validator ist das offizielle Werkzeug, mit dem auch Behördenportale prüfen. Ein Befehl lädt alles herunter: den Validator, die XRechnung-Konfiguration und die übersetzten Peppol-Regeln.

Terminal
php artisan quittio:schemas:download           # nach storage/quittio/validators
php artisan quittio:schemas:download --without-peppol

Am schnellsten läuft der Validator als Dienst (Daemon), der Rechnungen per HTTP entgegennimmt. Der Download-Befehl gibt dir den passenden Startbefehl aus:

Terminal
cd storage/quittio/validators
java -jar validator.jar \
    -s xrechnung/scenarios.xml -r xrechnung \
    -s ../../../vendor/quittio/core/resources/kosit/peppol-scenarios.xml -r . \
    -D -P 8081

Alternativ startest du ihn als Docker-Container. Das Quittio-Repository enthält dafür ein Dockerfile:

Terminal
docker build -t quittio/kosit -f tools/docker/kosit/Dockerfile .
docker run --rm -p 8081:8080 quittio/kosit

Daemon oder CLI?

Im Modus cli startet Quittio für jede Rechnung eine eigene Java-Instanz. Das ist einfach, aber langsam. Für viele Rechnungen nimm den Modus daemon.

veraPDF einrichten

veraPDF prüft, ob das PDF ein gültiges PDF/A-3b ist, also die Archivform, die ZUGFeRD verlangt. Am einfachsten per Docker: docker pull verapdf/cli. Alternativ installierst du veraPDF lokal und setzt den Modus cli.

Stufen einschalten

Ergänze kosit und verapdf in den Prüfstufen. Ab dann prüft jedes render() auch mit den offiziellen Validatoren:

config/quittio.php
// config/quittio.php
'validation' => [
    'levels' => ['php', 'xsd', 'kosit', 'verapdf'],
    'drivers' => [
        'kosit' => ['mode' => 'daemon', 'url' => env('QUITTIO_KOSIT_URL', 'http://127.0.0.1:8081')],   // Daemon-Adresse
        'verapdf' => ['mode' => 'docker', 'image' => 'verapdf/cli'],                                 // oder mode cli mit lokaler Installation
    ],
],

Wenn ein Validator fehlt

Ist der KoSIT-Validator nicht erreichbar oder veraPDF nicht installiert, meldet Quittio KOSIT-UNAVAILABLE bzw. VERAPDF-UNAVAILABLE als Fehler. Mit fail_on_unavailable => false wird daraus eine Warnung. In Produktion willst du das meist nicht.

Eine einzelne Datei prüfen

Für eine Rechnung, die du bekommen oder erzeugt hast, gibt es einen Befehl. Er zeigt Nummer, Syntax, Profil, geprüfte Stufen und eine Tabelle aller Befunde:

Terminal
php artisan quittio:validate invoice.pdf --profile=en16931 --kosit --verapdf
php artisan quittio:validate invoice.xml --json
Exit-CodeBedeutung
0Keine Fehler (Warnungen zählen nicht); mit --profile stimmt auch das Profil
1Fehler gefunden oder ein anderes Profil als erwartet
2Datei konnte nicht geprüft werden: fehlt, ist keine E-Rechnung oder unbekanntes Profil

In Tests prüfen

Mit check() siehst du alle Befunde einer Rechnung, ohne dass ein PDF entsteht oder etwas geworfen wird. Das eignet sich für schnelle Tests:

tests/Unit/InvoiceTest.php
$report = Quittio::make($invoice)->format('xrechnung-cii')->check();

$report->isValid();
$report->errors();

Für fertige Dateien gibt es die Assertion assertValidEInvoice() aus dem Trait InteractsWithQuittio. Sie prüft mit allen eingeschalteten Stufen. Weil die offiziellen Validatoren Java und Docker brauchen, laufen sie in einer eigenen Testgruppe:

tests/Feature/OfficialValidationTest.php
use Quittio\Core\Data\InvoiceData;
use Quittio\Core\Validation\Drivers\KositArtifacts;
use Quittio\Laravel\Facades\Quittio;

function useOfficialValidators(): void
{
    $root = storage_path('quittio/validators');            // php artisan quittio:schemas:download

    config()->set('quittio.validation.levels', ['php', 'xsd', 'kosit', 'verapdf']);
    config()->set('quittio.validation.drivers.kosit', [
        'mode' => 'cli',
        'jar' => $root.'/validator.jar',
        'scenarios' => $root.'/xrechnung/scenarios.xml',
        'repository' => $root.'/xrechnung',
        'configurations' => [['scenarios' => KositArtifacts::peppolScenarios(), 'repository' => $root]],
        'timeout' => 120,
    ]);
    config()->set('quittio.validation.drivers.verapdf', ['mode' => 'docker', 'image' => 'verapdf/cli']);
    Quittio::flush();
}

it('produces valid ZUGFeRD and XRechnung', function () {
    useOfficialValidators();
    $invoice = InvoiceData::factory()->state(fn ($b) => $b->buyerReference('04011000-12345-03'))->make();
    $result = Quittio::make($invoice)->formats(['zugferd', 'xrechnung-cii'])->render();

    $this->assertValidEInvoice($result->pdf(), 'en16931');
    $this->assertValidEInvoice($result->as('xrechnung-cii')->xml(), 'xrechnung');
})->group('official');

In GitHub Actions automatisieren

Ein eigener Job installiert Java, lädt die Validatoren (gecacht), holt das veraPDF-Image und führt nur die Gruppe official aus:

.github/workflows/tests.yml
official-validation:
  runs-on: ubuntu-latest
  steps:
    - uses: actions/checkout@v7
    - uses: shivammathur/setup-php@v2
      with:
        php: '8.4'
        extensions: dom, libxml, mbstring, xmlwriter, zlib, intl, gd, bcmath, zip
    - uses: actions/setup-java@v6
      with:
        distribution: temurin
        java-version: '21'
    - uses: actions/cache@v6
      with:
        path: storage/quittio/validators
        key: quittio-validators-${{ hashFiles('composer.lock') }}
    - run: composer install --no-interaction --no-progress
    - run: php artisan quittio:schemas:download     # keeps existing files
    - run: docker pull verapdf/cli
    - run: vendor/bin/pest --group=official

Den schnellen Testjob startest du mit --exclude-group=official. So bleiben die normalen Tests schnell, und die offiziellen Prüfungen laufen trotzdem bei jedem Push.

Eingehende Rechnungen prüfen

Dieselben Stufen kannst du auf Rechnungen anwenden, die du bekommst, etwa bevor du sie in die Buchhaltung übernimmst:

IncomingInvoiceController.php
$report = Quittio::validateFile($content);   // XML oder PDF einer Eingangsrechnung

Meldungen lesen

Jeder Befund nennt Regelcode, Feld, Meldung und einen Hinweis zur Lösung. Der Präfix zeigt die Herkunft: BR- (EN 16931), BR-DE- (XRechnung), PEPPOL- (Peppol), DE-USTG- (Umsatzsteuergesetz), QUITTIO- (Quittio), PDFA- (veraPDF). Ein fehlender Wert kann von mehreren Regeln gemeldet werden; behebst du ihn, verschwinden alle.

Häufige Fragen

Reicht die Prüfung ohne KoSIT-Validator?

Für die Entwicklung meist ja: Die PHP-Regeln und das XSD fangen die meisten Fehler ab. Die offiziellen Schematron-Regeln laufen aber nur im KoSIT-Validator. Für die CI und vor dem Livegang solltest du ihn einschalten.

Brauche ich Java auf dem Produktionsserver?

Nur, wenn du in Produktion mit dem KoSIT-Validator prüfen willst. Alternativ läuft er als eigener Dienst oder Docker-Container, den Quittio per HTTP anspricht.

Wie lange dauert eine Prüfung?

Die PHP-Stufen brauchen Millisekunden. Der KoSIT-Validator als Daemon ist schnell; im CLI-Modus startet pro Rechnung eine Java-Instanz und dauert entsprechend länger.

Prüft Quittio auch Peppol-Rechnungen?

Ja. Der Download-Befehl übersetzt die Peppol-BIS-Regeln für den KoSIT-Validator; sie laufen zusammen mit der XRechnung-Konfiguration.

Was ist mit Warnungen?

Warnungen stoppen das Rendern nicht und lassen Tests nicht fehlschlagen. Mit block_on_warnings behandelst du sie wie Fehler.

Stand: 15.10.2026. Keine Steuer- oder Rechtsberatung.

Quellen: BMF-Schreiben vom 15.10.2025 (Rz. 6a, 6b, 35a) · KoSIT: Validator und XRechnung-Konfiguration · veraPDF

Jede Rechnung geprüft, bevor sie rausgeht.

Alle Optionen der Validierung stehen in der Dokumentation.