Automation MCP Server Features Blog Pricing Contact
Integration ZUGFeRD Format

Create and Validate ZUGFeRD and XRechnung Invoices in C# / .NET: Complete Guide

Both German e-invoice formats from one .NET integration: ZUGFeRD hybrid PDFs for B2B and XRechnung pure XML for the public sector. Creating from an object, validating against EN 16931 and the KoSIT rules, reading incoming invoices with deterministic extraction plus AI parsing, and rendering previews, with plain HttpClient and System.Text.Json records, ASP.NET Core typed-client patterns, and an honest look at the ZUGFeRD-csharp NuGet package.

A large share of German business software is .NET, which makes the e-invoicing mandate a very direct C# problem. B2B customers expect ZUGFeRD, the PDF/A-3 hybrid with embedded CII XML that no German business has been allowed to refuse since January 2025 and that becomes mandatory to issue from 2027. Public sector buyers require XRechnung: pure XML, no visual layer, submitted to portals that run the KoSIT rules on acceptance and reject anything without a Leitweg-ID. Two deliverables, one semantic model, and a decision about how much of the machinery belongs in your solution.

This guide covers both formats from a single integration, because the API accepts the same invoice object for either; what changes between a B2B and a B2G document in the code below is the endpoint and two extra fields. Everything runs on plain HttpClient with System.Text.Json records, targeting .NET 8, and the ASP.NET Core section shows the production shape with a typed client and dependency injection. Runnable counterparts live in the .NET examples repository, and the French flavour of the same hybrid format is covered in the companion guide to Factur-X in C# / .NET.

The .NET ecosystem also brings a real open-source contender to this conversation: ZUGFeRD-csharp has built and read these documents for a decade. The landscape section gives it a fair hearing and marks where the compliance wall stands: official validation, PDF/A conformance, and unstructured input, the parts the ZUGFeRD API and XRechnung API keep server-side.

Two formats, one mandate

ZUGFeRD serves B2B. A PDF/A-3 the recipient opens like any invoice, carrying a factur-x.xml attachment their software parses; version 2.3 is current and technically identical to France's Factur-X 1.0.7. Reception has been mandatory for all German businesses since January 2025, issuance follows from January 2027 (above EUR 800k turnover) and January 2028 (everyone), and profiles EN 16931 and above qualify while MINIMUM and BASIC WL remain booking aids.

XRechnung serves B2G. Germany's national CIUS of EN 16931, maintained by KoSIT, version 3.0 in force since February 2024 and required by federal portals since November 2020. It adds the BR-DE rules, makes seller contact details and electronic addresses mandatory, and requires the Leitweg-ID in BT-10. ZRE, OZG-RE, and the state portals validate on acceptance, so anything short of fully compliant bounces.

GoBD archiving applies to both. Invoices must be stored as received, immutable and machine-evaluable, for the statutory retention period. ZUGFeRD archives as one self-contained file; XRechnung archives as the XML itself, which is why this guide includes a rendering step for human review.

Underneath sits the real engineering: roughly 200 EN 16931 business rules, the German overlays, PDF/A-3 conformance with exact XMP metadata, and Schematron pipelines to prove all of it. The technical overview of ZUGFeRD automation walks each layer.


Routing invoices to the right endpoint

Your customerFormatDeliverableEndpoint
German business (B2B)ZUGFeRD EN 16931Hybrid PDF/A-3/v1/create/zugferd
Federal or state authority (B2G)XRechnung 3.0Pure XML + Leitweg-ID/v1/create/xrechnung
French businessFactur-XHybrid PDF/A-3/v1/create/facturx
Peppol network recipientPeppol BIS 3.0UBL XML/v1/create/ubl

Every row consumes the same invoice object, so multi-format support in C# is a conditional on the endpoint string, not four codebases. This guide works the German rows; Factur-X and Peppol UBL have their own C# guides.


The .NET library landscape

.NET has one library that genuinely matters here:

ZUGFeRD-csharp has been maintained for a decade and its InvoiceDescriptor model writes and reads ZUGFeRD 1.x and 2.x CII documents across the profile ladder, XRechnung profile included. Plenty of German ISVs have shipped on it, which is exactly why its boundaries deserve a precise look.

Its scope, and the gaps around it, are worth stating precisely:

