Skip to main content

📒 pyxaf

CI PyPI Python License: MIT

Read and validate every version of the Dutch Auditfile Financieel in plain Python.

The Auditfile Financieel is the standard export of a general ledger that Dutch accounting software produces for tax inspectors and auditors. pyxaf opens all seven versions of it, from the fixed-width files of 1999 to XAF 4.0, gives you one clean, typed model to work with, and tells you exactly what is wrong with a file instead of choking on it. No runtime dependencies, no upload to anyone's server, no gigabytes of RAM.

import pyxaf

with pyxaf.open("2024.xaf") as af:
    print(af.version, af.company.name)  # 4.0 Voorbeeld & Zonen B.V.

🧭 Table of contents

✨ Why pyxaf

Real auditfiles are messy. Official test files break the official rules, vendors invent their own namespaces, and an "ISO-8859-1" file turns out to be Windows-1252. pyxaf was built for those files.

  • All versions, one model. XAF 4.0, 3.2.1, 3.2, 3.1, 3.0, CLAIR2 and the fixed-width ADF all map onto the same classes: Header, LedgerAccount, Transaction, Line and friends. The raw layer keeps every element exactly as written, vendor extensions included.
  • Lenient reading, honest reporting. pyxaf never refuses a file because of bad data. Every problem becomes a graded finding with a stable code, such as XAF5010 ERROR, so nothing is silently guessed or dropped.
  • A validator on your own machine. The official validation service is for subscribers and stops at 5 MB. pyxaf checks files of any size: structure, references, control totals, balance, uniqueness, data quality and RGS codes, including the official XAF 4.0 rules [0001]–[0010].
  • Exact money. Amounts are decimal.Decimal, never float, with the original text kept next to every parsed value.
  • Streaming. Master data is read when the file opens; transactions and lines are streamed, so memory stays flat even for files of several gigabytes.
  • Lightweight. The core uses only the standard library and is fully typed. pandas, polars, Arrow, lxml and the command line are optional extras that are only imported when you use them.
  • Safe with untrusted files. DOCTYPE and ENTITY declarations are refused (no "billion laughs", no XXE), nesting, text and decompression sizes are capped, and there is no "recover" mode that could quietly lose data.

📦 Installation

pyxaf needs Python 3.11 or newer.

pip install pyxaf

The core has no dependencies. Add an extra for each optional feature you need:

Extra Adds Install
cli the pyxaf command line pip install "pyxaf[cli]"
xsd validation against the official XSDs (lxml) pip install "pyxaf[xsd]"
polars to_polars(), without pyarrow pip install "pyxaf[polars]"
pandas to_pandas() with Arrow-backed types pip install "pyxaf[pandas]"
arrow Arrow streams for DuckDB, pyarrow and friends (nanoarrow) pip install "pyxaf[arrow]"
parquet Parquet export pip install "pyxaf[parquet]"
all everything above pip install "pyxaf[all]"

Using uv? uv add pyxaf works the same way, for example uv add "pyxaf[cli,polars]".

🚀 Quickstart

Grab an auditfile exported from your accounting software. The examples use 2024.xaf, but any version works, and so do .gz and .zip files.

1. Open the file

import pyxaf

af = pyxaf.open("2024.xaf")
print(af.version, af.company.name, af.header.fiscal_year)
print(len(af.accounts), "ledger accounts")
4.0 Voorbeeld & Zonen B.V. 2024
11 ledger accounts

pyxaf detected the version and encoding, and read the master data: company, ledger accounts, customers and suppliers, VAT codes and periods.

2. Walk through the transactions

Transactions and their lines are streamed straight from the file, one at a time:

for tx in af.transactions():
    print(tx.journal_id, tx.number, tx.date, tx.description, tx.balanced)
    for line in tx.lines:
        print("   ", line.account_id, line.side, line.amount)
MEM 1 2024-01-05 Boeking 1 True
    1300 D 3612.16
    8000 C 2985.26
    1800 C 626.90
...

The opening balance gets the same treatment, whichever way the file stores it:

print(af.opening_balance().by_account())  # signed: debit positive, credit negative
af.close()
{'1100': Decimal('10000.00'), '0100': Decimal('2500.50'), '0500': Decimal('-12500.50')}

3. Validate it

report = pyxaf.validate("2024.xaf")
print(report.ok)
for finding in report.errors:
    print(finding.code, finding.line, finding.message)

A clean file prints True. A file in which someone typed 3621.16 instead of 3612.16 prints:

False
XAF5007 None transactions totalDebit 38260.42 ≠ sum of debit lines 38269.42
XAF5010 223 transaction '1' in journal 'MEM' does not balance: debit 3621.16, credit 3612.16

Every finding has a stable code, a severity (ERROR, WARNING or INFO) and, where possible, a line number. The finding-code reference explains each one.

4. Or use the command line

With pyxaf[cli] installed:

$ pyxaf validate 2024.xaf
== 2024.xaf
4.0 — 0 error(s), 0 warning(s), 0 info

$ pyxaf info 2024.xaf
version          4.0
company          Voorbeeld & Zonen B.V.
fiscal year      2024
transactions     12
lines            36
total debit      38260.42
total credit     38260.42
...

pyxaf validate exits with 0 when the file is fine, 1 for warnings (with --strict), 2 for errors and 3 when pyxaf itself could not do its job, so it slots straight into scripts and CI.

🧰 More examples

Export to tables

