Automation MCP Server Features Blog Pricing Contact
Integration ZUGFeRD Format

ZUGFeRD-csharp Alternatives: Library vs REST API for .NET Teams

ZUGFeRD-csharp has carried German e-invoicing in .NET for a decade, and this comparison starts by giving it that credit. What follows is the part the quick-start never shows: a capability matrix against a managed REST API, the validation wall that .NET's missing XSLT 2.0 support builds into every library-based stack, what the project's shift to maintenance-only status means for teams tracking a moving specification, and a concrete migration path from the InvoiceDescriptor model to one HTTP call.

Nobody searches for a ZUGFeRD-csharp alternative because ZUGFeRD-csharp is bad software. It has been the default answer to "ZUGFeRD in C#" for a decade, it is free under Apache-2.0, and its InvoiceDescriptor model is how a large share of German .NET invoicing products build their CII XML today. The search usually starts somewhere else: an invoice a recipient's validator rejected that the library happily produced, a PDF/A-3 container that fails a conformance check, a FeRD or KoSIT release your product must support before the package does, or the project's own signpost that new feature development now happens in a commercial successor.

This guide is the full comparison: what the library genuinely does well, a capability matrix against the InvoiceXML ZUGFeRD API, the validation wall that no .NET library can cross in-process, and a concrete migration path including a field-by-field mapping from InvoiceDescriptor to the API request model. One disclosure up front: this comparison is written by the maker of one of the alternatives, which is exactly why every ZUGFeRD-csharp fact below was checked against the official repository as of September 2026.

If you want the language-specific integration walkthroughs instead, the guides to ZUGFeRD and XRechnung in C# and Factur-X in C# cover the full lifecycle with runnable code. This page is about the decision itself.

What ZUGFeRD-csharp does well

Credit where it is due, because the comparison is meaningless without it:

A decade of real-world use. Stephan Stapel's library has tracked German e-invoicing since the ZUGFeRD 1.x era, and its longevity shows in the details: the profile ladder is modelled, the enums cover the code lists developers actually hit, and the issue tracker reads like a history of German e-invoicing edge cases, solved.

A pleasant, idiomatic API. InvoiceDescriptor.CreateInvoice(...), a chain of setters, AddTradeLineItem(...), Save(...) with a version and profile enum. It feels like .NET, it works offline, and for generating structurally sound CII XML from clean data it does exactly what it promises.

Reading as well as writing. The descriptor loads existing ZUGFeRD XML back into the same model, which makes the library useful on the receiving side for hybrids whose XML you have already extracted.

Free and unencumbered. Apache-2.0, on NuGet, no license negotiation between you and a prototype. That matters, and an honest comparison says so.

So the question this page answers is not whether ZUGFeRD-csharp is good. It is whether the piece it covers is the piece your compliance problem actually consists of, and that question is best answered with the full picture on the table.


Capability matrix

Library capabilities as documented in the official repository as of September 2026; API capabilities link to their documentation.

CapabilityZUGFeRD-csharpInvoiceXML REST API
ZUGFeRD / Factur-X XML generationYes, via the InvoiceDescriptor modelYes, JSON in, validated document out (/v1/create/zugferd, /v1/create/facturx)
Reading ZUGFeRD XMLYes, descriptor loadYes, /v1/extract/json and /v1/extract/xml
PDF/A-3 container conformanceYour responsibility: pairing with PDF tooling and owning fonts, ICC profiles, and XMP metadataHandled server-side; output is validated before it is returned
EN 16931 Schematron validationNo in-process route: the official rules are XSLT 2.0, which .NET does not execute nativelyOfficial rule sets server-side, JSON findings with rule ids and field paths (/v1/validate/zugferd)
KoSIT BR-DE (XRechnung) validationNo, same XSLT 2.0 wallYes, /v1/validate/xrechnung, both syntaxes auto-detected
Reading plain supplier PDFs (no embedded XML)No, structured input requiredYes, AI parsing with confidence scores (/v1/parse/json)
XRechnungCII profile generation; UBL support has been growing release by releaseNative UBL and CII (/v1/create/xrechnung, syntax selected via options.syntax), validated against the KoSIT rules
Peppol BISNot its scopePeppol BIS 3.0 UBL via /v1/create/ubl, plus EHF, NLCIUS and PINT
Rendering previews of XML invoicesNo/v1/render/cii/to/pdf and /v1/render/xrechnung/to/pdf
Specification updatesWait for a package release, bump, retest, redeploy; new feature development now targets the commercial successorLive server-side with no change on your side
Infrastructure requiredThe package plus whatever PDF tooling completes the containerAny HTTP client; zero compliance dependencies in your build
SupportCommunity, via GitHub issues; commercial support via the successor productProfessional support; missing features integrated on request