The descriptor is not the document. The library produces and parses the XML. For ZUGFeRD you still need a PDF/A-3 container with the attachment merged in and the XMP metadata declaring the profile, which means pairing it with a PDF library and owning archival conformance (embedded fonts, color spaces) yourself. Files that look fine and fail a recipient's PDF/A check are the classic failure mode.

Official validation does not run in-process. The EN 16931 and KoSIT rule sets compile to XSLT 2.0; .NET's XslCompiledTransform implements XSLT 1.0 and stopped there. Saxon's .NET edition can execute the transforms, but no maintained package wires the official pipelines together and tracks KoSIT's yearly XRechnung releases, so a locally generated document is structurally plausible, not verified.

Rendering, attachments, and unstructured input are out of scope. No XRechnung preview PDFs, no BG-24 attachment handling, and no way to read the plain supplier PDFs that still dominate real AP inboxes.

The rest of this guide runs the full lifecycle over the API, and the closing section weighs that maintenance load against a complete compliance service.


Client setup with HttpClient

One client instance, configured once and reused; the console examples create it directly, and the ASP.NET Core section moves the same configuration into IHttpClientFactory.

Create a free InvoiceXML account to get an API key with 100 free credits, no credit card needed. Read the key from the environment or your secrets store, never from source:

using System.Net.Http.Headers;

var http = new HttpClient { BaseAddress = new Uri("https://api.invoicexml.com") };
http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
    "Bearer", Environment.GetEnvironmentVariable("INVOICEXML_API_KEY"));
http.Timeout = TimeSpan.FromSeconds(120);

Create ZUGFeRD invoices in C#

An anonymous object is all the payload modeling required; PostAsJsonAsync serializes it camelCase as-is. Send net prices, quantities, and rates, and the API computes totals and the per-rate VAT breakdown, generates the CII XML, produces the conformant PDF/A-3, and validates against the full EN 16931 rule set before returning. The sample mixes Germany's 19% and 7% rates:

using System.Net.Http.Json;

var invoice = new
{
    invoice = new
    {
        invoiceNumber = "RE-2026-4407",
        issueDate = "2026-07-28",
        currency = "EUR",
        seller = new
        {
            name = "Elbufer Systeme GmbH",
            vatIdentifier = "DE243871650",
            postalAddress = new
            {
                line1 = "Käthe-Kollwitz-Ufer 21",
                city = "Dresden",
                postCode = "01307",
                country = "DE"
            }
        },
        buyer = new
        {
            name = "Taunus Pharma Handel GmbH",
            postalAddress = new
            {
                line1 = "Wilhelmstraße 24",
                city = "Wiesbaden",
                postCode = "65183",
                country = "DE"
            }
        },
        paymentDetails = new { paymentAccountIdentifier = "DE27100777770209299700" },
        lines = new object[]
        {
            new
            {
                quantity = 12,
                item = new { name = "Laborinformationssystem, Modul QS, Juli 2026" },
                priceDetails = new { netPrice = 240.00 },
                vatInformation = new { rate = 19 }
            },
            new
            {
                quantity = 60,
                item = new { name = "Validierungshandbuch (Druck)" },
                priceDetails = new { netPrice = 16.00 },
                vatInformation = new { rate = 7 }
            }
        }
    }
};

var response = await http.PostAsJsonAsync("/v1/create/zugferd", invoice);
if (!response.IsSuccessStatusCode)
{
    throw new InvalidOperationException(await response.Content.ReadAsStringAsync());
}

await File.WriteAllBytesAsync("rechnung-zugferd.pdf",
    await response.Content.ReadAsByteArrayAsync());

The response is a finished ZUGFeRD 2.3 document: PDF/A-3 container, embedded factur-x.xml, XMP metadata declaring the EN 16931 profile, checked against the official Schematron. Data that cannot yield a compliant invoice is refused with HTTP 400 and the violated rules as structured findings, which is why the error body is worth reading rather than swallowing.

Runnable .NET examples on GitHub →


Validate ZUGFeRD invoices in C#

Validate outgoing documents before dispatch and supplier hybrids on arrival. The endpoint extracts the embedded XML, reads the declared profile from BT-24, and runs that profile's official XSD plus Schematron rules. Records plus ReadFromJsonAsync (which is case-insensitive by default) give you a typed verdict with no mapping code:

