---
title: "Validating e-invoices: KoSIT validator and veraPDF in CI"
description: "Check e-invoices with the official validators: Quittio’s seven validation levels, setting up the KoSIT validator and veraPDF, automating it in GitHub Actions."
canonical_url: "https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf"
locale: en
meta:
  "og:description": "Check e-invoices with the official validators: Quittio’s seven validation levels, setting up the KoSIT validator and veraPDF, automating it in GitHub Actions."
  "og:image": "https://quittio.dev/og/quittio-en.jpg"
  "og:image:alt": "Quittio: Invoices, settled. E-invoicing for PHP and Laravel, with a validated sample invoice."
  "og:image:height": "630"
  "og:image:type": image/jpeg
  "og:image:width": "1200"
  "og:title": "Validating e-invoices: KoSIT validator and veraPDF in CI"
  "twitter:image": "https://quittio.dev/og/quittio-en.jpg"
  "twitter:image:alt": "Quittio: Invoices, settled. E-invoicing for PHP and Laravel, with a validated sample invoice."
---

> Index of every page on this site: [llms.txt](https://quittio.dev/llms.txt)

# Validating e-invoices: KoSIT validator and veraPDF in CI

An e-invoice with a format error is not an e-invoice. Here is how to check every invoice with the official validators, locally and automatically in your CI.

**How-to**15 Oct 2026 · about 11 minutes

**In short**

Quittio validates every invoice on seven levels. Five of them run in PHP without any installation. The two official validators, the **KoSIT validator** (Schematron rules of EN 16931, XRechnung and Peppol) and **veraPDF** (PDF/A-3b), are installed with `php artisan quittio:schemas:download` or via Docker and switched on in `validation.levels`. In CI they run as a separate test group.

## Why validate?

The German Federal Ministry of Finance letter of 15 October 2025 groups errors in e-invoices into three classes. The key consequence: a **format error** means the invoice no longer counts as an e-invoice but as an “other invoice”. Once the transition periods end, that is no longer allowed for domestic B2B invoices.

| Class | Examples | Quittio levels | Consequence |
| --- | --- | --- | --- |
| Format error (no. 6a) | XML does not match the schema, a mandatory profile field is missing, the PDF is not PDF/A | L1, L2, L4, L6 | not an e-invoice |
| Business rule error (no. 6b) | Totals don’t add up, Leitweg-ID missing | L3, L5 | still an e-invoice, should be corrected |
| Content error (no. 35a) | A mandatory VAT detail is missing or wrong | `DE-USTG-…`, L7 | still an e-invoice, may need correcting |

Quittio maps its findings to these classes when you need it:

InvoiceService.php

```
use Quittio\Country\DE\CountryDE;

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

## The seven validation levels

| Level | Checks | Runs with |
| --- | --- | --- |
| L1 | Structure and data formats: country and currency codes, decimals | PHP |
| L2 | Mandatory and allowed fields of the profile | PHP |
| L3 | Business rules: EN 16931, XRechnung, Peppol, German VAT Act | PHP |
| L4 | XML against the XSD (CII D16B, UBL 2.1) | PHP (`ext-dom`) |
| L5 | Official Schematron rules of EN 16931, XRechnung and Peppol | KoSIT validator (Java) |
| L6 | PDF/A-3b conformance | veraPDF (local or Docker) |
| L7 | Plausibility: IBAN, BIC, VAT ID, Leitweg-ID, optional VIES | PHP |

Without further setup, the PHP levels and the XSD run (`['php', 'xsd']`). That catches most errors. The official rules, however, are XSLT 2.0, which PHP cannot execute. For full certainty you need the KoSIT validator and veraPDF.

## Setting up the KoSIT validator

KoSIT is the German agency that publishes XRechnung. Its validator is the official tool that public portals use too. One command downloads everything: the validator, the XRechnung configuration and the compiled Peppol rules.

Terminal

```
php artisan quittio:schemas:download           # into storage/quittio/validators
php artisan quittio:schemas:download --without-peppol
```

The validator is fastest as a service (daemon) that receives invoices over HTTP. The download command prints the matching start command:

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
```

Alternatively, run it as a Docker container. The Quittio repository contains a Dockerfile for that:

Terminal

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

**Daemon or CLI?**

In `cli` mode, Quittio starts a separate Java process for every invoice. That is simple but slow. For many invoices, use `daemon` mode.

## Setting up veraPDF

veraPDF checks whether the PDF is a valid PDF/A-3b, the archival form that ZUGFeRD requires. The easiest way is Docker: `docker pull verapdf/cli`. Alternatively, install veraPDF locally and use `cli` mode.

## Switching the levels on

Add `kosit` and `verapdf` to the validation levels. From then on every `render()` also checks with the official validators:

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 address
        'verapdf' => ['mode' => 'docker', 'image' => 'verapdf/cli'],                                 // or mode cli with a local installation
    ],
],
```

**When a validator is missing**

If the KoSIT validator is unreachable or veraPDF is not installed, Quittio reports `KOSIT-UNAVAILABLE` or `VERAPDF-UNAVAILABLE` as an error. With `fail_on_unavailable => false` it becomes a warning. In production you usually don’t want that.

## Checking a single file

For an invoice you received or produced, there is a command. It shows number, syntax, profile, the levels checked and a table of all findings:

Terminal

```
php artisan quittio:validate invoice.pdf --profile=en16931 --kosit --verapdf
php artisan quittio:validate invoice.xml --json
```

| Exit code | Meaning |
| --- | --- |
| `0` | No errors (warnings don’t count); with `--profile` the profile matches as well |
| `1` | Errors found or a different profile than expected |
| `2` | The file could not be checked: missing, not an e-invoice or unknown profile |

## Checking in tests

`check()` shows all findings of an invoice without producing a PDF or throwing. That suits fast tests:

tests/Unit/InvoiceTest.php

```
$report = Quittio::make($invoice)->format('xrechnung-cii')->check();

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

