Factur-X and ZUGFeRD invoices from /render
Pass your invoice XML in options.facturX and /render returns a Factur-X / ZUGFeRD hybrid PDF (PDF/A-3A) that is also tagged for accessibility. /preview checks the XML for free.
POST /api/v1/render now accepts options.facturX. Send the invoice as CrossIndustryInvoice XML together with a profile, and the PDF you get back is a Factur-X / ZUGFeRD hybrid e-invoice: the XML is embedded in the PDF as an associated file and described in its metadata, so accounting software can read the data while people read the page. The PDF is PDF/A-3A and stays tagged for PDF/UA-1, so it is still an accessible document.
It works with both a saved templateId and inline DSL. The profile is one of MINIMUM, BASIC WL, BASIC, EN 16931, EXTENDED or XRECHNUNG. The embedded file is called xrechnung.xml for XRECHNUNG and factur-x.xml otherwise. You can also set the file's relationship to the PDF (Data, Alternative or Source). It defaults to Data for MINIMUM and BASIC WL, which must use it, and Alternative for the other profiles. The XML must be well-formed UTF-8, up to 1 MB, with a CrossIndustryInvoice root element. Anything else returns a 400 that says what to fix.
We do not check the business content of the invoice, such as the EN 16931 or XRechnung rules. You supply valid XML, and a free validator, such as the KoSIT validator for XRechnung or the Mustang and FNFE-MPE tools for Factur-X, will tell you whether it is. MINIMUM and BASIC WL are not valid invoices in Germany, where e-invoices become mandatory for domestic B2B from 2027 (turnover above EUR 800k) and 2028 (everyone) and ZUGFeRD is an accepted format. France accepts Factur-X in its 2026-2027 reform.
POST /api/v1/preview and /api/v1/preview/validate check the same option for free and return the same errors, but /preview does not embed the XML: its PDF stays an ordinary preview, and the response carries X-Factur-X: valid; not embedded, use /api/v1/render. Only /render produces the e-invoice.
Without options.facturX nothing changes, and billing and limits are the same. The option is described in the API reference.