record ValidationReport(bool Valid, string? Detail, ReportData? Data,
    List<Finding>? Errors, List<Finding>? Warnings);

record ReportData(bool? SchemaValid, bool? SchematronValid, string? Profile);

record Finding(string? Rule, string? Layer, string? Message, List<string>? Fields);

static async Task<ValidationReport> ValidateZugferdAsync(HttpClient http, string path)
{
    using var form = new MultipartFormDataContent();
    await using var stream = File.OpenRead(path);
    form.Add(new StreamContent(stream), "file", Path.GetFileName(path));

    var response = await http.PostAsync("/v1/validate/zugferd", form);
    return (await response.Content.ReadFromJsonAsync<ValidationReport>())!;
}

var report = await ValidateZugferdAsync(http, "rechnung-zugferd.pdf");

if (report.Valid)
{
    Console.WriteLine($"Gültiges ZUGFeRD, Profil {report.Data?.Profile}");
}
else
{
    foreach (var finding in report.Errors ?? [])
    {
        Console.WriteLine($"[{finding.Rule}] {finding.Message}");
        Console.WriteLine($"  Felder: {string.Join(", ", finding.Fields ?? [])}");
    }
}

A completed validation always answers HTTP 200; the verdict is the Valid property, and non-2xx statuses mean transport problems such as a bad key or an unreadable upload. Findings carry rule ids, plain-language messages, business term codes, and field paths, ready for a UI or a supplier email without post-processing.


Create XRechnung invoices in C#

Same object shape, different endpoint, plus the two additions XRechnung enforces beyond base EN 16931:

buyerReference carries the Leitweg-ID. Mapped to BT-10 and enforced by rule BR-DE-15, assigned by the public sector buyer during onboarding. It is routing infrastructure rather than invoice data: keep it on the buyer record and inject it into every request.

Seller contact and electronic addresses are mandatory. The full seller.contact group (name, phone, email) plus electronic addresses for both parties.

var xrechnung = new
{
    invoice = new
    {
        invoiceNumber = "RE-2026-4408",
        issueDate = "2026-07-28",
        dueDate = "2026-08-27",
        currency = "EUR",
        buyerReference = "14612-00088-27",
        seller = new
        {
            name = "Elbufer Systeme GmbH",
            vatIdentifier = "DE243871650",
            postalAddress = new
            {
                line1 = "Käthe-Kollwitz-Ufer 21",
                city = "Dresden",
                postCode = "01307",
                country = "DE"
            },
            contact = new
            {
                name = "Katrin Seidel",
                phone = "+49 351 8802340",
                email = "[email protected]"
            },
            electronicAddress = new { identifier = "DE243871650", schemeId = "0204" }
        },
        buyer = new
        {
            name = "Landeshauptstadt Dresden, Amt für Schulen",
            postalAddress = new
            {
                line1 = "Fiedlerstraße 30",
                city = "Dresden",
                postCode = "01307",
                country = "DE"
            },
            electronicAddress = new { identifier = "14612-00088-27", schemeId = "0204" }
        },
        paymentDetails = new
        {
            paymentMeansCode = 58,
            paymentAccountIdentifier = "DE27100777770209299700"
        },
        lines = new object[]
        {
            new
            {
                quantity = 140,
                unitCode = "HUR",
                item = new { name = "Betrieb Schulverwaltungssoftware, Juli 2026" },
                priceDetails = new { netPrice = 98.00 },
                vatInformation = new { rate = 19, categoryCode = "S" }
            }
        }
    }
};

var response = await http.PostAsJsonAsync("/v1/create/xrechnung", xrechnung);
if (!response.IsSuccessStatusCode)
{
    throw new InvalidOperationException(await response.Content.ReadAsStringAsync());
}

await File.WriteAllBytesAsync("rechnung-xrechnung.xml",
    await response.Content.ReadAsByteArrayAsync());

The response is XRechnung 3.0 in CII syntax, already validated against the EN 16931 Schematron and the KoSIT rules. If your stack also serves the Peppol network, generate the legally equivalent UBL binding instead: POST /v1/create/ubl with options = new { profile = "xrechnung" } and the same invoice object.


Validate XRechnung invoices in C#