pyxaf turns a file into twelve normalized tables, such as accounts, transactions and lines, with the same columns for every version:

with pyxaf.open("2024.xaf") as af:
    af.export("out/", format="csv")  # standard library only; also "jsonl"
    af.export("out/", format="parquet")  # pyxaf[parquet]
    frames = af.to_polars()  # pyxaf[polars]: a dict of DataFrames
    frames["lines"]  # amounts as Decimal(20, 2), not float

Query with DuckDB or pyarrow

Tables speak the Arrow PyCapsule interface, so Arrow-aware tools read them directly (needs pyxaf[arrow]):

import duckdb

with pyxaf.open("2024.xaf") as af:
    lines = af.tables["lines"]
    duckdb.sql("select account_id, sum(signed_amount) from lines group by account_id").show()

Check RGS codes

pyxaf reads each account's RGS (Referentie Grootboekschema) code. Load the official RGS Excel release you downloaded, and the validator checks every code against it:

import pyxaf.rgs

schema = pyxaf.rgs.load_excel("RGS 3.8-def.xlsx")
report = pyxaf.validate("2024.xaf", rgs=schema)

Split files and problem files

# a split auditfile ("Vervolgbestand 2 van 3") or one file per period, read as one
with pyxaf.open(["big.xaf", "big-2.xaf", "big-3.xaf"]) as af:
    ...

# a file that lies about its encoding, with stray control characters and bare "&"
af = pyxaf.open("old.xaf", encoding="cp1252", repair={"control-chars", "bare-ampersand"})

Repairs are opt-in and every repair shows up as a finding, so you always know what was changed.

Go deeper: raw records, detection details and the official XSDs
with pyxaf.open("2024.xaf") as af:
    af.format  # FormatInfo: version, namespace status, encoding, reasons
    af.accounts["1000"].rgs  # RgsRef(code='BLimKas', source='RGScode', ...)
    for journal_id, record in af.raw.transactions():
        ...  # lossless raw records, about twice as fast

report = pyxaf.validate("2024.xaf", xsd=True)  # also check the official XSD (pyxaf[xsd])

📜 Supported versions

Version Year How pyxaf recognises it
XAF 4.0 (4.0.3) 2025 namespace (including known variants) and 4.0-only elements
XAF 3.2.1 2024 its own ODB namespace
XAF 3.2 2014, 2017 http://www.auditfiles.nl/XAF/3.2
XAF 3.1 and 3.0 ~2010, ~2008 namespace and vocabulary (no public XSD exists)
CLAIR2 2003 header/auditfileVersion is CLAIR2.00.00, no namespace
ADF 1999 fixed-width ASCII starting with CLAIR1.00.00

From 1 January 2027 the Belastingdienst only accepts XAF 4.0. Older files stay around for years because of the seven-year retention period, which is why pyxaf reads them all.

📚 Documentation

The full documentation lives at https://spireflyhq.github.io/pyxaf/:

Release notes are in the changelog. For questions, see SUPPORT.md.

🤝 Contributing

Contributions are very welcome, especially reports of files from software that pyxaf does not handle well yet. Start with CONTRIBUTING.md for the development setup, and ARCHITECTURE.md for a map of the code. Issues labelled good first issue are a good place to begin.

Never attach a real auditfile to an issue or pull request. Auditfiles contain personal data and confidential financial records. The version, the exporting software, the finding codes and a small hand-made snippet are all we need.

Security problems are reported privately, as described in SECURITY.md.

📄 License and attribution

pyxaf is released under the MIT license.

The bundled XML schemas are published by the Belastingdienst (XAF 3.2.1 and 4.0), the former auditfiles.nl platform (XAF 3.2) and SRA (CLAIR2). The XAF 3.0 and 3.1 element lists are derived from the AnalyticsLibrary "XAF Mapping en Namen" table (Apache-2.0). RGS data is not bundled; load the official Excel release yourself with pyxaf.rgs.load_excel.

pyxaf is an independent open-source project. It is not affiliated with or endorsed by the Belastingdienst, the Taakgroep RGS or any software vendor, and a clean pyxaf report does not guarantee that the Belastingdienst will accept a file.

Metadata

Release files for pyxaf 0.1.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 pyxaf 0.1.0
File Size Uploaded
pyxaf-0.1.0.tar.gz 150.9 kB Details

Built distribution (wheel)

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

Total release size: 283.7 kB

Release files / pyxaf-0.1.0.tar.gz

Download URL pyxaf-0.1.0.tar.gz
Size 150.9 kB
Tags Source
SHA-256 checksum
How to use checksums
bc74714de9dd1e60fc0612892eb674b3b36f7c3727d721e4fa0cde00a3d0999b
BLAKE2b-256 checksum
How to use checksums
371984cad52c73f8b025dca6a97c01162ddee435cccc9c54512e6b93fd844c14
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 Oct 1, 2026.

Transparency log

Release files / pyxaf-0.1.0-py3-none-any.whl

Download URL pyxaf-0.1.0-py3-none-any.whl
Size 132.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
30e0cca9bdbf66befb1a952065bf1bc4ac0dd96574829deb103a140241579a7d
BLAKE2b-256 checksum
How to use checksums
202d9195d057f9857560279c2bf321d46bff64dca4d247e5debbd5509899ed4f
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 Oct 1, 2026.

Transparency log

Release history Release notifications | RSS feed

0.2.0

2 release files

0.1.2

2 release files

0.1.1

2 release files

This release

0.1.0 This release

2 release files

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