How to Create a ZUGFeRD PDF in .NET with C#

Create a ZUGFeRD or Factur-X hybrid PDF/A-3 invoice with pdfRest using .NET with C#.
Share this page

This tutorial shows how to create a ZUGFeRD or Factur-X hybrid PDF/A-3 invoice with .NET with C#. It uses the pdfRest Create ZUGFeRD PDF API Tool to send canonical invoice XML and a visual invoice PDF, then returns a managed invoice resource for the next step in the workflow.

Why Create a ZUGFeRD PDF in .NET with C#?

Electronic invoicing programs increasingly require an invoice that people can read and systems can process. ZUGFeRD and Factur-X pair a visual PDF with structured invoice XML so finance teams, customers, and downstream systems can work from the same invoice record.

A .NET with C# billing service could generate the invoice XML from its accounting data, pair it with a customer-facing PDF, and submit both to pdfRest before delivery. That gives the service a documented API step for producing the hybrid invoice rather than relying on a manual desktop process.

The supplied PDF is retained when it agrees with the canonical XML. With regenerate_pdf=true, the service can create a replacement PDF when it cannot confirm that agreement or finds a mismatch, which gives the workflow a defined fallback instead of silently accepting an uncertain visual invoice.

What the Request Does

The multipart request sends the invoice XML as file and the visual invoice PDF as pdf_file. regenerate_pdf controls the fallback behavior. Optional render_options set locale, label language, fonts, and accent color only for a replacement PDF; they do not alter a supplied PDF that is preserved.

The C# samples use framework HttpClient APIs and disposable request content. The API key is read from PDFREST_API_KEY, keeping the credential out of the source file.

How to Create a ZUGFeRD PDF in .NET with C# Code Example

/*
 * What this sample does:
 * - Creates a ZUGFeRD / Factur-X PDF/A-3 invoice from XML and an existing PDF through multipart/form-data.
 * - Preserves the supplied PDF when it agrees with the canonical XML.
 * - Regenerates a styled replacement only for a mismatch or unconfirmed PDF/XML match.
 *
 * Setup (environment):
 * - Copy .env.example to .env and set PDFREST_API_KEY=your_api_key_here.
 * - Optional: set PDFREST_URL to override the API region. For EU/GDPR compliance and proximity, use:
 *     PDFREST_URL=https://eu-api.pdfrest.com
 *   For more information visit https://pdfrest.com/pricing#how-do-eu-gdpr-api-calls-work
 *
 * Usage:
 *   dotnet run -- zugferd-pdf-multipart /path/to/invoice.xml /path/to/invoice.pdf
 *
 * Output:
 * - Prints the API JSON response and returns a nonzero exit code when the request fails.
 */
using System.Net.Http.Headers;
using Newtonsoft.Json.Linq;

namespace Samples.EndpointExamples.MultipartPayload;

public static class ZugferdPdf
{
    public static async Task Execute(string[] args)
    {
        var invoiceXml = args.Length > 0 ? args[0] : "/path/to/invoice.xml";
        var invoicePdf = args.Length > 1 ? args[1] : "/path/to/invoice.pdf";
        if (!File.Exists(invoiceXml)) throw new FileNotFoundException("Invoice XML not found.", invoiceXml);
        if (!File.Exists(invoicePdf)) throw new FileNotFoundException("Invoice PDF not found.", invoicePdf);
        var apiKey = Environment.GetEnvironmentVariable("PDFREST_API_KEY") ?? throw new InvalidOperationException("Missing PDFREST_API_KEY");
        var baseUrl = Environment.GetEnvironmentVariable("PDFREST_URL") ?? "https://api.pdfrest.com";
        using var client = new HttpClient { BaseAddress = new Uri(baseUrl) };
        client.DefaultRequestHeaders.TryAddWithoutValidation("Api-Key", apiKey);
        using var form = new MultipartFormDataContent();
        var xml = new ByteArrayContent(await File.ReadAllBytesAsync(invoiceXml));
        xml.Headers.ContentType = new MediaTypeHeaderValue("application/xml");
        form.Add(xml, "file", Path.GetFileName(invoiceXml));
        var pdf = new ByteArrayContent(await File.ReadAllBytesAsync(invoicePdf));
        pdf.Headers.ContentType = new MediaTypeHeaderValue("application/pdf");
        form.Add(pdf, "pdf_file", Path.GetFileName(invoicePdf));
        // Render options style only a fallback-generated replacement, not a preserved PDF.
        form.Add(new StringContent("true"), "regenerate_pdf");
        form.Add(new StringContent(new JObject { ["locale"] = "de-DE", ["label_language"] = "de", ["font"] = "arial", ["bold_font"] = "arialbold", ["accent_color_rgb"] = new JArray(0, 92, 171) }.ToString()), "render_options");
        form.Add(new StringContent("zugferd_invoice"), "output");
        var response = await client.PostAsync("zugferd-pdf", form);
        Console.WriteLine(await response.Content.ReadAsStringAsync());
        if (!response.IsSuccessStatusCode) Environment.ExitCode = 1;
    }
}

Source: View the multipart sample on GitHub.

Breaking Down the Code

The request is compact, but each field has a distinct role in the hybrid-invoice workflow.

  • Identify the two document roles. The file field holds canonical invoice XML, while pdf_file holds the customer-facing invoice PDF. The sample marks them as application/xml and application/pdf, respectively, so the service can distinguish the structured record from its visual companion.
  • Build the multipart request. MultipartFormDataContent owns the fields and generates the multipart boundary. Each StreamContent value is disposed with the form after HttpClient sends it.
  • Configure the request safely. The sample gets PDFREST_API_KEY and an optional PDFREST_URL from environment variables, keeping both regional selection and credentials outside the code.
  • Understand the fallback settings. regenerate_pdf=true permits a replacement PDF when pdfRest cannot confirm that the supplied PDF agrees with the XML. The render_options object controls locale, labels, fonts, and accent color for that replacement only; it does not restyle a supplied PDF that pdfRest preserves.
  • Name and handle the result. output=zugferd_invoice gives the generated resource a useful output name. The example prints response.Content and returns a nonzero process exit code for an unsuccessful HTTP status. Parse the JSON only after IsSuccessStatusCode is true.

Beyond the Tutorial

In this .NET with C# tutorial, you created a ZUGFeRD or Factur-X hybrid invoice from invoice XML and a visual PDF. The response gives the application a managed output resource it can download, deliver, or pass to a following pdfRest operation.

For the complete request fields, accepted values, response contract, and service limits, review the Create ZUGFeRD PDF API Tool documentation. The same endpoint can be used with multipart uploads when the files are present in the current request or with resource IDs after a separate upload step.

The repository also includes a JSON-payload Create ZUGFeRD PDF sample for .NET with C#. That version uploads the source files first and then sends their resource IDs to /zugferd-pdf, which is useful when a service already stages input files for later operations.

Generate a self-service API Key now!
Create your FREE API Key to start processing PDFs in seconds, only possible with pdfRest.