POST /v1/validate/xrechnung auto-detects CII or UBL syntax and runs three layers: the XSD, the EN 16931 core Schematron, and the German BR-DE rules from the KoSIT Schematron, the same stack ZRE and OZG-RE apply. It accepts XML from any producer, ZUGFeRD-csharp output included, and the record types from the ZUGFeRD section deserialize this verdict too:

static async Task<ValidationReport> ValidateXrechnungAsync(HttpClient http, string path)
{
    using var form = new MultipartFormDataContent();
    await using var stream = File.OpenRead(path);
    form.Add(new StreamContent(stream), "file", Path.GetFileName(path));

    var response = await http.PostAsync("/v1/validate/xrechnung", form);
    return (await response.Content.ReadFromJsonAsync<ValidationReport>())!;
}

var report = await ValidateXrechnungAsync(http, "rechnung-xrechnung.xml");

if (!report.Valid)
{
    foreach (var finding in report.Errors ?? [])
    {
        // finding.Layer is "xsd", "en16931", or "cius" (the BR-DE rules)
        Console.WriteLine($"[{finding.Rule}] ({finding.Layer}) {finding.Message}");
    }
}

Surface the Layer value to users: a cius finding means the document is a valid EN 16931 invoice that specifically misses a German requirement, with BR-DE-15 on a missing Leitweg-ID as the textbook case. This endpoint accepts XML only; a ZUGFeRD hybrid declaring the XRechnung reference profile goes to /v1/validate/zugferd, and its embedded XML reaches the same KoSIT rules automatically.


Read incoming invoices: extraction plus AI

Reception has been mandatory since 2025, and the resulting inbox is heterogeneous: ZUGFeRD hybrids, XRechnung XML, and plain PDFs from suppliers who never modernised. One method covers all three, trying deterministic extraction first and falling back to AI only when the API reports error code 4006 (no embedded XML):

using System.Text.Json;

record ParseConfidence(double Overall, double SellerIdentification,
    double BuyerIdentification, double TaxCalculation, double LineItems);

static async Task<HttpResponseMessage> UploadAsync(
    HttpClient http, string endpoint, string path)
{
    using var form = new MultipartFormDataContent();
    await using var stream = File.OpenRead(path);
    form.Add(new StreamContent(stream), "file", Path.GetFileName(path));
    return await http.PostAsync(endpoint, form);
}