For finished files there is the assertion `assertValidEInvoice()` from the `InteractsWithQuittio` trait. It checks with all enabled levels. Because the official validators need Java and Docker, they run in a separate test group:

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');
```

## Automating in GitHub Actions

A dedicated job installs Java, downloads the validators (cached), pulls the veraPDF image and runs only the `official` group:

.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
```

Run the fast test job with `--exclude-group=official`. Your regular tests stay fast and the official checks still run on every push.

## Checking incoming invoices

You can apply the same levels to invoices you receive, for example before importing them into accounting:

IncomingInvoiceController.php

```
$report = Quittio::validateFile($content);   // XML or PDF of an incoming invoice
```

## Reading the messages

Every finding names the rule code, field, message and a hint for the fix. The prefix shows the origin: `BR-` (EN 16931), `BR-DE-` (XRechnung), `PEPPOL-` (Peppol), `DE-USTG-` (German VAT Act), `QUITTIO-` (Quittio), `PDFA-` (veraPDF). One missing value can be reported by several rules; fix it and they all go away.

## Frequently asked questions

### **Is validation without the KoSIT validator enough?**

For development usually yes: the PHP rules and the XSD catch most errors. The official Schematron rules only run in the KoSIT validator, though. Switch it on for CI and before going live.

### **Do I need Java on the production server?**

Only if you want to validate with the KoSIT validator in production. Alternatively run it as a separate service or Docker container that Quittio calls over HTTP.

### **How long does a check take?**

The PHP levels take milliseconds. The KoSIT validator as a daemon is fast; in CLI mode a Java process starts per invoice and takes longer.

### **Does Quittio validate Peppol invoices too?**

Yes. The download command compiles the Peppol BIS rules for the KoSIT validator; they run alongside the XRechnung configuration.

### **What about warnings?**

Warnings don’t stop rendering or fail tests. With block\_on\_warnings you treat them like errors.

**As of: 15 Oct 2026. Not tax or legal advice.**