Two rows decide most evaluations, and neither is about generation. Validation and specification updates are about risk and time: who catches the invoice that would be rejected, and who does the recurring work when the rules move. The next two sections take them in turn.


The validation wall in .NET

Here is the structural fact underneath every .NET e-invoicing stack: the official EN 16931 and KoSIT rule sets are published as Schematron that compiles to XSLT 2.0, and .NET has no native XSLT 2.0 processor. XslCompiledTransform stops at 1.0. The community workaround is Saxon-HE cross-compiled through IKVM, which drags a four-way version compatibility matrix (Saxon, IKVM, the wrapper package, your runtime) and Java type interop into a C# codebase. Most teams decline, and rightly so.

The consequence is that a ZUGFeRD-csharp pipeline generates documents it cannot fully check. XSD validation catches malformed XML; it does not catch the two hundred plus business rules where real rejections live: totals that do not reconcile per BR-CO-15, a VAT category whose required exemption reason is missing, a BR-DE contact rule XRechnung insists on. Those surface when a recipient's system, which does run the official rules, says no. The library is not at fault; it never claimed to validate. But the wall means the claim can never be added in-process, on any .NET library, until .NET grows an XSLT 2.0 engine.

The API's answer is to run the machinery where it can run: POST /v1/validate/zugferd executes the official XSD and Schematron stacks plus PDF/A checks server-side and returns findings as structured JSON with rule ids, plain-language messages, and field paths. And on the create side the same rules run before any document leaves: /v1/create/zugferd refuses to return a file that would fail them, converting a recipient-side rejection into an immediate HTTP 400 with the reasons enumerated.


Maintenance mode and the treadmill

European e-invoicing specifications move on a schedule your roadmap does not control: FeRD and FNFE-MPE revise ZUGFeRD and Factur-X, KoSIT revises XRechnung annually, CEN maintains the EN 16931 artifacts underneath. For a library-based stack, every publication starts the same loop: watch the announcement, wait for the package release that supports it, bump, retest the pipeline, and redeploy every service that touches invoices.

For ZUGFeRD-csharp that loop now has an extra clause. As of September 2026 the project describes itself as maintenance-only: the Apache-2.0 repository stays up and keeps receiving fixes, while new feature development happens in FactoorSharp, a commercial successor by the same author. That is a legitimate and transparent way to sustain a decade-old open-source project, and teams evaluating today should price it in: the free package is no longer where new specification support lands first, so "wait for the release" becomes either "buy the successor" or "leave the treadmill".

The API removes the loop rather than relocating it. Current FeRD, KoSIT, and CEN artifacts are applied server-side before their effective dates; your integration does not change, and nothing on your side needs monitoring, bumping, or redeploying. The team whose full-time job is e-invoicing compliance absorbs the treadmill so yours does not.


Migrating from ZUGFeRD-csharp

The switch is smaller than most teams expect, because the API's request model covers the same semantic ground as the descriptor, and the API computes totals and the VAT breakdown from line items, so the migration is a mapping exercise, not a redesign. Three subsections: the concept mapping, the same invoice written both ways, and the migration sequence.


Mapping the API surface

The table maps the descriptor calls a typical generation pipeline uses onto the JSON request fields of POST /v1/create/zugferd (the identical model serves /v1/create/facturx and /v1/create/xrechnung):