static async Task<(string Source, JsonElement Invoice, ParseConfidence? Confidence)>
    ReadIncomingInvoiceAsync(HttpClient http, string path)
{
    var extract = await UploadAsync(http, "/v1/extract/json", path);
    if (extract.IsSuccessStatusCode)
    {
        var invoice = await extract.Content.ReadFromJsonAsync<JsonElement>();
        return ("embedded-xml", invoice, null);
    }

    var error = await extract.Content.ReadFromJsonAsync<JsonElement>();
    if (!error.TryGetProperty("errorCode", out var code) || code.GetInt32() != 4006)
    {
        throw new InvalidOperationException(error.ToString());
    }

    // No embedded XML: an old-school PDF, run the AI parser
    var parse = await UploadAsync(http, "/v1/parse/json", path);
    parse.EnsureSuccessStatusCode();

    var envelope = await parse.Content.ReadFromJsonAsync<JsonElement>();
    var confidence = envelope.GetProperty("confidence").Deserialize<ParseConfidence>(
        new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
    return ("ai", envelope.GetProperty("invoice"), confidence);
}

The two paths differ in kind. Extraction is exact XML parsing: what the supplier declared is what your ERP imports. The AI path reads the document visually (OCR included, scans and photos welcome), so it reports a typed ParseConfidence next to the invoice: an overall score and four area scores (seller, buyer, tax calculation, line items) between 0.0 and 1.0. Gate the automation on it:

var (source, invoice, confidence) =
    await ReadIncomingInvoiceAsync(http, "eingang/lieferant-scan.pdf");

if (source == "ai" && confidence!.Overall < 0.7)
{
    await QueueForReviewAsync(invoice, confidence);
}
else
{
    await ImportIntoErpAsync(invoice);
}

Non-invoices fail the AI path with error code 4008 and multi-invoice files with 4009. The third intake endpoint handles a German B2G habit: supporting documents (delivery notes, timesheets, order copies) travel base64-embedded inside the invoice XML in the BG-24 group, and POST /v1/extract/attachments decodes them into a ZIP, answering 404 when the invoice carries none:

using System.Net;

static async Task<string?> ExtractAttachmentsAsync(
    HttpClient http, string path, string zipPath)
{
    var response = await UploadAsync(http, "/v1/extract/attachments", path);
    if (response.StatusCode == HttpStatusCode.NotFound)
    {
        return null; // invoice carries no embedded attachments
    }
    response.EnsureSuccessStatusCode();

    await File.WriteAllBytesAsync(zipPath,
        await response.Content.ReadAsByteArrayAsync());
    return zipPath;
}

await ExtractAttachmentsAsync(http, "rechnung-xrechnung.xml", "belege.zip");

Prefer the raw embedded CII over parsed JSON? /v1/extract/xml streams the XML out of a hybrid PDF untouched, ready for XDocument or the ZUGFeRD-csharp reader.


Render XRechnung previews

XRechnung gives a human nothing to look at, which turns into a workflow gap the first time accounting wants to check an invoice before portal submission. POST /v1/render/xrechnung/to/pdf lays every business term out on a clean, readable PDF, Leitweg-ID labelled in the buyer reference section, with both syntaxes detected automatically:

static async Task RenderXrechnungAsync(HttpClient http, string xmlPath, string pdfPath)
{
    var response = await UploadAsync(http, "/v1/render/xrechnung/to/pdf", xmlPath);
    response.EnsureSuccessStatusCode();

    await File.WriteAllBytesAsync(pdfPath,
        await response.Content.ReadAsByteArrayAsync());
}

await RenderXrechnungAsync(http, "rechnung-xrechnung.xml", "rechnung-vorschau.pdf");

The preview has no legal standing; the XML remains the invoice, and only the XML is submitted.


ASP.NET Core integration

In an ASP.NET Core service, register a typed client with IHttpClientFactory and collapse the B2B/B2G split into one boolean:

// Program.cs
builder.Services.AddHttpClient<GermanInvoiceClient>(client =>
{
    client.BaseAddress = new Uri(builder.Configuration["InvoiceXml:BaseUrl"]!);
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
        "Bearer", builder.Configuration["InvoiceXml:ApiKey"]);
    client.Timeout = TimeSpan.FromSeconds(120);
});

// GermanInvoiceClient.cs
public class InvoiceComplianceException(string findings) : Exception(findings);

public class GermanInvoiceClient(HttpClient http)
{
    public async Task<(byte[] Content, string Extension)> CreateAsync(
        object invoice, bool publicSector)
    {
        var endpoint = publicSector ? "/v1/create/xrechnung" : "/v1/create/zugferd";
        var response = await http.PostAsJsonAsync(endpoint, new { invoice });

        if (!response.IsSuccessStatusCode)
        {
            throw new InvoiceComplianceException(
                await response.Content.ReadAsStringAsync());
        }

        return (await response.Content.ReadAsByteArrayAsync(),
            publicSector ? "xml" : "pdf");
    }

    public async Task<ValidationReport> ValidateAsync(byte[] content, bool publicSector)
    {
        var endpoint = publicSector ? "/v1/validate/xrechnung" : "/v1/validate/zugferd";
        var filename = publicSector ? "invoice.xml" : "invoice.pdf";

        using var form = new MultipartFormDataContent();
        form.Add(new ByteArrayContent(content), "file", filename);

        var response = await http.PostAsync(endpoint, form);
        return (await response.Content.ReadFromJsonAsync<ValidationReport>())!;
    }
}

For resilience, add AddStandardResilienceHandler (or Polly directly) to the registration and let it retry transport failures and 5xx responses only. HTTP 400 must not be retried: it carries validation findings about your data, identical input reproduces identical findings, and that is exactly why InvoiceComplianceException exists as a distinct type your retry policy ignores. Keep the Leitweg-ID on the buyer record and validate its presence before work is enqueued, not at the portal.

The highest-leverage habit is a compliance test in CI. Two xUnit facts pin the whole mapping from your domain model to both formats:

[Fact]
public async Task Zugferd_payload_stays_compliant()
{
    var client = CreateClient();
    var (content, _) = await client.CreateAsync(SampleB2bPayload(), publicSector: false);
    var report = await client.ValidateAsync(content, publicSector: false);

    Assert.True(report.Valid,
        string.Join("\n", (report.Errors ?? []).Select(e => e.Message)));
}

