Automation MCP Server Features Blog Pricing Contact
API

Render Any E-Invoice XML to PDF with One API Call

A reception pipeline never gets to choose what arrives: CII from ZUGFeRD-centric partners, UBL from Peppol, XRechnung in either of its syntaxes, and the occasional credit note. POST /v1/render/xml/to/pdf takes all of them on one route, detects the syntax from the root namespace, and returns a clean, complete PDF preview. This guide covers the endpoint, its options, error behaviour, integration snippets for five stacks, and the JSON alternative for rendering invoices in your own UI.

The awkward truth of e-invoice reception is that the sender picks the format. One customer's pipeline sees UN/CEFACT CII because their partners live in the ZUGFeRD world; the next sees UBL because everything arrives over Peppol; a German inbox sees XRechnung in either of its two syntaxes, plus credit notes, plus the occasional file nobody can place. Every one of those needs the same feature eventually: a human-readable PDF. POST /v1/render/xml/to/pdf exists so that feature is one route with no format switch in front of it: any supported invoice XML in, a clean PDF out, syntax detected from the file itself.

One route for a mixed inbox

The render family has four paths, and they are deliberately the same action:

RouteWhen to prefer it
POST /v1/render/xml/to/pdfMixed or unknown input; the pipeline route
POST /v1/render/xrechnung/to/pdfExplicitly an XRechnung flow; the path documents intent
POST /v1/render/cii/to/pdfExplicitly CII, e.g. XML extracted from ZUGFeRD/Factur-X hybrids
POST /v1/render/ubl/to/pdfExplicitly UBL, e.g. Peppol reception

All four are permanent aliases with identical behaviour and auto-detection; none is deprecated. Detection is deterministic from the root element and namespace: CrossIndustryInvoice in the UN/CEFACT namespace is CII, Invoice or CreditNote in the OASIS UBL namespace is UBL. Because XRechnung, Peppol BIS 3.0, PINT, NLCIUS, and EHF are profiles of those two syntaxes, they all render without anything to declare. Credit notes are labelled as credit notes, so a credited amount never reads as a payable.


The endpoint

Multipart form upload, binary PDF response:

curl -X POST https://api.invoicexml.com/v1/render/xml/to/pdf \
  -H "Authorization: Bearer YOUR_API_KEY" \
  -F "[email protected]" \
  --output invoice-preview.pdf

Files up to 20 MB are accepted (invoice XML is typically a few kilobytes), processing is stateless (in memory for the duration of the request, discarded with the response), and the response is application/pdf with the original file name preserved with a .pdf extension.


What the PDF contains

The layout is generated fresh from the structured data; it is not a stylesheet over the XML, and it is not the visual layer of any hybrid the XML came from. Its design goal is completeness for the human in the loop: seller and buyer with their identifiers, line items with quantities, unit codes, and prices, allowances and charges, the VAT breakdown per category and rate, totals, payment details, and delivery and period information. On top of the standard body, an "Electronic invoice details" section surfaces what other renderers drop: the specification identifier, electronic addresses, legal registrations, line-level references and classifications, and any embedded attachments listed by file name. For reviewers, that section shows exactly what the machine-readable layer claims, which is what an ERP will actually book.


Two optional form fields shape the output. language (en, de, fr, default en) sets the field labels and date formatting; invoice content is never translated. logoUrl takes an absolute http(s) URL of a public PNG or JPEG (up to 2 MB) and prints it top-left, exactly as the create endpoints do, which matters when previews and generated invoices should look like siblings in the same product. Everything else is derived from the document.


Error behaviour you can build on

A pipeline route earns its keep on the bad files. Malformed XML, a root element that is neither CII nor UBL, or a file that is not XML at all each return a structured problem-details response with a machine-readable error code and a plain-language message, never a broken PDF. That makes the failure path wire-able: render on arrival, attach the PDF on success, and route the rare rejection to a human queue with the API's message attached. Note the boundary: rendering reads whatever business terms the file carries and lays them out; it does not run the EN 16931 or national rule sets, so a rendering success is not a compliance verdict. Validation stays its own call, covered below.


Integration snippets

The integration is the same three lines of intent everywhere: build a multipart request, send the file, save the PDF.

C# / .NET

using var http = new HttpClient();
http.DefaultRequestHeaders.Authorization = new("Bearer",
    Environment.GetEnvironmentVariable("INVOICEXML_API_KEY"));

using var form = new MultipartFormDataContent();
form.Add(new ByteArrayContent(await File.ReadAllBytesAsync("invoice.xml")),
    "file", "invoice.xml");
