Skip to main content

getafix

CI PyPi version PyPI Downloads PyPi license Ruff uv

Build, parse and validate ZUGFeRD 2.x / Factur-X Cross-Industry-Invoice (CII) XML in Python. getafix provides a fully typed dataclass model for creating and parsed invoices - every field traceable to the EN 16931 business terms (BT-…) and groups (BG-…). Validation follows a broad, but selective set of business rules (BR-…). An accompanied cli tool getafix renders and validaes invoices in the terminal.

minimum invoice

Why "getafix"?

"get a fix" — get a Factur-X

The project's working title was carthorse - in the spirit of drafthorse, pycheval, mustang, ... - but when I learned, that is already taken, I wanted to break free from the horse naming.

Getafix is the village druid in Asterix — the one that brews the magic potion. I only knew the german name Miraculix and was hooked. The Gaulish -ix ending echoes the -X in Factur-X, and Factur-X is the French half of the standard: so the French druid it is!

Status: pre-1.0. Solid for the fields that are modelled across MINIMUM / BASIC_WL / BASIC / COMFORT (EN 16931); broad EXTENDED coverage. PDF/A-3 conformance for the host PDF is out of scope — getafix attaches the embedded factur-x.xml but does not upgrade the surrounding PDF to PDF/A-3.

License and attribution

getafix is distributed under the Apache License 2.0 — see LICENSE.

This project is an application of the ZUGFeRD / Factur-X publication issued by the Forum elektronische Rechnung Deutschland (FeRD) at AWV e.V. The format incorporates EN 16931, reproduced by FeRD with the permission of CEN and DIN. ZUGFeRD and Factur-X are trademarks of FeRD / AWV e.V., used here only to identify the standard this library implements.

The vendored test fixtures under tests/schemas/ and tests/samples/ come from third parties and are redistributed under the Apache License 2.0:

  • FeRD / AWV e.V. — XML schemas, Schematron, and the *_zf24_* example invoices from the official ZUGFeRD 2.4 / Factur-X 1.08 distribution.
  • ZUGFeRD/mustangproject — additional reference CII invoices from the Java implementation's test resources.
  • ZUGFeRD/corpus — community-curated XML-Rechnung/CII/ reference invoices.

Original copyright remains with the respective upstream holders; per-file provenance is tracked in tests/samples/SOURCES.md.

Important: It is the user's responsibility to ensure that invoices generated or parsed with getafix meet all legal and regulatory requirements applicable in their jurisdiction. getafix does not guarantee compliance with any specific national or sector-specific e-invoicing mandate.

Installation

Requires Python 3.12+. We recommend uv for dependency management.

pip install getafix

The base install lets you build / serialise / validate documents with the Python stdlib XML parser. The optional extras unlock more:

Extra Pulls in Enables
getafix[lxml] lxml Round-tripping XML produced by other tools (the stdlib parser is fine for most documents; lxml is faster and more tolerant of large / namespaced inputs).
getafix[pdf] pypdf Embedding / extracting factur-x.xml in a PDF (getafix.pdf.attach_xml and extract_xml).
getafix[rich] rich Pretty-print an invoice using the rich library.
getafix[cli] lxml, rich, pypdf The getafix console script — pretty-print an invoice and run the BR-* validators against it.

Install several at once:

pip install 'getafix[lxml,pdf]'

Quickstart — build an invoice

from datetime import date
from decimal import Decimal

from getafix.schema.document import (
    Context, Document, GuidelineDocument, Header
)
from getafix.schema.accounting import MonetarySummation, TaxTotal
from getafix.schema.agreement import TradeAgreement
from getafix.schema.delivery import TradeDelivery
from getafix.schema.party import (
    BuyerTradeParty, PostalTradeAddressExtended, SellerTradeParty,
    SpecifiedTaxRegistration, TaxSchemeId,
)
from getafix.schema.settlement import TradeSettlement
from getafix.schema.trade import Trade
from getafix.schema.types import Country, Currency, Profile, TypeCode

doc = Document(
    context=Context(guideline=GuidelineDocument(id=Profile.MINIMUM)),
    header=Header(
        id="INV-2025-0001",
        type_code=TypeCode.T_CommercialInvoice,  # 380
        issue_date=date(2025, 11, 16),
    ),
    trade=Trade(
        agreement=TradeAgreement(
            seller=SellerTradeParty(
                name="Acme GmbH",
                address=PostalTradeAddressExtended(country_id=Country.DE),
                tax_registrations=[
                    SpecifiedTaxRegistration(
                        id=TaxSchemeId(id="DE123456789", scheme_id="VA"),
                    ),
                ],
            ),
            buyer=BuyerTradeParty(
                name="Beta AG",
                address=PostalTradeAddressExtended(country_id=Country.DE),
            ),
        ),
        delivery=TradeDelivery(),
        settlement=TradeSettlement(
            currency_code=Currency.EUR,
            monetary_summation=MonetarySummation(
                tax_basis_total=Decimal("100.00"),
                tax_total=[TaxTotal(amount=Decimal("19.00"), currency_id=Currency.EUR)],
                grand_total=Decimal("119.00"),
                due_amount=Decimal("119.00"),
            ),
        ),
    ),
)

