ZUGFeRD, Factur-X and XRechnung CII
ZUGFeRD and Factur-X, and XRechnung when written as CII, use the UN/CEFACT Cross Industry
Invoice syntax (rsm:CrossIndustryInvoice). Popular libraries produce it:
horstoeko/zugferd (PHP, CII only),
Mustang (Java) and
factur-x (Python, PDF embedding).
Peppol carries UBL. POST /api/v1/documents refuses a CII document with
400 โฆ [INVALID_ROOT] โฆ got 'CrossIndustryInvoice' โ GoRoute does not convert it for you.
So the question is how your invoice becomes UBL. There are three good answers.
| Route | Best when | You keep |
|---|---|---|
| A. Produce UBL directly | You can change which writer your code calls | Your own XML, end to end |
| B. Convert CII to UBL | Your CII pipeline is fixed and you only need a delivery step | Your CII for archive and email; UBL for Peppol |
| C. Let GoRoute build it | You have the invoice data, not just the XML | One call; GoRoute renders UBL, XRechnung or CII as the buyer needs |
A. Produce UBL directlyโ
Many libraries write both syntaxes. Check whether yours can export UBL โ for EN 16931 and XRechnung the UBL and CII forms carry the same business content. For example, horstoeko/zugferd's README names its successor horstoeko/invoicesuite, which covers ZUGFeRD, Factur-X and XRechnung in both CII and UBL. In PHP, josemmo/einvoicing writes UBL with a Peppol preset.
Then follow Send XML you already generate.
B. Convert CII to UBLโ
horstoeko/zugferdublbridge converts ZUGFeRD/Factur-X CII to Peppol UBL (and back). Its README marks it experimental, so validate every converted document before you rely on the pipeline.
composer require horstoeko/zugferdublbridge guzzlehttp/guzzle
use horstoeko\zugferdublbridge\XmlConverterCiiToUbl;
use GuzzleHttp\Client;
// $ciiXml: the CII you already produce, e.g. with horstoeko/zugferd
$ublXml = XmlConverterCiiToUbl::fromString($ciiXml)->convert()->saveXmlString();
$goroute = new Client(['base_uri' => 'https://app.goroute.ai/peppol-api/', 'timeout' => 30]);
$headers = ['X-API-Key' => getenv('GOROUTE_API_KEY')];
// 1. Check the converted document before it leaves
$check = json_decode((string) $goroute->post('api/v1/documents/validate', [
'headers' => $headers,
'json' => ['document' => $ublXml],
])->getBody(), true);
if (!$check['valid']) {
throw new RuntimeException(json_encode($check['issues']));
}
// 2. Send it. The receiver comes from you, not from the XML.
$response = $goroute->post('api/v1/documents', [
'headers' => $headers + ['Idempotency-Key' => $invoiceNumber],
'json' => [
'receiver_scheme' => $buyerScheme, // e.g. "0204" for a Leitweg-ID
'receiver_id' => $buyerId,
'document' => $ublXml,
],
]);
Before you go live, look at the converted document's cbc:CustomizationID. GoRoute sends
under the Peppol BIS 3.0 Invoice document type unless you pass document_type; an XRechnung
or credit-note document needs its own value โ see
Credit notes and other document types
and the Germany guide.
Starting from a Factur-X or ZUGFeRD PDFโ
A hybrid PDF carries its CII as an embedded file. GoRoute does not accept the PDF itself.
Extract the XML first โ the factur-x Python package
(pip install factur-x) ships a facturx-pdfextractxml command-line tool for this โ and then
convert the extracted CII as above. The extracted XML is still CII: sending it to
POST /api/v1/documents without converting it is refused.
Java (Mustang) and other CII writersโ
The same rule applies to any library that writes CII: either export UBL (route A), convert the CII with a converter you trust and validate the result (route B), or hand GoRoute the data (route C).
C. Let GoRoute build itโ
If your system holds the invoice data โ seller, buyer, lines, VAT โ you do not need to
produce XML at all. POST /api/v1/invoices takes the invoice as JSON, renders the right
syntax for the buyer and delivers it:
{
"invoice": { "โฆ": "your canonical invoice" },
"document_format": "auto",
"delivery_channel": "auto"
}
| You need | Set |
|---|---|
| Peppol BIS 3.0 or PINT UBL for a buyer on the network | document_format: "auto" (default) or "ubl" |
| XRechnung 3.0 (UBL) for a German public buyer | document_format: "xrechnung" |
| A ZUGFeRD / Factur-X PDF to email to a buyer who is not on Peppol | document_format: "cii" (or "zugferd") with delivery_channel: "email" |
CII is an email format here: asking for cii with delivery_channel: "peppol" is refused with
CII_IS_EMAIL_ONLY. To check an invoice against the full official Schematron before you send
it, call POST /api/v1/invoices/validate/deep with the same invoice data. See Send an invoice
and Delivery and formats.
Relatedโ
- Send XML you already generate
- Germany โ XRechnung, Leitweg-ID and ZUGFeRD by email
- France โ Factur-X