E-Invoicing (Factur-X)

PDFik generates hybrid e-invoices: a normal, human-readable PDF that also carries the complete machine-readable invoice data inside the same file, in the Factur-X / ZUGFeRD format used for French and German e-invoicing. Available on every plan, Free included — the output is the same clean PDF/A-3 everywhere.

💡Tip

No API key yet? Paste your invoice XML into the sandbox on pdfik.net — it returns a real, watermarked Factur-X PDF through the same pipeline, a few renders per day per visitor.


What Factur-X Is

A Factur-X document is one PDF file with two audiences:

  • Humans see an ordinary invoice — the PDF is normalized to PDF/A-3, the archival PDF standard.
  • Accounting software reads the UN/CEFACT Cross-Industry Invoice (CII) XML embedded in the file as factur-x.xml and books the invoice without OCR or manual entry.

Factur-X (France) and ZUGFeRD 2.x (Germany) are the same standard under two names. You supply the XML — PDFik never generates or alters invoice data — and PDFik produces the hybrid PDF around it.

🔔Important

PDFik's Factur-X output is validated with veraPDF (PDF/A-3) and Mustangproject (Factur-X structure). Schema-valid does not mean tax-compliant — the invoice content is the caller's responsibility.

There are two ways to produce one:

ModeHowVisual half comes from
Dedicated endpointPOST /einvoice-to-pdf — send only the XMLA block template — PDFik builds the invoice layout from your XML data
einvoice optionAdd an einvoice block to POST /url-to-pdf or POST /html-to-pdfYour own rendered page

Endpoint: POST /einvoice-to-pdf

POST https://api.pdfik.net/einvoice-to-pdf

Request Body

ParameterTypeRequiredDescription
xmlstringYesUN/CEFACT Cross-Industry-Invoice XML (the Factur-X payload), UTF-8, up to 1 MB. Validated against the official XSD of the declared profile before any quota is spent.
profilestringNoFactur-X conformance profile the XML declares: "minimum", "basicwl", "basic", "en16931" (default), or "extended". See Profiles.
template_idstringNoId of a saved invoice template (Dashboard → E-Invoice). Mutually exclusive with template.
templateobjectNoInline block-template JSON (the same format the dashboard editor produces). Mutually exclusive with template_id.
webhook_urlstringNoOptional callback URL where the job outcome payload will be POSTed. Credentials must not be embedded in the URL (https://user:pass@host is rejected) — configure delivery headers in the dashboard instead.
testbooleanNoRun the job in test mode: full pipeline, no real render, a sample PDF is returned. Quotas are not debited. Defaults to false.

Omitting both template_id and template uses your account's default template (the one marked Make default on the dashboard's E-Invoice page), or the built-in default (English labels, A4) if you have not set one. Sending both is rejected with 422 einvoice-options-conflict.

Request Headers

HeaderRequiredDescription
X-API-KeyYesYour API key (sk_live_...).
Idempotency-KeyNo1-255 characters from A-Z a-z 0-9 _ - . :. Retrying a request with the same key returns the original job instead of creating (and billing) a duplicate. Recommended for retry-after-timeout logic.

Example

The XML has to travel inside a JSON string, so build the request body from the file instead of quoting it inline:

# invoice.xml is your UN/CEFACT CII file (EN 16931 profile)
jq -n --rawfile xml invoice.xml '{xml: $xml, profile: "en16931"}' > request.json

curl -X POST https://api.pdfik.net/einvoice-to-pdf \
  -H "X-API-Key: sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json

Response (202 Accepted):

{
  "job_id": "8bfa5cc3-09ef-436f-80d4-a0eb867123aa",
  "status": "queued",
  "detail": "Job successfully sent to processing queue."
}

From here the flow is the same as every other conversion: poll GET /jobs/{job_id} until status is done, then fetch the file via GET /jobs/{job_id}/download — see the asynchronous flow.

The XML must declare what you claim

Two fields in the XML are checked against the request before the XSD runs:

<rsm:CrossIndustryInvoice ...>
  <rsm:ExchangedDocumentContext>
    <ram:GuidelineSpecifiedDocumentContextParameter>
      <!-- must match the "profile" you declare -->
      <ram:ID>urn:cen.eu:en16931:2017</ram:ID>
    </ram:GuidelineSpecifiedDocumentContextParameter>
  </rsm:ExchangedDocumentContext>
  <rsm:ExchangedDocument>
    <!-- 380 = invoice, 381 = credit note -->
    <ram:TypeCode>380</ram:TypeCode>
    ...

A mismatch between the XML's guideline URN and the declared profile is rejected with 422 einvoice-xml-invalid — never silently upgraded or downgraded to another compliance level.


Profiles

ProfileGuideline URN the XML must declareComplete legal invoice?
minimumurn:factur-x.eu:1p0:minimumNo — accompanying data only
basicwlurn:factur-x.eu:1p0:basicwlNo — accompanying data only
basicurn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basicYes
en16931 (default)urn:cen.eu:en16931:2017Yes
extendedurn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extendedYes
⚠️Warning

The minimum and basicwl profiles do not carry a complete invoice: their embedded XML is accompanying data only and is NOT a legally sufficient e-invoice in Germany or France. Use basic, en16931 or extended for a full invoice. When you submit one of these profiles anyway, the 202 response repeats this warning in its detail field.

Germany states this explicitly: the Federal Ministry of Finance's FAQ E-Rechnung says XRechnung and ZUGFeRD from version 2.0.1 meet the VAT requirements for an e-invoice "mit Ausnahme der Profile MINIMUM und BASIC-WL" (read 7 September 2026).


Credit Notes