xml = doc.to_xml().render(indent=True)   # str — ready to write to factur-x.xml
doc.validate()                           # raises ValidationErrors on BR-* failures

doc.to_xml() picks the right profile from Context.guideline.id. Setting a field that requires a higher profile than the document raises ProfileMismatch at render time.

Quickstart — parse an invoice

From XML bytes or a file:

import xml.etree.ElementTree as ET
from getafix.schema.document import Document

tree = ET.parse("factur-x.xml")
doc = Document.from_xml(tree.getroot())

print(doc.header.id, doc.header.type_code, doc.header.issue_date)
print(doc.trade.settlement.monetary_summation.grand_total)

doc.validate()   # raises ValidationErrors with every violation

lxml.etree works the same way — pass the root element to Document.from_xml.

From a Factur-X / ZUGFeRD PDF (getafix[pdf] extra):

import xml.etree.ElementTree as ET
from getafix.pdf import extract_xml
from getafix.schema.document import Document

payload = extract_xml(Path("invoice.pdf"))   # bytes or None
if payload is None:
    raise SystemExit("No factur-x.xml found in the PDF")
doc = Document.from_xml(ET.fromstring(payload))

To embed an XML into an existing PDF:

from pathlib import Path
from getafix.pdf import attach_xml

attach_xml(Path("invoice.pdf"), Path("factur-x.xml"))   # in-place
attach_xml(Path("invoice.pdf"), Path("factur-x.xml"),
           pdf_out=Path("invoice-with-xml.pdf"))

attach_xml produces a valid PDF with a generic embedded file; it does not upgrade the host PDF to PDF/A-3, which is the formal Factur-X compliance requirement. Pair with a dedicated PDF/A-3 converter for full conformance.

Validation

from getafix.schema.element import ValidationErrors

try:
    doc.validate()
except ValidationErrors as exc:
    for err in exc.errors:
        print(f"{err.code}: {err.message}")

Document.validate() walks the document tree once and collects every business-rule violation, raising a single ValidationErrors aggregate. Each ValidationError carries the rule's code (e.g. BR-CO-15) and a human-readable message.