form.Add(new StringContent("de"), "language");

var response = await http.PostAsync(
    "https://api.invoicexml.com/v1/render/xml/to/pdf", form);
response.EnsureSuccessStatusCode();

await File.WriteAllBytesAsync("invoice-preview.pdf",
    await response.Content.ReadAsByteArrayAsync());

Node.js

import { readFile, writeFile } from "node:fs/promises";

const form = new FormData();
form.append("file", new Blob([await readFile("invoice.xml")]), "invoice.xml");
form.append("language", "de");

const response = await fetch("https://api.invoicexml.com/v1/render/xml/to/pdf", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.INVOICEXML_API_KEY}` },
  body: form
});
if (!response.ok) throw new Error(await response.text());

await writeFile("invoice-preview.pdf", Buffer.from(await response.arrayBuffer()));

Python

import os
import requests

with open("invoice.xml", "rb") as f:
    response = requests.post(
        "https://api.invoicexml.com/v1/render/xml/to/pdf",
        headers={"Authorization": f"Bearer {os.environ['INVOICEXML_API_KEY']}"},
        files={"file": ("invoice.xml", f, "application/xml")},
        data={"language": "de"},
    )
response.raise_for_status()

with open("invoice-preview.pdf", "wb") as out:
    out.write(response.content)

PHP

$ch = curl_init("https://api.invoicexml.com/v1/render/xml/to/pdf");
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_RETURNTRANSFER => true,
    CURLOPT_HTTPHEADER => ["Authorization: Bearer " . getenv("INVOICEXML_API_KEY")],
    CURLOPT_POSTFIELDS => [
        "file" => new CURLFile("invoice.xml", "application/xml", "invoice.xml"),
        "language" => "de",
    ],
]);
$pdf = curl_exec($ch);
if (curl_getinfo($ch, CURLINFO_RESPONSE_CODE) !== 200) {
    throw new RuntimeException($pdf);
}
curl_close($ch);

file_put_contents("invoice-preview.pdf", $pdf);

Every snippet works against any of the four routes; swap the path if the explicit format documents intent better in your codebase.


JSON instead of PDF: your own UI

A PDF is the right output when the destination is a document: the archive, an approval email, an attachment pane. When the destination is a screen you control (an AP workbench, a customer portal, a dashboard) render the data, not a document: POST /v1/extract/json reads the same CII and UBL files with the same parser and returns every EN 16931 business term as structured JSON with readable property names: parties, lines, price details, VAT breakdowns, totals, payment details, references, attachments. Your frontend lays it out natively, your search index covers it, and your mobile view stops fighting a fixed page size. Extraction is deterministic (parsed from the structured XML, no AI, no confidence scores), so what is on screen is what is in the file. The two calls pair naturally: JSON for the workflow and the screens, PDF for the humans outside the system, one upload each.


Attachments and validation

Two neighbours complete the reception picture. Embedded attachments: invoice XML can carry supporting documents base64-embedded in the file (BG-24: timesheets, delivery notes, a supplier's own PDF rendition). The rendered preview lists them; POST /v1/extract/attachments returns them as a ZIP, decoded verbatim, with externally referenced URIs deliberately not fetched. Validation: rendering shows what a file says, the validate endpoints judge whether it complies, per format, against the official XSD and Schematron rule sets. A reception pipeline typically validates at the boundary, renders for the archive, and extracts for the workflow, three calls on the same upload shape.


Endpoint reference

OperationEndpointInputOutput
Render any invoice XMLPOST /v1/render/xml/to/pdfCII / UBL XML (multipart)PDF binary
Render, explicit routes/v1/render/xrechnung/to/pdf, /v1/render/cii/to/pdf, /v1/render/ubl/to/pdfSameSame (permanent aliases)
Invoice data as JSONPOST /v1/extract/jsonXML or hybrid PDF{ "invoice": ... } JSON
Embedded attachmentsPOST /v1/extract/attachmentsXML or hybrid PDFZIP of embedded files
Compliance verdict/v1/validate/xrechnung, /v1/validate/cii, /v1/validate/ublXMLValidation JSON findings

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


Get started

If an invoice XML is on disk right now, the curl call above is the whole integration; the online previews for XRechnung, CII, and UBL show the output without writing code. Create a free InvoiceXML account → and get 100 credits for free, no credit card required.

Related resources:


InvoiceXML is a REST API for European e-invoice compliance covering CII, UBL, XRechnung, ZUGFeRD, Factur-X, and Peppol profiles. Stateless processing, GDPR compliant by architecture, and callable from any stack in minutes.

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