TrustRender
Validate and normalize billing data before Factur-X/ZUGFeRD embedding.
Quick start
pip install trustrender
Stripe and Shopify billing exports don't include the seller fields required for compliant invoices. TrustRender catches this:
$ trustrender validate examples/demo_stripe.json --source stripe
Invoice: INV-2026-0187
From:
To: Rheingold Maschinenbau GmbH
Items: 3
Total: $2,945.25
BLOCKED — 1 problem(s)
Missing vendor/sender name
Add a sender.name field to your invoice data.
This invoice cannot be processed until the problems above are fixed.
$ trustrender validate examples/demo_shopify.json --source shopify
Invoice: 1047
From:
To: Klaus Berger
Items: 3
Total: $1,309.00
BLOCKED — 1 problem(s)
Missing vendor/sender name
Add a sender.name field to your invoice data.
This invoice cannot be processed until the problems above are fixed.
Add your seller identity to the source payload and it passes:
$ trustrender validate examples/demo_stripe_ready.json --source stripe
Invoice: INV-2026-0187
From: NovaTech Solutions GmbH
To: Rheingold Maschinenbau GmbH
Items: 3
Total: $2,945.25
PASS — invoice data is valid
Safe to embed in Factur-X/ZUGFeRD PDF.
$ trustrender validate examples/demo_shopify_ready.json --source shopify
Invoice: 1047
From: Werkzeug-Kontor GmbH
To: Klaus Berger
Items: 3
Total: $1,309.00
PASS — invoice data is valid
Safe to embed in Factur-X/ZUGFeRD PDF.
The only difference between the blocked and passing files is one added field:
"sender": { "name": "NovaTech Solutions GmbH" }
Why this exists
Stripe and Shopify billing exports are missing seller fields, use platform-specific formats (cents, Unix timestamps, decimal strings), and have no concept of tax compliance. factur-x and drafthorse generate compliant Factur-X/ZUGFeRD XML, but they assume clean input. TrustRender validates and normalizes source billing data before handoff — it catches arithmetic mismatches, missing required fields, and structural problems so they don't silently produce non-compliant documents.
Install
pip install trustrender
Core install requires only drafthorse. No Typst, no browser, no heavy deps.
Optional extras:
pip install "trustrender[zugferd]" # XSD/Schematron validation
pip install "trustrender[render]" # PDF rendering via Typst
pip install "trustrender[all]" # everything
Requires Python 3.11+.
Python API
from trustrender import validate_invoice
from trustrender.adapters import from_stripe
# Raw Stripe API response → canonical invoice (structural validation)
result = validate_invoice(from_stripe(raw_stripe_response))
if result["render_ready"]:
canonical = result["canonical"]
# canonical is now ready for either:
# - your own template renderer (most users)
# - the to_zugferd_data() bridge → drafthorse / factur-x (see end-to-end example below)
else:
for error in result["errors"]:
print(f"BLOCKED: {error['message']}")
validate_invoice() returns:
status: "ready" | "ready_with_warnings" | "blocked"render_ready: boolcanonical: normalized invoice dict (all fields in canonical names)errors: list of blocking issues with rule_id, path, expected/actualwarnings: advisory issuesnormalizations: field-level provenance (what was renamed, coerced, computed)
The zugferd=True flag is also accepted but only useful if your input is already in ZUGFeRD shape (with seller/buyer/tax_entries/payment keys). For Stripe and Shopify, use the to_zugferd_data() bridge after validate_invoice() instead — see the end-to-end example below.
Also works with messy data from any source:
result = validate_invoice({
"invoiceNo": "INV-001",
"vendor": {"companyName": "Acme Corp"},
"customer": {"Name": "Client Inc"},
"LineItems": [{"desc": "Widget", "qty": 2, "unitPrice": 50, "amount": 100}],
"SubTotal": 100,
"tax": 8.50,
"TotalAmt": 108.50,
}, zugferd=True)
Stripe adapter
Raw Stripe Invoice API responses use cents, Unix timestamps, and nested structures. The adapter handles all of it:
trustrender validate stripe_invoice.json --source stripe --zugferd
from trustrender.adapters import from_stripe
result = validate_invoice(from_stripe(raw_stripe_response), zugferd=True)
Converts cents to dollars, timestamps to dates, extracts line items from lines.data[], maps customer fields to recipient. If you enrich the source payload with sender, vendor, or seller, the adapter passes it through.
Shopify adapter
Shopify orders use decimal strings, split customer names, and a different structure from invoices:
trustrender validate shopify_order.json --source shopify
from trustrender.adapters import from_shopify
result = validate_invoice(from_shopify(raw_shopify_order))
Parses string amounts to floats, combines first_name + last_name, maps order fields to invoice structure, preserves structured address fields. Shopify orders have no seller info or due date — TrustRender flags the missing seller and handles the absent due date correctly.
End-to-end example: Stripe → real Factur-X PDF
examples/with_drafthorse_facturx.py runs the full pipeline:
from trustrender import validate_invoice
from trustrender.adapters import from_stripe
from trustrender.zugferd import to_zugferd_data, build_invoice_xml, apply_zugferd
# 1. Adapt + validate the Stripe payload (canonical structure check)
result = validate_invoice(from_stripe(stripe_invoice))
# 2. Bridge canonical → ZUGFeRD shape with the three things Stripe never includes:
# seller VAT/address, payment IBAN, applicable tax rate
zugferd_data = to_zugferd_data(
result["canonical"],
seller={"name": "...", "address": "...", "city": "...",
"postal_code": "...", "country": "DE", "vat_id": "DE..."},
payment={"means": "credit_transfer", "iban": "DE..."},
tax_rate=19,
)
# Real EN 16931 contract validation runs here — catches anything XML build would reject.
# 3. drafthorse builds UN/CEFACT CII XML
xml_bytes = build_invoice_xml(zugferd_data)
# 4. factur-x embeds the XML into a PDF/A-3b container
factur_x_pdf = apply_zugferd(your_visual_pdf_bytes, xml_bytes, lang="de")
Output: a 15 KB Factur-X invoice PDF that validates against the EN 16931 XSD (sample committed at examples/invoice_facturx.pdf).
$ python examples/with_drafthorse_facturx.py
Step 1: Adapt + validate via TrustRender (canonical structure) → status=ready
Step 2: Bridge canonical → ZUGFeRD shape (one call) → passes EN 16931 contract validation
Step 3: Build CII XML via drafthorse → 8,312 bytes of CII XML
Step 4: Render visual PDF (your template) → 1,599 bytes
Step 5: Embed CII XML as PDF/A-3b → 15,004 bytes
Step 6: Verify with factur-x library → ✓ passes EN 16931 XSD
✓ factur-x.xml is embedded in the PDF
What's TrustRender vs what's you: the seller VAT, payment IBAN, and tax rate come from your billing setup — Stripe's API doesn't include them, and TrustRender doesn't invent them. Everything else is automatic.
What it normalizes
90+ vendor field aliases across QuickBooks, Xero, Stripe, and generic CSV/ERP formats:
| Source field | Canonical field |
|---|---|
DocNumber, invoiceNo, inv_no, ref |
invoice_number |
CompanyName, account_name, bill_from_name |
sender.name |
customer, billTo, Contact |
recipient |
Line, LineItems, entries, products |
items |
UnitPrice, cost, rate, unitCost |
unit_price |
Amount, LineAmount, line_total |
line_total |
SubTotal, net_total, sub_total |
subtotal |
TotalAmt, grand_total, amount_due |
total |
Plus: type coercion ("$1,234.56" → 1234.56), date parsing ("April 10, 2026" → 2026-04-10), computed defaults (missing line_total = qty × price), near-match typo detection (invioce_number → suggests invoice_number).
What it checks
7 deterministic semantic checks, all arithmetic:
| Check | What it catches |
|---|---|
identity.invoice_number |
Missing or empty invoice number |
identity.sender_name |
Missing vendor/sender name |
identity.recipient_name |
Missing recipient/buyer name |
items.non_empty |
No line items |
arithmetic.line_total |
line_total ≠ qty × unit_price |
arithmetic.subtotal |
subtotal ≠ sum of line_totals |
arithmetic.total |
total ≠ subtotal + tax_amount |
No AI. No heuristics. Every check is deterministic and objectively verifiable.
CLI
trustrender validate <data.json> [--zugferd] [--format text|json]
trustrender ingest <data.json> [-o canonical.json]
trustrender render <template> <data.json> -o <output.pdf> [--zugferd en16931]
trustrender preflight <template> <data.json> [--zugferd en16931]
trustrender serve --templates <dir> [--port 8190]
trustrender doctor [--smoke]
EN 16931 e-invoicing (narrow scope)
Catches structural and contract-level EN 16931 issues before drafthorse builds the XML. Currently supports:
- Domestic German B2B invoices with standard VAT, EUR, SEPA payment
- Single VAT rate per invoice (mixed rates not yet supported in the bridge)
- Invoice type 380 and credit note 381
- PDF/A-3b with embedded CII XML via
apply_zugferd()(wrapsfactur-x.attach_xml)
Not supported, fails loudly: reverse charge, cross-border, allowances/charges, non-EUR currencies, non-DE seller country.
What still slips past TrustRender: things only the official Schematron rules catch (cross-field business rules, value range checks). Run xml_check_xsd and xml_check_schematron on the produced XML for full compliance certainty.
See docs/einvoice-scope.md for the full scope matrix.
Also: deterministic PDF rendering
TrustRender ships a Jinja2 + Typst render engine alongside the validation layer. Used internally to produce the visual PDF for Factur-X embedding, and usable standalone if you want one Python package that goes from data → validated canonical → final PDF without bringing in a headless browser.
- 1,000-line invoice rendered in ~59 ms median (~72 ms with content varying every render; Apple M5 Pro, single core, v0.3.5, 2026-08-29)
- 1,011-page invoice rendered in 37 seconds
- No headless browser, no Chromium binary, no Java runtime — pure Python + the Typst CLI
- SHA-256 fingerprinting of input, template, and output for audit trails (see
trustrender audit) - Drift detection vs saved baselines for visual regression in CI
- Subprocess-based backend with real timeouts (killable renders, not just abandoned)
- Bundled fonts, six built-in templates (invoice, einvoice, statement, letter, receipt, report)
pip install "trustrender[render]"
trustrender render invoice.j2.typ data.json -o invoice.pdf --zugferd en16931
render, preflight, audit, baseline, and serve are all part of the rendering surface — see trustrender --help for the full set.
What this is not
- Not a full AP automation platform
- Not an e-invoice compliance certification (run Schematron on the output XML for that)
- Not an AI-powered data fixer — all normalizations and validations are deterministic
- Not a replacement for factur-x or drafthorse — it validates billing data before them and wraps their entry points for convenience
- Not a billing platform replacement — it expects you already collect payment via Stripe, Shopify, or something else
Development
pip install -e ".[dev]"
trustrender doctor --smoke
pytest
License
MIT
Metadata
Release files for trustrender 0.3.5
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| trustrender-0.3.5.tar.gz | 1.8 MB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| trustrender-0.3.5-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 3.6 MB
Release files / trustrender-0.3.5.tar.gz
| Download URL | trustrender-0.3.5.tar.gz |
|---|---|
| Size | 1.8 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
34521f69f71a45cdbd6bf8df9c52fff6f86a9a10d819b27345ef1aaadf6ae1a2
|
|
BLAKE2b-256 checksum How to use checksums |
0a6ce17e7317105f5384041c5d9e7002db8cee18c323e9a785fe6c6f6f859fdf
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.
Transparency logRelease files / trustrender-0.3.5-py3-none-any.whl
| Download URL | trustrender-0.3.5-py3-none-any.whl |
|---|---|
| Size | 1.8 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f6c0a9b34d1c9b67bfce5a575488e1520225ac078cceb4e06835982f10badf6f
|
|
BLAKE2b-256 checksum How to use checksums |
e911b8f2c36ed268af8a746fe3a531330f7491cb6c2788e8d9d2938ca11ba537
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Aug 30, 2026.
Transparency log