Plausibility checks are reported as ValidationWarning (a ValidationError subclass). They never raise: validate() returns them when the document is otherwise valid, and the CLI prints them without failing. Rule codes starting with GETAFIX- are getafix's own checks, not part of Factur-X — e.g. GETAFIX-ADV-PREPAID (advance payments must not exceed BT-113) the GETAFIX-ADV-VAT-PLAUSIBLE warning (an advance payment's VAT must fit its amount and rate), and the GETAFIX-LINE-* warnings (a line's net amount, allowance / charge totals, VAT and gross total must follow from its quantity, price, allowances, charges and VAT rate).

Every rule getafix enforces lives in getafix.rules — one module per schema topic (accounting, line, party, settlement, trade, extended), each wired onto the relevant element's _validators.

Command-line tool

The getafix[cli] extra ships a console script that pretty-prints an invoice and runs the validators:

> getafix path/to/factur-x.xml
> getafix path/to/invoice.pdf            # reads the embedded XML
> getafix --no-validate path/to/file.xml # skip BR-* checks

Exit codes:

  • 0 — parsed cleanly and passed every validator.
  • 1 — parsed but at least one validation rule fired (or the document could not be parsed as a CII invoice, or no Factur-X XML was found in the supplied PDF).
  • 2 — usage / IO / missing dependency error.

comfort invoice

Profiles

ZUGFeRD / Factur-X defines five conformance profiles, ordered by completeness:

Profile URN Carries line items
MINIMUM urn:factur-x.eu:1p0:minimum ✗ (header totals only).
BASIC_WL urn:factur-x.eu:1p0:basicwl ✗ (basic, without lines)
BASIC urn:cen.eu:en16931:2017#compliant#urn:factur-x.eu:1p0:basic ✓
COMFORT urn:cen.eu:en16931:2017 (a.k.a. EN 16931) ✓
EXTENDED urn:cen.eu:en16931:2017#conformant#urn:factur-x.eu:1p0:extended ✓ + sub-lines

The profile is set on the document via Context(guideline=GuidelineDocument(id=Profile.X)). Getafix enforces it at render time: setting a field that only exists at a higher profile raises ProfileMismatch.

Status and known gaps

Getafix models every field that the MINIMUM, BASIC_WL, BASIC and EN 16931 (COMFORT) profiles permit. EXTENDED coverage is broad — sub-line hierarchy (BT-X-7 / BT-X-8 / BT-X-304), bundle composition (BG-X-1 IncludedReferencedProduct), per-instance batch / serial details (BG-X-84), logistics service charges (BG-X-42), advance payments (BG-X-45 with BG-X-46 / BG-X-85), the EXTENDED-only deviating parties (sales agent, buyer agent, buyer / item-level seller / tax representative, invoicer, invoicee, payer, product end-user), the penalty / discount payment-term schedules (BG-X-43 / BG-X-44), tax-currency exchange (BG-X-41), line monetary totals (BT-X-327…BT-X-330, BT-X-590), delivery terms (BG-X-22), quotation / ultimate-customer-order references, and the PEPPOL-flavoured rule overlay (BR-FXEXT-*) are all modelled.

Every shipped sample re-renders 1:1 with its source XML (tests/test_roundtrip_fidelity.py) — getafix never silently drops a field it claims to model.

The remaining EXTENDED gaps are optional leaf attributes and line-level twins the shipped samples don't exercise. Each one should be easy to add: declare the field() gated at Profile.EXTENDED, ensure XSD-sequence order - test will fail otherwise; getafix will accept a PR, or pick one up when there is a need for it:

  • Party extras — RoleCode (BT-X-483…BT-X-575) on every trade party; additional legal info (Description) on the Buyer (BT-X-334) and the line-level item seller (BT-X-571); contact TypeCode (BT-X-315…BT-X-575) on every DefinedTradeContact (BG-6 / BG-9).
  • Line-level twins of header references — on LineTradeAgreement: delivery terms (BG-X-87), contract (BG-X-2), seller order (BG-X-81), ultimate-customer order (BG-X-5); on LineTradeDelivery: the actual delivery event / date (BT-X-85-000), despatch (BG-X-13) and receiving (BG-X-82) advice; on LineTradeSettlement: the preceding-invoice reference (BG-X-48); line-note codes (BT-X-9 / BT-X-10). (The line-level delivery-note BG-X-83 and additional-document BG-X-3 twins are modelled.)
  • Referenced-document leaves — FormattedIssueDateTime on the header additional / contract references (BT-X-33-00 / BT-X-148-00 / BT-X-149-00); preceding-invoice TypeCode (BT-X-555); accounting reference TypeCode (BT-X-99 / BT-X-290).
  • Other shared leaves — item characteristic TypeCode / ValueMeasure (BT-X-11 / BT-X-12), invoicing-period Description (BT-X-264), allowance/charge SequenceNumeric / BasisQuantity (BT-X-265 / BT-X-266), per-line product local ID (BT-X-305), net-price IncludedTradeTax (BG-X-4, B2C VAT in the unit price).

PDF/A-3 packaging is out of scope; use factur-x, Mustangproject or a dedicated converter for full Factur-X PDF conformance.

Contributing

See CONTRIBUTING.md for the developer guide — module layout, how the dataclass model works, how to add a new BT field or BR validator, and the test / lint workflow.

References

Specification and validators:

Metadata

Release files for getafix 0.5.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for getafix 0.5.0
File Size Uploaded Provenance Actions
getafix-0.5.0.tar.gz 120.3 kB Without provenance Details for getafix-0.5.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for getafix 0.5.0
File Interpreter ABI Platform Actions
getafix-0.5.0-py3-none-any.whl Python 3 none any Details for getafix-0.5.0-py3-none-any.whl

Total release size: 256.5 kB

Release files / getafix-0.5.0.tar.gz

Download URL getafix-0.5.0.tar.gz
Size 120.3 kB
Tags Source
SHA-256 checksum
How to use checksums
147e72eeb8351f0fe40f462124404bbbd716c33ac66fd6b17c6cac500a0cf5c2
BLAKE2b-256 checksum
How to use checksums
169b527886479ebc92f34676a1a61760164759127c11023f2f144407d6a8298a
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / getafix-0.5.0-py3-none-any.whl

Download URL getafix-0.5.0-py3-none-any.whl
Size 136.2 kB
Tags Python 3
SHA-256 checksum
How to use checksums
9c381d215074deac0fdb9055e310b5ba605bb8b05e2a3e385f124f5b67dc509a
BLAKE2b-256 checksum
How to use checksums
cb157a2826410080e7bfab6155c4b6c0c8f87d49dfff1a1bf49179552834f975
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.7 {"installer":{"name":"uv","version":"0.13.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"26.04","id":"resolute","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for getafix 0.5.0

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.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page