E-invoicing
Factur-X and ZUGFeRD PDFs with embedded EN 16931 XML, XRechnung export in CII or UBL, the invoice model, computed totals and validation reports.
Dynamic Document API turns one invoice model into e-invoices that pass the official validators. Two outputs are available:
- Hybrid PDFs (Factur-X, ZUGFeRD): your usual invoice PDF, converted to PDF/A-3b, with the invoice XML embedded.
- Standalone XML (XRechnung, EN 16931): the XML on its own, in CII or UBL syntax.
Every file is validated before it is delivered, and the validation report comes back with the render.
| Output | Request | Format |
|---|---|---|
| Factur-X / ZUGFeRD PDF | output.einvoice on any PDF render | PDF/A-3b with embedded UN/CEFACT CII XML (Factur-X 1.09, identical to ZUGFeRD 2.x) |
| XRechnung PDF | output.einvoice with the profile XRECHNUNG | PDF/A-3b with an embedded XRechnung 3.0 CII file, xrechnung.xml |
| XRechnung or EN 16931 XML | POST /v1/einvoices | CII (UN/CEFACT D16B) or UBL 2.1 |
The XML follows the European semantic model EN 16931-1. It is checked with the KoSIT validator, which applies the XRechnung, EN 16931 and Factur-X rules. PDFs are checked with veraPDF. Neither validator is modified.
E-invoicing is included in the Growth, Pro, Scale and Enterprise plans. On Free and Starter, requests with output.einvoice and calls to POST /v1/einvoices fail with 402 plan_feature_unavailable, and a template's default e-invoice is skipped with a warning. See Plans and limits.
What we guarantee
Dynamic Document API guarantees technical format conformance, validated against the official schemas and schematron rules. You remain responsible for the content of your invoices, their tax correctness and their transmission, for example through a certified platform (PDP/PA) in France or the e-invoicing portals of German public authorities.
Profiles
A profile decides which information the XML carries and which rules apply. Pick the one your recipient asks for. EN16931 is the right default for B2B invoices in the EU, and XRECHNUNG is the standard for German public-sector buyers.
| Profile | Guideline ID (BT-24) | Invoice lines | Embedded file | AFRelationship | Syntaxes |
|---|---|---|---|---|---|
MINIMUM | urn:factur-x.eu:1p0:minimum | not transmitted | factur-x.xml | Data | CII |
BASIC_WL | urn:factur-x.eu:1p0:basicwl | not transmitted | factur-x.xml | Data | CII |
BASIC | urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic | basic subset | factur-x.xml | Alternative | CII |
EN16931 | urn:cen.eu:en16931:2017 | complete | factur-x.xml | Alternative | CII, UBL |
EXTENDED | urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended | complete | factur-x.xml | Alternative | CII |
XRECHNUNG | urn:cen.eu:en16931:2017#compliant#urn:xeinkauf.de:kosit:xrechnung_3.0 | complete | xrechnung.xml | Alternative | CII, UBL |
MINIMUMandBASIC_WLcarry header data and totals only. They don't satisfy EN 16931, so German B2B rules don't accept them as e-invoices. Use them only where a booking aid is enough.- The PDF's XMP metadata names the profile in
fx:ConformanceLevel, as the Factur-X specification requires (for exampleEN 16931orBASIC WL). - UBL is available for standalone XML only. Hybrid PDFs always embed CII.
Create a Factur-X or ZUGFeRD PDF
Add output.einvoice to any PDF render: template, HTML, URL or Markdown. The page renders as usual. Then the engine:
- generates the XML from the invoice model and validates it
- converts the PDF to PDF/A-3b and embeds the XML as an attachment
- writes the Factur-X metadata and validates the finished PDF with veraPDF
The invoice model can sit in your template data. invoice_path points at it, so the template and the XML use the same numbers:
curl https://api-eu.dynamicdocumentapi.com/v1/renders \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Idempotency-Key: invoice-R-2026-0042" \
-H "Content-Type: application/json" \
-d '{
"input": { "type": "template", "template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C" },
"data": {
"invoice": {
"number": "R-2026-0042",
"issue_date": "2026-09-23",
"due_date": "2026-10-23",
"currency": "EUR",
"buyer_reference": "PO-4711",
"seller": {
"name": "Example GmbH",
"vat_id": "DE123456789",
"electronic_address": { "id": "invoices@example.com", "scheme": "EM" },
"address": { "line1": "Beispielstraße 1", "city": "Berlin", "postcode": "10115", "country": "DE" }
},
"buyer": {
"name": "Customer AG",
"vat_id": "DE987654321",
"electronic_address": { "id": "ap@customer.example", "scheme": "EM" },
"address": { "line1": "Marktplatz 5", "city": "München", "postcode": "80331", "country": "DE" }
},
"payment": { "means_code": "58", "credit_transfers": [{ "account_id": "DE02120300000000202051" }] },
"lines": [
{ "quantity": "10", "unit": "HUR", "price": { "net": "100.00" }, "tax": { "category": "S", "rate": "19" }, "item": { "name": "Consulting" } }
]
}
},
"output": {
"format": "pdf",
"filename": "invoice-{{ data.invoice.number }}.pdf",
"einvoice": { "profile": "EN16931", "invoice_path": "invoice" }
}
}'The E-Rechnung template in the gallery is built for this. It reads the invoice model from invoice, prints the computed amounts and works with every profile.
| Field | Description |
|---|---|
output.einvoice.profile | Required. One of the profiles |
output.einvoice.invoice | The invoice model itself |
output.einvoice.invoice_path | A dotted path into data that holds the invoice model, such as "invoice" or "order.invoice". Send exactly one of invoice and invoice_path. |
output.einvoice.syntax | Omit it, or send "cii". Hybrid PDFs always embed CII. |
Rules checked before anything renders. Each is a 400 validation_error with a JSON Pointer to the field:
output.formatmust bepdf. An e-invoice PDF is always PDF/A-3b, sopdf.pdfamust be omitted or"3b".pdf.protectcan't be combined withoutput.einvoice, because PDF/A forbids encryption.invoice_pathmust resolve to an object indata. It can't be combined withdata_url, because the data isn't available when the request is checked; sendinvoiceinstead. (A template's default e-invoice works withdata_url.)- The invoice is validated against the model's JSON Schema, for example
/output/einvoice/invoice/lines/0/tax/category. - Editor previews reject
output.einvoice. Test renders accept it; see test mode.
In batches, a literal invoice applies to every item. invoice_path is resolved in each item's own data, so every row can carry its own invoice.
Print the computed amounts
When a render embeds an e-invoice, or its template has a default e-invoice, the engine computes the invoice before the template runs and passes the results to the template as render.invoice. Print these values instead of calculating totals in the template, and the visible invoice always matches the XML to the cent:
{% set e = render.invoice %}
<table>
{% for line in invoice.lines %}
<tr>
<td>{{ line.item.name }}</td>
<td>{{ line.quantity }}</td>
<td>{{ e.lines[loop.index0].net_amount | format_currency(e.currency, "de-DE") }}</td>
</tr>
{% endfor %}
</table>
{% for vat in e.vat_breakdown %}
<p>VAT {{ vat.rate }} % on {{ vat.basis | format_currency(e.currency, "de-DE") }}: {{ vat.tax | format_currency(e.currency, "de-DE") }}</p>
{% endfor %}
<p><strong>Total due: {{ e.totals.due_payable | format_currency(e.currency, "de-DE") }}</strong></p>| Field | Description |
|---|---|
number, currency | From the invoice |
lines[].id, lines[].net_amount | Line ID and line net amount (BT-131), in the order of invoice.lines |
vat_breakdown[] | One entry per VAT category and rate: category, rate, basis, tax, exemption_reason, exemption_reason_code |
totals | line_total, allowance_total, charge_total, tax_basis_total, tax_total, grand_total, prepaid, rounding, due_payable |
Amounts are strings with two decimals, such as "1190.00", which format_currency accepts.
render.invoice is there in every render that embeds an e-invoice (output.einvoice, or the template's default), and in every render of a template with a default e-invoice that doesn't embed it: the opt-out below, plans without e-invoicing, image output, editor and dashboard previews. The computation is free, so the template needs no fallback and no arithmetic of its own.
render.einvoice holds the same values plus profile, but only when the XML is embedded. Use it to tell the two apart, for example for a note on the page:
{% if render.einvoice %}<p>This PDF carries an embedded {{ render.einvoice.profile }} e-invoice.</p>{% endif %}An invoice that can't be computed fails an e-invoice render with einvoice_invalid before the template runs. Where nothing is embedded, it never fails the render: render.invoice is left out and the render carries the warning invoice_totals_unavailable, whose message names the first problem.
Make a template an e-invoice by default
A PDF template (code or Markdown) can store the e-invoice in its default options. Every render of it is then a Factur-X / ZUGFeRD PDF, and callers only send the data:
{
"input": { "type": "template", "template_id": "tpl_01J9ZK3M7Q8V5W2X4Y6Z8A0B1C" },
"data": { "invoice": { "number": "R-2026-0042", "issue_date": "2026-09-23", "currency": "EUR", "…": "…" } }
}Turn it on in the dashboard under the template's Options → E-invoice (ZUGFeRD / Factur-X): pick the profile (EN16931 by default) and the data path (invoice by default). Through the API, it is einvoice in the template's options, next to output_format and pdf:
"options": {
"output_format": "pdf",
"pdf": { "paper": { "size": "A4" } },
"einvoice": { "profile": "EN16931", "invoice_path": "invoice" }
}A template default holds a profile and a data path, never an invoice. It is checked when the template is saved: profile must be one of the profiles, invoice_path a dotted data path, output_format must be pdf, and pdf.protect isn't allowed with it. A default pdf.pdfa of "2b" is raised to "3b" at render time. Canvas templates can't store an e-invoice; send output.einvoice with the request instead. Every invoice template in the gallery ships with {"profile": "EN16931", "invoice_path": "invoice"}, and "Use this template" keeps it.
What a request sends decides:
| Request | Result |
|---|---|
No output.einvoice | The template's default applies, with the same checks as output.einvoice |
output.einvoice with an object | Replaces the default completely, for example another profile or path |
"output": { "einvoice": null } | Turns the default off: a plain PDF ("einvoice": null in the convenience endpoints) |
The same applies to batches (POST /v1/batches): the default's invoice_path is resolved in each item's data, an item without an invoice object there rejects the batch with 400 validation_error at /items/<i>/data/<path> (/csv/row/<n>/data/<path> for CSV rows, whose text cells can't hold the invoice's lines, so send JSON items), and "output": {"einvoice": null} turns the default off for every item. In the dashboard's batch wizard, that is the Embed the e-invoice checkbox on the review step. Other differences from a request-level output.einvoice:
- Missing invoice. If the path doesn't lead to an object in the request's
data, the request fails with400 validation_errorat/data, and the message names the opt-out. Model errors point intodata, for example/data/invoice/seller/name. data_urlworks. The data isn't available when the request is checked, so the renderer resolves the path. If it finds no invoice there, the render fails witheinvoice_invalid.- Plans without e-invoicing. On Free and Starter the default is skipped: the render is a normal PDF and carries the warning
einvoice_skipped_plan. A request that sendsoutput.einvoiceitself still fails with402 plan_feature_unavailable. - Other formats. The default only applies to PDF output: rendering such a template as PNG, JPG or WebP gives a plain image without an e-invoice.
- Previews. Editor previews and the publish test render ignore the default and stay plain PDFs.
In all of these cases without an embedded e-invoice, the template still gets the computed amounts as render.invoice, at no cost.
Export XRechnung or EN 16931 XML
POST /v1/einvoices returns the XML on its own, without a PDF. It creates a regular render with one application/xml file, and accepts the usual mode, delivery, webhook, reference, metadata and test fields and the Idempotency-Key header:
curl https://api-eu.dynamicdocumentapi.com/v1/einvoices \
-H "Authorization: Bearer $DYNAMIC_DOCUMENT_API_KEY" \
-H "Idempotency-Key: xrechnung-XR-2026-0007" \
-H "Content-Type: application/json" \
-d @xrechnung-request.json \
-o XR-2026-0007.xmlxrechnung-request.json holds the profile, the syntax and the invoice, for example the one in The invoice model (shortened here):
{
"profile": "XRECHNUNG",
"syntax": "ubl",
"filename": "XR-2026-0007.xml",
"invoice": { "number": "XR-2026-0007", "issue_date": "2026-09-23", "currency": "EUR", "seller": {}, "buyer": {}, "lines": [] },
"delivery": { "type": "binary" }
}| Field | Description |
|---|---|
profile | Required. One of the profiles |
syntax | cii (default) or ubl. UBL is available for EN16931 and XRECHNUNG only. |
invoice | Required. The invoice model |
filename | Defaults to the invoice number, cleaned up for use as a file name, plus .xml |
With delivery.type: "binary" the response body is the XML file itself. Otherwise you get the render object: its input_type is einvoice, its output_format is xml, and it carries the validation report in result. In UBL, credit notes (type code 381) use the CreditNote root element and everything else uses Invoice.
The invoice model
The invoice model is JSON for the EN 16931 semantic model, with snake_case names. Every field corresponds to one business term (BT) or business group (BG) of the standard, and the API reference lists all of them. This complete example is a valid XRechnung for a German federal authority:
{
"number": "XR-2026-0007",
"issue_date": "2026-09-23",
"due_date": "2026-10-23",
"currency": "EUR",
"buyer_reference": "04011000-12345-34",
"payment_terms": "Zahlbar innerhalb von 30 Tagen ohne Abzug.",
"invoice_period": { "start": "2026-09-01", "end": "2026-09-30" },
"seller": {
"name": "Größenwahn GmbH",
"vat_id": "DE123456789",
"tax_number": "30/123/45678",
"legal_registration": { "id": "HRB 12345" },
"electronic_address": { "id": "rechnung@groessenwahn.example", "scheme": "EM" },
"address": { "line1": "Beispielstraße 1", "city": "Berlin", "postcode": "10115", "country": "DE" },
"contact": { "name": "Erika Muster", "phone": "+49 30 1234567", "email": "erika.muster@groessenwahn.example" }
},
"buyer": {
"name": "Bundesamt für Beispiele",
"electronic_address": { "id": "04011000-12345-34", "scheme": "0204" },
"address": { "line1": "Amtsweg 2", "city": "Bonn", "postcode": "53113", "country": "DE" }
},
"payment": {
"means_code": "58",
"remittance_information": "XR-2026-0007",
"credit_transfers": [{ "account_id": "DE02120300000000202051", "account_name": "Größenwahn GmbH" }]
},
"lines": [
{
"quantity": "8",
"unit": "HUR",
"price": { "net": "95.00" },
"tax": { "category": "S", "rate": "19" },
"item": { "name": "Softwarewartung", "description": "Wartung Fachverfahren, September 2026" }
},
{
"quantity": "1",
"unit": "C62",
"price": { "net": "250.00" },
"tax": { "category": "S", "rate": "19" },
"item": { "name": "Lizenz Modul Statistik" },
"allowances": [{ "amount": "25.00", "reason": "Rabatt" }]
}
]
}Conventions
| Topic | Convention |
|---|---|
| Amounts | Decimal strings such as "95.00", with at most two decimals. JSON numbers are accepted, but strings avoid floating-point surprises. |
| Quantities and prices | Up to six decimals, for example "0.125" |
| Dates | YYYY-MM-DD |
| Codes | Currencies in ISO 4217 (EUR), countries in ISO 3166-1 alpha-2 (DE), units from UN/ECE Recommendation 20 (C62 one, H87 piece, HUR hour, DAY day, KGM kilogram). unit defaults to C62. |
| Electronic addresses | id plus an EAS scheme, such as EM for an e-mail address, 0204 for a German Leitweg-ID or 9930 for a German VAT ID |
| Document type | type_code (BT-3) defaults to 380, a commercial invoice. Others include 381 credit note, 384 corrected invoice, 389 self-billed invoice, 326 partial invoice and 386 prepayment invoice. |
Required fields
Every invoice needs number, issue_date, currency, seller (with name and address), buyer (with name and address) and at least one line. Each line needs quantity, price.net, tax.category and item.name, and a tax.rate unless its category is O. MINIMUM and BASIC_WL don't transmit lines, but they still need them to compute the totals.
The profiles add their own rules. For XRECHNUNG:
buyer_reference(BT-10), the Leitweg-ID for German public buyersseller.contactwithname,phoneandemailelectronic_addressfor both the seller and the buyerpaymentwith a payment means code, for example58for a SEPA credit transfer, and the account
process_id (BT-23) defaults to urn:fdc:peppol.eu:2017:poacc:billing:01:1.0 for XRECHNUNG.
VAT categories
| Category | Meaning | Rate | Also required |
|---|---|---|---|
S | Standard rate | the rate, such as 19 | — |
Z | Zero rated goods | 0 | — |
E | Exempt from VAT | 0 | vat_exemptions.E |
AE | Reverse charge | 0 | vat_exemptions.AE, seller and buyer VAT IDs |
K | Intra-community supply | 0 | vat_exemptions.K, seller and buyer VAT IDs, delivery information |
G | Export outside the EU | 0 | vat_exemptions.G |
O | Not subject to VAT | none | vat_exemptions.O |
L | Canary Islands IGIC | the IGIC rate | — |
M | Ceuta and Melilla IPSI | the IPSI rate | — |
vat_exemptions gives the exemption reason (BT-120) and code (BT-121) for each category. This is a reverse-charge supply to a customer in another EU country:
{
"vat_exemptions": { "AE": { "reason": "Reverse charge", "reason_code": "vatex-eu-ae" } },
"charges": [{ "amount": "15.00", "tax_category": "AE", "tax_rate": "0", "reason": "Versand", "reason_code": "FC" }],
"lines": [
{ "quantity": "12", "unit": "H87", "price": { "net": "49.50" }, "tax": { "category": "AE", "rate": "0" }, "item": { "name": "Druckkopf X2" } }
]
}Document-level allowances and charges need their own tax_category and tax_rate, because they change the VAT basis of that category.
Credit notes
Set type_code to "381" and reference the original invoice in preceding_invoices. Quantities and prices stay positive, because the type code is what makes the document a credit:
{
"number": "GS-2026-0003",
"type_code": "381",
"issue_date": "2026-09-23",
"preceding_invoices": [{ "number": "R-2026-0042", "issue_date": "2026-09-23" }]
}Computed fields
Don't send line totals, the VAT breakdown or document totals. The engine computes them from the lines, allowances and charges, using the EN 16931 rules:
| Value | Business terms | Rule |
|---|---|---|
| Line net amount | BT-131 | quantity × net price ÷ base quantity + line charges − line allowances |
| VAT per category and rate | BT-116, BT-117 | basis = the lines, allowances and charges of that category and rate; tax = basis × rate ÷ 100, rounded to two decimals |
| Sum of line net amounts | BT-106 | sum of the line net amounts |
| Total without VAT | BT-109 | lines − document allowances + document charges |
| Total VAT | BT-110 | sum of the VAT amounts per category |
| Total with VAT | BT-112 | total without VAT + total VAT |
| Amount due | BT-115 | total with VAT − prepaid_amount + rounding_amount |
For the example above: 8 × 95.00 = 760.00 and 250.00 − 25.00 = 225.00, so the lines total 985.00. VAT at 19 % on 985.00 is 187.15, and 1,172.15 is due.
A line may carry its own net_amount. It isn't needed, but when present the engine checks it against the computed value and rejects the invoice if they differ.
Validation report
PDF/A and e-invoice renders carry a conformance report in result.conformance. Succeeded renders have one, and so do renders that failed validation:
{
"result": {
"conformance": {
"pdfa": {
"level": "3b",
"status": "passed",
"validator": "veraPDF 1.30.2",
"profile": "PDF/A-3B validation profile",
"errors": [],
"warnings": []
},
"einvoice": {
"profile": "XRECHNUNG",
"syntax": "cii",
"status": "passed",
"validator": "KoSIT Validator 1.6.3",
"scenario": "EN16931 XRechnung (CII)",
"errors": [],
"warnings": []
}
}
}
}A render that failed because the invoice lacks a field the profile requires carries the same object with status: "failed", and its findings point at the invoice:
{
"rule": "BR-DE-2",
"message": "The seller contact (BG-6) is required for XRECHNUNG.",
"location": "invoice.seller.contact",
"count": 1
}| Field | Description |
|---|---|
status | passed, failed or unavailable. unavailable means the validator couldn't be reached in time and the file was delivered unvalidated. |
level | PDF/A level that was checked, 2b or 3b |
profile, syntax | The e-invoice profile and syntax that were generated |
validator, scenario | The validator, its version and the rule set it applied |
errors, warnings | Findings, each with rule (such as BR-DE-15 or a PDF/A clause like 6.2.4.3-2), message, location and count |
Each list holds at most 100 findings. Findings with the same rule and location are merged, and count says how often they occurred. For problems found in the invoice model, location is a JSON path such as invoice.seller.contact.email. For problems in the generated file, it is the validator's XPath or veraPDF's object path. The dashboard shows the same report on the render's page.
Errors and warnings
A failed e-invoice render isn't billed. Synchronous requests answer 422 with the error, and the report is in the render's result:
| Code | When | Fix |
|---|---|---|
einvoice_invalid | The invoice breaks a rule of the profile before any XML is written, for example a missing BT the profile requires, a VAT category without a rate or a net_amount that doesn't add up | result.conformance.einvoice.errors lists every problem with a JSON path into the invoice |
einvoice_validation_failed | The validator rejected the generated XML | Read the rules in result.conformance.einvoice.errors; they are usually about content, such as a missing Leitweg-ID or an invalid code |
pdfa_conversion_failed | The PDF couldn't be made PDF/A conformant, or veraPDF found it non-compliant | See PDF/A; fonts that aren't embedded are the usual cause |
Warnings don't fail the render:
| Code | Meaning |
|---|---|
einvoice_validation_warning | The validator accepted the XML with warnings; the message names the rules |
einvoice_validation_unavailable | The e-invoice validator didn't answer in time, so the XML is unvalidated |
pdfa_validation_unavailable | veraPDF didn't answer in time, so the PDF/A file is unvalidated |
einvoice_skipped_plan | The template embeds an e-invoice by default, but the plan doesn't include e-invoicing, so the PDF has none |
invoice_totals_unavailable | The render embeds no e-invoice and its invoice couldn't be computed, so render.invoice is missing; the message names the first problem, such as a missing invoice in data |
See Errors for every other code.
Test mode
Test renders are free and validated like live renders, so you can build and check your integration with a test key. Test PDFs carry the TEST watermark. It is drawn as vector outlines and stamped before the PDF/A conversion, so a test PDF is conformant too and the report describes the file you receive.
An XML file can't carry a watermark, so test XML carries a note instead. This applies to POST /v1/einvoices and to the XML embedded in a test PDF, the part that systems receiving the invoice read. The note is the first invoice note (BT-22):
TESTRECHNUNG – keine gültige Rechnung. TEST INVOICE – not a valid invoice (created with a test API key).- Every profile gets the note except
MINIMUM, which has no field for it. AMINIMUMXML isn't an e-invoice on its own. EXTENDEDalso setsTestIndicatortotrue, the test flag that only its schema defines.- Apart from the marker, the XML is the one a live key produces, and the validation report is the same.
Use a live key for invoices you send.
Billed renders
| Operation | Billed renders |
|---|---|
| E-invoice layer on a PDF render | included (+0), only the PDF itself is billed |
XML export with POST /v1/einvoices | 1 |
| Test renders and failed renders | 0 |
A two-page Factur-X invoice is 1 render. E-invoicing needs the Growth plan or higher. See Plans and limits.
Related pages
- PDF options for PDF/A without an e-invoice, and for converting existing PDFs
- Renders for the render object and its
result - Template language for
format_currencyand the other filters
PDF tools
Merge, split, rotate, protect and unlock existing PDFs and read or change their metadata, from sources and page lists to delivery, encrypted files, errors and billing.
Image and screenshot options
Render PNG, JPEG and WebP images from templates, HTML or URLs, with viewport, scale, cropping, transparency and quality settings.