ZUGFeRD-csharp conceptInvoiceXML request field or endpoint
InvoiceDescriptor.CreateInvoice("RE-2026-001", date, CurrencyCodes.EUR)invoice.invoiceNumber, invoice.issueDate, invoice.currency
SetSeller(...) with name, street, postcode, city, countryinvoice.seller.name and invoice.seller.postalAddress (line1, postCode, city, country)
AddSellerTaxRegistration("DE...", TaxRegistrationSchemeID.VA)invoice.seller.vatIdentifier
SetBuyer(...)invoice.buyer (same shape as the seller)
SetBuyerReference(...) (the Leitweg-ID for XRechnung)invoice.buyerReference
SetPaymentMeans(...) and creditor account detailsinvoice.paymentDetails.paymentAccountIdentifier
SetTradePaymentTerms(...), due dateinvoice.dueDate, invoice.paymentDetails
AddTradeLineItem(name, quantity, unit, price, tax rate)lines[]: item.name, quantity, unitCode, priceDetails.netPrice, vatInformation.rate
Save(stream, ZUGFeRDVersion..., Profile...)The endpoint choice and options: /v1/create/zugferd, /v1/create/facturx, or /v1/create/xrechnung
Pairing the XML with PDF tooling into a PDF/A-3Not needed: the create endpoints return a finished, conformant PDF/A-3
Descriptor load of existing XMLPOST /v1/extract/json (or /v1/extract/xml for the raw CII)
No equivalentPOST /v1/validate/zugferd, /v1/parse/json, /v1/render/cii/to/pdf

Everything that has no row on the right side is work that disappears rather than moves: the PDF/A-3 conformance project, the validation gap, and the version-tracking habit.


The same invoice, side by side

First the library version, in the descriptor style its documentation teaches. Note what the last line leaves open: the output is CII XML, and turning it into a conformant ZUGFeRD PDF/A-3 hybrid still needs a PDF/A pipeline you provide:

using s2industries.ZUGFeRD;

var desc = InvoiceDescriptor.CreateInvoice(
    "RE-2026-001", new DateTime(2026, 9, 29), CurrencyCodes.EUR);

desc.SetSeller("Mustermann Software GmbH",
    "10115", "Berlin", "Hauptstrasse 12", CountryCodes.DE, "");
desc.AddSellerTaxRegistration("DE123456789", TaxRegistrationSchemeID.VA);

desc.SetBuyer("Beispiel Handel AG",
    "80331", "Muenchen", "Marienplatz 8", CountryCodes.DE, "");

desc.AddTradeLineItem(
    name: "Softwareentwicklung",
    netUnitPrice: 250.00m,
    billedQuantity: 10m,
    unitCode: QuantityCodes.HUR,
    categoryCode: TaxCategoryCodes.S,
    taxPercent: 19m,
    taxType: TaxTypes.VAT);

desc.Save("invoice.xml", ZUGFeRDVersion.Version23, Profile.Comfort);
// Still ahead: PDF/A-3 container, XMP metadata, embedding,
// and validation against rules the library cannot run.

The same invoice as one HTTP call. The API generates the PDF layout, computes totals and the VAT breakdown, produces the conformant PDF/A-3, and validates against the official rule set before returning:

using System.Net.Http.Json;

var payload = new
{
    invoice = new
    {
        invoiceNumber = "RE-2026-001",
        issueDate = "2026-09-29",
        currency = "EUR",
        seller = new
        {
            name = "Mustermann Software GmbH",
            vatIdentifier = "DE123456789",
            postalAddress = new
            {
                line1 = "Hauptstrasse 12", city = "Berlin",
                postCode = "10115", country = "DE",
            },
        },
        buyer = new
        {
            name = "Beispiel Handel AG",
            postalAddress = new
            {
                line1 = "Marienplatz 8", city = "Muenchen",
                postCode = "80331", country = "DE",
            },
        },
        lines = new[]
        {
            new
            {
                quantity = 10,
                unitCode = "HUR",
                item = new { name = "Softwareentwicklung" },
                priceDetails = new { netPrice = 250.00 },
                vatInformation = new { rate = 19 },
            },
        },
    },
};

