Creating ZUGFeRD / Factur-X e-invoice
What is ZUGFeRD / Factur-X?
ZUGFeRD (Germany) and Factur-X (France) are hybrid electronic invoice standards. A hybrid e-invoice is a single PDF/A-3 file that is both human-readable (the visual PDF you design) and machine-readable (a structured invoice XML embedded inside the PDF). This lets a person open the invoice like any normal PDF, while accounting systems can extract the embedded XML to process the invoice automatically and meet e-invoicing compliance requirements.
The two standards share the same underlying XML format, so CraftMyPDF can produce either one from the same request.
With CraftMyPDF, you design the invoice template as usual, then enable the einvoice options in the PDF Generation API so the generated PDF is converted to a PDF/A-3 invoice with your supplied invoice XML embedded.
Experimental feature — ZUGFeRD / Factur-X e-invoice conversion is experimental and may change or be removed in future releases. We'd love your feedback, so please reach out to us so we can improve it.
How it works
E-invoice conversion runs as the final step of PDF generation (after compression). Enable it by adding the following options to your /v1/create request body:
| Option | Description |
|---|---|
einvoice | Set to true to enable ZUGFeRD / Factur-X PDF/A-3 conversion. |
einvoice_xml | The invoice XML to embed. Required when einvoice is enabled. |
einvoice_format | Output format: fx (Factur-X) or zf (ZUGFeRD). Defaults to fx. |
einvoice_profile | The profile to use. Defaults to EN16931. Valid profiles depend on einvoice_version:• Version 1: BASIC, COMFORT, EXTENDED.• Version 2: MINIMUM, BASICWL, BASIC, EN16931, EXTENDED-CTC-FR, EXTENDED, XRECHNUNG. |
einvoice_version | ZUGFeRD version, 1 or 2. Defaults to 2. |
einvoice_validate | Optional. Set to true to validate the produced invoice. |
When einvoice and einvoice_validate are both enabled (and export_type is json), the response includes:
einvoice_validation_status— overall validation status, eithervalidorinvalid.einvoice_validation_details— the full validation report (XML) produced by the validator.
E-invoice conversion is incompatible with password_protected and pdf_version: '1.4'. Both options are ignored when einvoice is enabled.
Example request
The examples below use the same payload as our Bruno test collection. The einvoice_xml value is a complete Factur-X invoice — to keep the snippets readable it's shown as a placeholder below, and the full XML is provided at the end of this page.
curl
curl -X POST \
--header "Content-Type: application/json" \
--header "X-API-KEY: <your-api-key>" \
--data '{
"data": {
"order_id": "123456789",
"shipping_date": "2024-10-01",
"recipient_name": "John Doe",
"recipient_address": "123 Main Street, \nApt 4B, \nNew York, 10001, USA",
"sender_name": "ACME Corporation",
"sender_address": "456 Industrial Blvd, \nLos Angeles, 90001, USA",
"sender_phone": "+1-555-987-6543",
"package_weight": "2.5 KG",
"package_dimensions": "12cmx12cmx12cm",
"tracking_number": "TRACK123456789US",
"remarks": "NO REMARKS"
},
"template_id": "1d277b2341cb5b0a",
"einvoice": true,
"einvoice_validate": true,
"einvoice_format": "fx",
"einvoice_profile": "BASIC",
"einvoice_version": 2,
"einvoice_xml": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><rsm:CrossIndustryInvoice ...>...</rsm:CrossIndustryInvoice>"
}' \
"https://api.craftmypdf.com/v1/create"
Node.js
const apiKey = "<your-api-key>";
// The full Factur-X invoice XML (see the bottom of this page for the complete sample)
const einvoiceXml =
'<?xml version="1.0" encoding="UTF-8"?><rsm:CrossIndustryInvoice ...>...</rsm:CrossIndustryInvoice>';
const payload = {
data: {
order_id: "123456789",
shipping_date: "2024-10-01",
recipient_name: "John Doe",
recipient_address: "123 Main Street, \nApt 4B, \nNew York, 10001, USA",
sender_name: "ACME Corporation",
sender_address: "456 Industrial Blvd, \nLos Angeles, 90001, USA",
sender_phone: "+1-555-987-6543",
package_weight: "2.5 KG",
package_dimensions: "12cmx12cmx12cm",
tracking_number: "TRACK123456789US",
remarks: "NO REMARKS",
},
template_id: "1d277b2341cb5b0a",
einvoice: true,
einvoice_validate: true,
einvoice_format: "fx",
einvoice_profile: "BASIC",
einvoice_version: 2,
einvoice_xml: einvoiceXml,
};
const response = await fetch("https://api.craftmypdf.com/v1/create", {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-API-KEY": apiKey,
},
body: JSON.stringify(payload),
});
const result = await response.json();
console.log("Status:", result.status);
console.log("E-invoice validation:", result.einvoice_validation_status);
console.log("Download URL:", result.file);
A successful response (export_type: json) returns the generated PDF URL in file, plus einvoice_validation_status (valid / invalid) and einvoice_validation_details when validation is enabled.
Validating the e-invoice on your end
While CraftMyPDF can validate the invoice for you (via einvoice_validate), you can independently verify the generated PDF on the receiving/customer end. A compliant ZUGFeRD / Factur-X file must pass two checks:
- The PDF/A-3 container is well-formed (archival PDF conformance).
- The embedded invoice XML conforms to the EN 16931 / profile Schematron rules.
Mustang (mustangproject.org)
Mustang is the de-facto open-source tool for validating ZUGFeRD / Factur-X / XRechnung invoices. It extracts the embedded XML, runs the Schematron rules for the detected profile, and also checks the PDF/A-3 layer (via veraPDF), so a single command covers both checks above.
CraftMyPDF also uses Mustang internally to validate the PDF when you set einvoice_validate: true. The result is returned in einvoice_validation_status and einvoice_validation_details, so you should get the same outcome when validating on your end with the same Mustang version.
Download the command-line JAR from the Mustang command line page, then validate your PDF:
java -Xmx1G -Dfile.encoding=UTF-8 -jar Mustang-CLI-x.x.x.jar \
--action validate \
--source your-einvoice.pdf
You can also validate a standalone XML file (--source factur-x.xml), suppress secondary notices with --no-notices, or save the report as a PDF with --log-as-pdf. The tool returns an XML report detailing the rules fired and any failures.
veraPDF
veraPDF is a purpose-built, open-source validator for the PDF/A family of standards. Use it to confirm the generated file is a valid PDF/A-3 container (the archival wrapper required for ZUGFeRD / Factur-X):
verapdf --flavour 3b your-einvoice.pdf
Note that veraPDF validates the PDF/A container only — it does not check the business rules in the embedded invoice XML. For the XML/Schematron rules, use Mustang above. Running both gives you full coverage of a compliant e-invoice.
Resources
- Mustang project: mustangproject.org — command line download
- veraPDF: verapdf.org — download
- ZUGFeRD / Factur-X standard: ferd-net.de and fnfe-mpe.org (Factur-X)
Full sample invoice XML
This is the complete einvoice_xml value used in the examples above (a minimal Factur-X BASIC invoice), pretty-printed for readability:
<?xml version="1.0" encoding="UTF-8"?>
<rsm:CrossIndustryInvoice
xmlns:rsm="urn:un:unece:uncefact:data:standard:CrossIndustryInvoice:100"
xmlns:ram="urn:un:unece:uncefact:data:standard:ReusableAggregateBusinessInformationEntity:100"
xmlns:udt="urn:un:unece:uncefact:data:standard:UnqualifiedDataType:100">
<rsm:ExchangedDocumentContext>
<ram:GuidelineSpecifiedDocumentContextParameter>
<ram:ID>urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic</ram:ID>
</ram:GuidelineSpecifiedDocumentContextParameter>
</rsm:ExchangedDocumentContext>
<rsm:ExchangedDocument>
<ram:ID>INV-2026-0001</ram:ID>
<ram:TypeCode>380</ram:TypeCode>
<ram:IssueDateTime>
<udt:DateTimeString format="102">20260626</udt:DateTimeString>
</ram:IssueDateTime>
</rsm:ExchangedDocument>
<rsm:SupplyChainTradeTransaction>
<ram:IncludedSupplyChainTradeLineItem>
<ram:AssociatedDocumentLineDocument>
<ram:LineID>1</ram:LineID>
</ram:AssociatedDocumentLineDocument>
<ram:SpecifiedTradeProduct>
<ram:Name>Example item</ram:Name>
</ram:SpecifiedTradeProduct>
<ram:SpecifiedLineTradeAgreement>
<ram:NetPriceProductTradePrice>
<ram:ChargeAmount>100.00</ram:ChargeAmount>
</ram:NetPriceProductTradePrice>
</ram:SpecifiedLineTradeAgreement>
<ram:SpecifiedLineTradeDelivery>
<ram:BilledQuantity unitCode="C62">1</ram:BilledQuantity>
</ram:SpecifiedLineTradeDelivery>
<ram:SpecifiedLineTradeSettlement>
<ram:ApplicableTradeTax>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>19</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:SpecifiedTradeSettlementLineMonetarySummation>
<ram:LineTotalAmount>100.00</ram:LineTotalAmount>
</ram:SpecifiedTradeSettlementLineMonetarySummation>
</ram:SpecifiedLineTradeSettlement>
</ram:IncludedSupplyChainTradeLineItem>
<ram:ApplicableHeaderTradeAgreement>
<ram:BuyerReference>BUYER-REF-001</ram:BuyerReference>
<ram:SellerTradeParty>
<ram:Name>Example Seller GmbH</ram:Name>
<ram:PostalTradeAddress>
<ram:PostcodeCode>10115</ram:PostcodeCode>
<ram:LineOne>Sellerstrasse 1</ram:LineOne>
<ram:CityName>Berlin</ram:CityName>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
<ram:SpecifiedTaxRegistration>
<ram:ID schemeID="VA">DE123456789</ram:ID>
</ram:SpecifiedTaxRegistration>
</ram:SellerTradeParty>
<ram:BuyerTradeParty>
<ram:Name>Example Buyer Ltd</ram:Name>
<ram:PostalTradeAddress>
<ram:PostcodeCode>80331</ram:PostcodeCode>
<ram:LineOne>Buyerstrasse 2</ram:LineOne>
<ram:CityName>Munich</ram:CityName>
<ram:CountryID>DE</ram:CountryID>
</ram:PostalTradeAddress>
</ram:BuyerTradeParty>
</ram:ApplicableHeaderTradeAgreement>
<ram:ApplicableHeaderTradeDelivery>
<ram:ActualDeliverySupplyChainEvent>
<ram:OccurrenceDateTime>
<udt:DateTimeString format="102">20260626</udt:DateTimeString>
</ram:OccurrenceDateTime>
</ram:ActualDeliverySupplyChainEvent>
</ram:ApplicableHeaderTradeDelivery>
<ram:ApplicableHeaderTradeSettlement>
<ram:InvoiceCurrencyCode>EUR</ram:InvoiceCurrencyCode>
<ram:ApplicableTradeTax>
<ram:CalculatedAmount>19.00</ram:CalculatedAmount>
<ram:TypeCode>VAT</ram:TypeCode>
<ram:BasisAmount>100.00</ram:BasisAmount>
<ram:CategoryCode>S</ram:CategoryCode>
<ram:RateApplicablePercent>19</ram:RateApplicablePercent>
</ram:ApplicableTradeTax>
<ram:SpecifiedTradePaymentTerms>
<ram:DueDateDateTime>
<udt:DateTimeString format="102">20260726</udt:DateTimeString>
</ram:DueDateDateTime>
</ram:SpecifiedTradePaymentTerms>
<ram:SpecifiedTradeSettlementHeaderMonetarySummation>
<ram:LineTotalAmount>100.00</ram:LineTotalAmount>
<ram:TaxBasisTotalAmount>100.00</ram:TaxBasisTotalAmount>
<ram:TaxTotalAmount currencyID="EUR">19.00</ram:TaxTotalAmount>
<ram:GrandTotalAmount>119.00</ram:GrandTotalAmount>
<ram:DuePayableAmount>119.00</ram:DuePayableAmount>
</ram:SpecifiedTradeSettlementHeaderMonetarySummation>
</ram:ApplicableHeaderTradeSettlement>
</rsm:SupplyChainTradeTransaction>
</rsm:CrossIndustryInvoice>
When embedding this XML in the JSON request, it must be a single string with the quotes escaped (see the curl example above).
API reference
For the complete list of options and response fields, see the PDF Generation API reference.