Parse and create ZUGFerD files.
Project description
getafix
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.
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 —
getafixattaches the embeddedfactur-x.xmlbut 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
getafixmeet all legal and regulatory requirements applicable in their jurisdiction.getafixdoes 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.
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.
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), 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); contactTypeCode(BT-X-315…BT-X-575) on everyDefinedTradeContact(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); onLineTradeDelivery: the actual delivery event / date (BT-X-85-000), despatch (BG-X-13) and receiving (BG-X-82) advice; onLineTradeSettlement: the preceding-invoice reference (BG-X-48); line-note codes (BT-X-9/BT-X-10). (The line-level delivery-noteBG-X-83and additional-documentBG-X-3twins are modelled.) - Line monetary totals —
AllowanceTotalAmount/ChargeTotalAmount/TaxTotalAmount/GrandTotalAmount(BT-X-327…BT-X-330); the line total and total-allowance-charge are modelled. - Referenced-document leaves —
FormattedIssueDateTimeon the header additional / contract references (BT-X-33-00/BT-X-148-00/BT-X-149-00); preceding-invoiceTypeCode(BT-X-555); accounting referenceTypeCode(BT-X-99/BT-X-290). - Other shared leaves — item characteristic
TypeCode/ValueMeasure(BT-X-11/BT-X-12), invoicing-periodDescription(BT-X-264), allowance/chargeSequenceNumeric/BasisQuantity(BT-X-265/BT-X-266), per-line product localID(BT-X-305), net-priceIncludedTradeTax(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:
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file getafix-0.1.0.tar.gz.
File metadata
- Download URL: getafix-0.1.0.tar.gz
- Upload date:
- Size: 114.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0a0c18f13611d16dc03735f0fcbc8d8014682281ee16103c61544cfa94093023
|
|
| MD5 |
80634075cc30d81980621b363d17de1f
|
|
| BLAKE2b-256 |
9ca1b8c7af5bce61d3ea7a0daba84e19a713537536cafaa272a2e6a64c75c0a7
|
File details
Details for the file getafix-0.1.0-py3-none-any.whl.
File metadata
- Download URL: getafix-0.1.0-py3-none-any.whl
- Upload date:
- Size: 130.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: uv/0.11.24 {"installer":{"name":"uv","version":"0.11.24","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3b9c7b76831e8f7f4130d1c2c7a07d7d366341fffcc9167e27be32de6cd00083
|
|
| MD5 |
8a27f87e5b901a731ac953cfa5b55420
|
|
| BLAKE2b-256 |
6023498f26613b7f38e2aae822d32a854301e76de19d21237f5337104e9840cf
|