Sources: [Federal Ministry of Finance letter of 15 Oct 2025 (nos. 6a, 6b, 35a)](https://www.bundesfinanzministerium.de/Content/DE/Downloads/BMF_Schreiben/Steuerarten/Umsatzsteuer/Umsatzsteuer-Anwendungserlass/2025-10-15-einfuehrung-obligatorische-e-rechnung.html) · [KoSIT: validator and XRechnung configuration](https://github.com/itplr-kosit/validator) · [veraPDF](https://verapdf.org)

## Read next

- [**Formats**

  **ZUGFeRD profiles explained: why MINIMUM and BASIC WL are not enough**

  What each profile contains, which count as e-invoices and which one to use.](https://quittio.dev/en/guides/zugferd-profiles-explained)
- [**How-to**

  **Create an e-invoice in Laravel, step by step**

  From composer require to a valid ZUGFeRD invoice: installation, company data, format choice, errors, tests and cancellations.](https://quittio.dev/en/guides/create-e-invoices-with-laravel)
- [**Formats**

  **ZUGFeRD, XRechnung or Peppol: which format do you need?**

  How the formats differ, who needs which and how Quittio chooses automatically.](https://quittio.dev/en/guides/zugferd-xrechnung-or-peppol)

## Every invoice checked before it goes out.

All validation options are in the documentation.

[**Validation in the docs**](https://docs.quittio.dev/validation) [**See features**](https://quittio.dev/en/features#validierung)

```json
{"@context":"https://schema.org","@graph":[{"@id":"https://quittio.dev/en#website","@type":"WebSite","description":"E-invoicing for PHP and Laravel: create, validate, read and archive ZUGFeRD, XRechnung and Peppol invoices.","inLanguage":"en","name":"Quittio","url":"https://quittio.dev/en","publisher":{"@id":"https://quittio.dev/#identity"},"translationOfWork":{"@id":"https://quittio.dev/#website"}},{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#webpage","@type":"WebPage","dateModified":"2026-10-15","datePublished":"2026-10-15","description":"Check e-invoices with the official validators: Quittio’s seven validation levels, setting up the KoSIT validator and veraPDF, automating it in GitHub Actions.","name":"Validating e-invoices: KoSIT validator and veraPDF in CI","url":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf","breadcrumb":{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#breadcrumb"},"isPartOf":{"@id":"https://quittio.dev/en#website"},"potentialAction":[{"@type":"ReadAction","target":["https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf"]}],"primaryImageOfPage":{"@id":"https://quittio.dev/#logo"}},{"@id":"https://quittio.dev/#identity","@type":"Organization","description":"Open-Source-Bibliothek für E-Rechnungen in PHP und Laravel (ZUGFeRD, XRechnung, Peppol) und Quittio Managed.","email":"kontakt@niklasforst.de","legalName":"Niklas Forst – Softwareentwicklung","name":"Quittio","url":"https://quittio.dev","founder":{"@type":"Person","name":"Niklas Forst","url":"https://quittio.dev/ueber"},"logo":{"@id":"https://quittio.dev/#logo"},"sameAs":["https://github.com/Quittio/quittio","https://packagist.org/packages/quittio/quittio"]},{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#article","dateModified":"2026-10-15","datePublished":"2026-10-15","description":"Check e-invoices with the official validators: Quittio’s seven validation levels, setting up the KoSIT validator and veraPDF, automating it in GitHub Actions.","headline":"Validating e-invoices: KoSIT validator and veraPDF in CI","inLanguage":"en","thumbnailUrl":"https://quittio.dev/og/quittio-en.jpg","@type":["Article","TechArticle"],"author":{"@id":"https://quittio.dev/#/schema/person/1"},"image":{"@id":"https://quittio.dev/#/schema/image/1"},"isPartOf":{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#webpage"},"mainEntityOfPage":{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#webpage"},"publisher":{"@id":"https://quittio.dev/#identity"}},{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#breadcrumb","@type":"BreadcrumbList","itemListElement":[{"@type":"ListItem","name":"Home","item":"https://quittio.dev/en","position":1},{"@type":"ListItem","name":"Guides","item":"https://quittio.dev/en/guides","position":2},{"@type":"ListItem","name":"Validation","position":3}]},{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#/schema/question/1","@type":"Question","inLanguage":"en","name":"Is validation without the KoSIT validator enough?","acceptedAnswer":{"@type":"Answer","text":"For development usually yes: the PHP rules and the XSD catch most errors. The official Schematron rules only run in the KoSIT validator, though. Switch it on for CI and before going live."}},{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#/schema/question/2","@type":"Question","inLanguage":"en","name":"Do I need Java on the production server?","acceptedAnswer":{"@type":"Answer","text":"Only if you want to validate with the KoSIT validator in production. Alternatively run it as a separate service or Docker container that Quittio calls over HTTP."}},{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#/schema/question/3","@type":"Question","inLanguage":"en","name":"How long does a check take?","acceptedAnswer":{"@type":"Answer","text":"The PHP levels take milliseconds. The KoSIT validator as a daemon is fast; in CLI mode a Java process starts per invoice and takes longer."}},{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#/schema/question/4","@type":"Question","inLanguage":"en","name":"Does Quittio validate Peppol invoices too?","acceptedAnswer":{"@type":"Answer","text":"Yes. The download command compiles the Peppol BIS rules for the KoSIT validator; they run alongside the XRechnung configuration."}},{"@id":"https://quittio.dev/en/guides/validating-e-invoices-kosit-verapdf#/schema/question/5","@type":"Question","inLanguage":"en","name":"What about warnings?","acceptedAnswer":{"@type":"Answer","text":"Warnings don’t stop rendering or fail tests. With block_on_warnings you treat them like errors."}},{"@id":"https://quittio.dev/#/schema/person/1","@type":"Person","name":"Niklas Forst","url":"https://quittio.dev/ueber"},{"@id":"https://quittio.dev/#logo","@type":"ImageObject","caption":"Quittio","contentUrl":"https://quittio.dev/logo.png","inLanguage":"en","url":"https://quittio.dev/logo.png"},{"@id":"https://quittio.dev/#/schema/image/1","@type":"ImageObject","contentUrl":"https://quittio.dev/og/quittio-en.jpg","inLanguage":"en","url":"https://quittio.dev/og/quittio-en.jpg"}]}
```