Dokmatiq DOKMATIQ

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

  1. 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
  2. Producing ZUGFeRD/XRechnung takes weeks with Mustang or iText — and becomes a topic again with every schema update
  3. GoBD-compliant archiving requires PDF/A plus timestamping — usually not native to the ERP
  4. Month-end batch printing hits single-threaded limits (50,000 invoices in one night)
  5. 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

Compliance context

For German ERPs at minimum these milestones matter:

The API covers all these milestones technically — the organisational rollout stays with the ERP team.

Performance profile

ScenarioThroughput per nodeNote
ZUGFeRD generation (simple invoice)~40 docs/sCPU-bound for Schematron validation
Word to PDF/A-3 (10 pages)~8 docs/sOpenXML parsing is hungrier
PDF/A conversion (20-page PDF)~12 docs/sfont embedding dominates
PAdES signature with TSA round-trip~5 docs/sTSA 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

  1. Too-conservative batch sizes — the API scales; 50 parallel requests are a normal starting scenario
  2. TSA timeouts on signing — the external time-stamping authority may take 1–3 s; size client timeouts accordingly
  3. PDF/A conversion on existing PDFs — un-embedded fonts frequently cause issues; the API reports them precisely
  4. 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.