ExchangedDocument/TypeCode 380 (commercial invoice) and 381 (credit note) are supported. A credit note renders with the template's credit-note heading (the title_credit_note label, "CREDIT NOTE" in the default template) instead of the invoice title. Any other type code is rejected with 422 einvoice-xml-invalid.


Invoice Templates

The visual invoice for /einvoice-to-pdf is built from a block template: a fixed set of sections (heading, trading parties, line items, VAT breakdown, totals, payment instructions, notes, footer) whose labels, locale, accent color, logo, and page setup you control.

  • Dashboard constructor: create and edit templates under Dashboard → E-Invoice, with a live preview of the composed invoice. Saved templates are referenced by template_id in API calls. The Built-in default row offers language presets (English, Deutsch, Français, Italiano, Español, Nederlands, Polski, Português, Čeština, Українська — 14 locales): pick one and Save as my default to get an editable, localized template that becomes your account default.
  • Inline template: send the same JSON directly in the template field, without saving it first.
  • Neither: your account's default template applies (dashboard → E-Invoice → Make default); without one, the built-in default is used (English labels, A4).

An inline template must list exactly the nine v1 block types in their fixed order — disable a block with "enabled": false instead of removing it:

{
  "version": 1,
  "locale": "de-DE",
  "branding": { "accent_color": "#0f4c81", "font": "sans" },
  "page": { "format": "A4", "margin": "10mm 15mm" },
  "blocks": [
    { "type": "invoice-heading",
      "labels": { "title": "RECHNUNG", "title_credit_note": "GUTSCHRIFT", "number": "Nr.", "issue_date": "Rechnungsdatum", "due_date": "Fällig am" } },
    { "type": "trading-parties", "labels": { "seller": "Verkäufer", "buyer": "Rechnungsempfänger" } },
    { "type": "line-items", "show_vat_column": true,
      "labels": { "description": "Beschreibung", "qty": "Menge", "unit_price": "Einzelpreis", "vat": "USt.", "amount": "Betrag" } },
    { "type": "vat-breakdown", "labels": { "title": "USt.-Übersicht" } },
    { "type": "amounts-due", "labels": { "subtotal": "Zwischensumme (netto)", "tax": "USt. {rate}%", "total": "Gesamtbetrag" } },
    { "type": "payment-instructions", "text": "Zahlbar bis {due_date}." },
    { "type": "invoice-notes", "enabled": false },
    { "type": "additional-invoice-data" },
    { "type": "document-footer", "text": "Factur-X {profile} · {guideline}" }
  ]
}

Labels and text fields accept a whitelist of {placeholder} values substituted from the XML (such as {due_date}, {rate}, {profile}); an unknown placeholder or block field is rejected with a message naming the offending path. template_id and template are mutually exclusive — sending both returns 422 einvoice-options-conflict, and an unknown or foreign template_id returns 404 einvoice-template-not-found.


Using Your Own Design (the einvoice option)

If you already render your invoice with /url-to-pdf or /html-to-pdf, attach the XML to that render instead: the page you render becomes the human-readable half, the output is normalized to PDF/A-3, and the XML is embedded as factur-x.xml.

ParameterTypeRequiredDescription
einvoice.formatstringNoE-invoice container format. Only "factur-x" (the default) in v1.
einvoice.profilestringNoSame profiles and default ("en16931") as above.
einvoice.xmlstringYesThe CII XML, UTF-8, up to 1 MB — validated exactly like on /einvoice-to-pdf.
jq -n --rawfile xml invoice.xml '{
  html: "<html><body><h1 style=\"color: #0f4c81;\">Invoice #90812</h1><p>...</p></body></html>",
  einvoice: { format: "factur-x", profile: "en16931", xml: $xml }
}' > request.json

curl -X POST https://api.pdfik.net/html-to-pdf \
  -H "X-API-Key: sk_live_YOUR_KEY" \
  -H "Content-Type: application/json" \
  -d @request.json
ℹ️Note

einvoice cannot be combined with options.user_password (PDF/A forbids encryption) or options.compression (re-saving breaks the PDF/A attributes). The combination is rejected with 422 einvoice-options-conflict on every plan.


Limits, Plans, and Errors

  • XML size: up to 1 MB per invoice. Invoice XML of any size within this limit is accepted — the one exception is XML with embedded base64 file attachments, which can make the request too large: it is rejected with 413 payload-too-large-for-queue before anything is charged. Submit the invoice without embedded attachments.
  • Plans: available on all plans, Free included, and does not require an upgrade. The output is the same clean PDF/A-3 on every plan.
  • Rejected requests cost nothing: XML validation, template resolution, and the size check all run before any quota is spent.
  • Job failures: if the render or the PDF/A-3 embedding fails after the job is queued, the job status and failure webhook carry error_code: EINVOICE_FAILED.
HTTP StatusError CodeMeaning
422EINVOICE_XML_INVALIDThe XML failed parsing, the profile check, the type-code check, or the XSD.
422EINVOICE_OPTIONS_CONFLICTMutually exclusive fields were combined.
404EINVOICE_TEMPLATE_NOT_FOUNDtemplate_id does not exist on this account.
413PAYLOAD_TOO_LARGE_FOR_QUEUEThe job message does not fit the processing queue even compressed.

Testing

Test mode ("test": true) works on /einvoice-to-pdf and with the einvoice option: the request is fully validated (XML, profile, template), the job runs through the real pipeline, and the download returns the bundled sample PDF — not a Factur-X render of your XML. Use it to verify integration plumbing; submit a real job to check the actual hybrid output.

The dashboard's E-Invoice page also shows a live preview of the composed invoice while you edit a template. The preview is not test mode either — it displays the invoice layout without rendering a PDF. See the note in Testing.