[Fact]
public async Task Xrechnung_payload_passes_the_kosit_rules()
{
    var client = CreateClient();
    var (content, _) = await client.CreateAsync(SampleB2gPayload(), publicSector: true);
    var report = await client.ValidateAsync(content, publicSector: true);

    Assert.True(report.Valid,
        string.Join("\n", (report.Errors ?? []).Select(e => e.Message)));
}

.NET library vs REST API

The fair split for .NET teams:

The library is not a solution, it is a build project. ZUGFeRD-csharp is free and mature, and adopting it still leaves you owning everything around the descriptor: a PDF/A-3 pipeline with archival conformance, validation you cannot run in-process (structural plausibility is not the 200+ business rules, and the difference surfaces as recipient rejections), XRechnung previews, intake, and the KoSIT release calendar. Every specification update means watching for the announcement, waiting for the package to catch up, bumping the version, retesting, and redeploying every service that touches invoices, and German e-invoicing updates every year.

The API is a complete compliance service. Creation, validation, extraction, AI parsing, and rendering behind one typed client, with the official EN 16931 and KoSIT rule sets live server-side before their effective dates. New ZUGFeRD and XRechnung versions ship out of the box: nothing to monitor, no package to wait for, no redeploy. Your solution carries zero compliance dependencies, the domain expertise sits on our side with professional support behind it, missing features get integrated on request, and the same integration extends to Factur-X and Peppol UBL by swapping an endpoint string.

The deciding question is who owns the moving target. Compliance is not a feature you ship once; the rules change on a government calendar, not yours. With a library, that calendar drives your release schedule forever. With the API, your integration is finished the day it works, and staying compliant is our job.

Data handling is stateless by architecture: documents are processed in memory and purged when the response ships. Nothing is stored, nothing is logged, and no invoice data trains any model. VAT numbers, IBANs, and Leitweg-IDs exist server-side for the duration of one HTTP exchange, nothing more.


Endpoint reference

OperationEndpointInputOutput
Create ZUGFeRDPOST /v1/create/zugferdJSONPDF/A-3 binary
Create XRechnung (CII)POST /v1/create/xrechnungJSONXRechnung 3.0 XML
Create XRechnung (UBL)POST /v1/create/ubl + options.profile: xrechnungJSONXRechnung UBL XML
Validate ZUGFeRDPOST /v1/validate/zugferdZUGFeRD PDFValidation JSON
Validate XRechnungPOST /v1/validate/xrechnungXRechnung XML (CII or UBL)Validation JSON
Extract as JSONPOST /v1/extract/jsonZUGFeRD PDF or XRechnung XMLStructured JSON
Parse PDF with AIPOST /v1/parse/jsonAny invoice PDF (typed, scanned, photo)Structured JSON + confidence
Extract attachmentsPOST /v1/extract/attachmentsZUGFeRD PDF or XRechnung XMLZIP archive
Render XRechnung as PDFPOST /v1/render/xrechnung/to/pdfXRechnung XMLPDF binary
Extract CII XMLPOST /v1/extract/xmlZUGFeRD PDFCII XML

Full OpenAPI 3.1 schema: api.invoicexml.com/v1/openapi  |  Interactive API explorer: api.invoicexml.com/v1/scalar


Get started

Everything in this guide runs on stock .NET 8 with zero NuGet packages: set INVOICEXML_API_KEY and run. The runnable versions live in the examples repository:

InvoiceXML/facturx-api-examples/csharp →

Create a free InvoiceXML account → get 100 credits for free, no credit card required.

Related resources:


InvoiceXML is a REST API for European e-invoice compliance covering ZUGFeRD, XRechnung, Factur-X, Peppol UBL, and CII. Stateless processing, GDPR compliant by architecture, and callable from any .NET application: ASP.NET Core, Azure Functions, WPF or WinForms desktop software, or a console job.

Start free today

Ready to automate your invoices?

Validate, convert and embed compliant e-invoices through one API. Start your 30-day free trial. No credit card required.

GDPR Compliant No credit card required Setup in minutes
Peppol UBL
Factur-X
EN 16931
142 / 142 passed
Compliant
PDF/A-3 embedded