The document layer your ERP will need eventually
Whether a custom build, a SAP extension or an Odoo module: at some point ZUGFeRD, PDF/A archiving, batch printing and signatures land on your plate. Instead of orchestrating three libraries, Dokmatiq delivers the full document layer as a REST API.
For: Developers and integrators working on ERP, inventory and accounting systems
Who this is for
Developers building or extending ERP, inventory or accounting systems — whether:
- Custom builds on a Java/C#/PHP stack
- SAP extensions (ABAP, SAP BTP, CAP)
- Odoo, Xentral, ERPNext, Akeneo modules
- Integration layers via middleware (MuleSoft, webMethods, Dell Boomi)
The typical trigger: 2025 brings the e-invoicing receiving obligation, 2027 the sending obligation — and your system currently only prints PDFs via Crystal Reports or JasperReports. The gap becomes visible.
Typical pain points
- PDF rendering with your own engine (JasperReports, BIRT, iText) is documented but slow to evolve — corporate fonts, new format variants and signatures are a ticket every time
- Producing ZUGFeRD/XRechnung takes weeks with Mustang or iText — and becomes a topic again with every schema update
- GoBD-compliant archiving requires PDF/A plus timestamping — usually not native to the ERP
- Month-end batch printing hits single-threaded limits (50,000 invoices in one night)
- Cross-country variants (Factur-X for France, ebInterface for Austria) multiply maintenance effort
Four typical scenarios
1. Batch invoice output
Month-end: 20,000 invoices overnight, both as ZUGFeRD PDFs and pure XRechnung XML to the customer portal:
from dokmatiq import Client
from concurrent.futures import ThreadPoolExecutor
client = Client(api_key=os.environ["DOKMATIQ_KEY"])
def render_invoice(row):
pdf = client.einvoice.zugferd(
profile="EN16931",
invoice=row.to_invoice_dict(),
output_profile="PDF/A-3b",
idempotency_key=f"invoice-{row.id}"
)
xml = client.einvoice.xrechnung(
syntax="UBL",
invoice=row.to_invoice_dict(),
idempotency_key=f"xrechnung-{row.id}"
)
return row.id, pdf, xml
with ThreadPoolExecutor(max_workers=50) as pool:
for row_id, pdf, xml in pool.map(render_invoice, invoice_rows):
save_to_archive(row_id, pdf)
enqueue_for_peppol(row_id, xml)
The API is stateless and horizontally scalable. 50 parallel workers are a realistic starting point; for 50,000+ invoices per night a dedicated rate-limit bucket pays off.
2. Add ZUGFeRD to existing PDF invoices
The ERP already produces invoice PDFs via JasperReports. For the new mandate, the XML should be embedded retroactively:
curl -X POST https://api.dokmatiq.com/v1/einvoice/embed-cii \
-H "Authorization: Bearer $DOKMATIQ_KEY" \
-F "document=@invoice.pdf" \
-F 'invoice={"id":"2026-0042","issueDate":"2026-04-18","seller":{...},"buyer":{...},"lines":[...]}' \
-F "profile=EN16931" \
-o invoice-zugferd.pdf
The existing visual layout stays intact; the API embeds the CII XML as a PDF/A-3 attachment and, on request, converts the PDF to PDF/A-3b. No intervention in the reporting engine.
3. GoBD-compliant archive pipeline
For every created document a GoBD-grade version is produced in parallel to DMS storage — PDF/A-3 plus a qualified timestamp:
pdf_a3 = client.pdf.convert(
document=source_pdf,
target_profile="PDF/A-3b"
)
timestamped = client.pdf.timestamp(
document=pdf_a3,
tsa="qualified"
)
dms.archive(timestamped, metadata={"invoiceId": row.id, "retentionUntil": "2037-12-31"})
The qualified timestamp (see timestamp glossary) is eIDAS-compliant and satisfies the GoBD requirement for provably unchanged retention.
4. Cross-country invoicing
A German company with French subsidiaries needs three formats in parallel:
- German B2B invoice: ZUGFeRD 2.x in profile EN 16931
- French B2B invoice from 09/2026: Factur-X + Peppol transport
- Austrian B2G: ebInterface 6.1 via USP
One API instead of three libraries:
client.einvoice.zugferd(profile="EN16931", ...) # DE
client.einvoice.factur_x(profile="EN16931", ...) # FR
client.einvoice.ebinterface(version="6.1", ...) # AT
All three endpoints speak the same EN 16931 data model internally — the ERP provides invoice data once; the rest is a format choice.
Features most relevant
- E-invoices — ZUGFeRD, XRechnung, Factur-X, ebInterface, Peppol BIS from one API
- Document generation — when your own report engine becomes too rigid
- Excel generation — for analyses, DATEV exports, lists
- PDF tools — merge, split, PDF/A conversion, form filling
- Digital signatures — PAdES via API, qualified certificates
Compliance context
For German ERPs at minimum these milestones matter:
- E-Invoicing Mandate Germany 2025 — receiving is mandatory
- E-Invoicing Mandate Germany 2027 — sending from €800,000 revenue
- E-Invoicing Mandate France 2026 — Y-model, PDPs, Factur-X
The API covers all these milestones technically — the organisational rollout stays with the ERP team.
Performance profile
| Scenario | Throughput per node | Note |
|---|---|---|
| ZUGFeRD generation (simple invoice) | ~40 docs/s | CPU-bound for Schematron validation |
| Word to PDF/A-3 (10 pages) | ~8 docs/s | OpenXML parsing is hungrier |
| PDF/A conversion (20-page PDF) | ~12 docs/s | font embedding dominates |
| PAdES signature with TSA round-trip | ~5 docs/s | TSA round-trip is the limiter |
All numbers scale horizontally — more parallelism yields more throughput. For batch runs > 10k documents a dedicated rate-limit bucket (Enterprise plan) makes sense.
Deployment options
- Managed API (default) — hosted in Germany, GDPR, ISO 27001
- Self-hosted via Docker container — for scenarios with regulatory constraints (healthcare, public sector)
- Hybrid: managed control plane, on-prem document rendering
Self-hosting is explicitly supported — no hidden lock-in.
When Dokmatiq is not the right fit
- If you already master JasperReports and have no e-invoicing mandate — switching is unnecessary
- For very niche industry formats without an EN 16931 mapping (some EDI dialects) — custom development required
- Purely internal form generation without stationery — a simpler library will do
Common pitfalls
- Too-conservative batch sizes — the API scales; 50 parallel requests are a normal starting scenario
- TSA timeouts on signing — the external time-stamping authority may take 1–3 s; size client timeouts accordingly
- PDF/A conversion on existing PDFs — un-embedded fonts frequently cause issues; the API reports them precisely
- Leitweg-ID missing from ERP master data — must be maintained per public-sector recipient; a mandatory field on the customer record prevents surprises
Next up
Pick the entry point that fits your role — or explore the other personas to see what else is relevant.