var response = await http.PostAsJsonAsync("/v1/create/zugferd", payload);
response.EnsureSuccessStatusCode();
await File.WriteAllBytesAsync("invoice-zugferd.pdf",
    await response.Content.ReadAsByteArrayAsync());

The difference is not line count; both snippets are short. The difference is what stands behind them. Behind the first: XML only, with the container and the validation still yours. Behind the second: a finished, validated hybrid, and if the request cannot yield a compliant invoice, an HTTP 400 with the violated rules as structured findings instead of a file. To keep your own PDF layout rather than the generated one, the create endpoints accept it and embed the compliant XML into your design.


The migration path in three steps

Step 1: validate your current output. The opening move costs one HTTP call and no code changes: upload documents your ZUGFeRD-csharp pipeline produces today to POST /v1/validate/zugferd. The endpoint accepts files from any producer, extracts or reads the XML, detects the declared profile, and runs the official XSD and Schematron rules plus PDF/A checks, returning findings with rule ids, plain-language messages, and field paths. The report is your baseline: it shows precisely where your current output stands against the current rule sets and therefore exactly what the switch resolves. Wiring the same call into xUnit keeps that baseline visible for the rest of the migration.

Step 2: swap generation. Replace the descriptor chain and the PDF/A work with the JSON request from the mapping table. This is the substantive step, and it is smaller than it looks: the field mapping is mechanical, the API computes totals and the VAT breakdown from your line items, and the container problem disappears because the response already is the finished hybrid. Teams that also serve B2G swap in /v1/create/xrechnung for those buyers with the same request model, and the C# guide has the full lifecycle code including ASP.NET Core typed-client patterns.

Step 3: remove the package. Drop ZUGFeRD-CSharp and the PDF tooling that completed the container from your project. From this point your invoicing code has zero compliance dependencies and no specification-driven redeploys ahead of it. Incoming documents run through /v1/extract/json for hybrids and /v1/parse/json for the plain supplier PDFs the library never covered, with /v1/extract/attachments unpacking embedded BG-24 documents.

Step 1 is the first move of the migration, not a permanent arrangement: once generation moves in step 2, the same validation endpoint simply becomes your regression check on the API's own output inside CI.


A complete compliance service

What the switch buys, stated as the value proposition it is:

The whole lifecycle behind one integration. Creating ZUGFeRD, Factur-X, XRechnung (both syntaxes), and Peppol BIS UBL from one request model; validating documents from any producer against the official rules; extracting incoming hybrids as JSON; AI parsing for the plain PDFs no library reads; rendering previews; attachment handling. The capability matrix above is not a feature race, but the right column is the complete problem, covered.

The specifications stop being your problem. Current FeRD, KoSIT, and CEN artifacts are live server-side before their effective dates. No release monitoring, no version bumps, no retest-and-redeploy cycle, and no dependency on any package's roadmap, commercial or otherwise. Your integration is finished the day it works.

Minimal infrastructure, by design. No PDF library, no XSLT processor, no Java bridge, no Schematron artifacts. One HttpClient, which .NET already ships.

Expertise on call. Deep e-invoicing knowledge with professional support behind the integration, and missing features are integrated on request rather than filed and hoped for.

Processing is stateless throughout: documents are handled in memory and purged when the response ships, nothing is stored or logged, and no invoice data trains any model.


Get started

The fastest way to ground the decision in your own data is step 1 of the migration: validate a few invoices your current pipeline produces and read the report. Create a free InvoiceXML account → and get 100 credits for free, no credit card required.

Runnable C# examples for every operation live in the examples repository.

Related resources:


InvoiceXML is a REST API for European e-invoice compliance covering ZUGFeRD, Factur-X, XRechnung, Peppol UBL, and CII. Stateless processing, GDPR compliant by architecture, and callable from any stack: .NET, Java, Node.js, Python, PHP, Ruby, or anything else with an HTTP client.

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