Are you the author? Sign in to claim
Read ISO 20022 camt Bank-to-Customer Statements (camt.052/053/054) and generate validated reversing entries by return re
Read ISO 20022 camt Bank-to-Customer Cash Management messages, extract
booked entries by return reason code (e.g. AC04 Closed Account), and generate
validated reversing entries — the core of a modern, AI-assisted treasury
stack with native MCP and LSP integrations.
Latest release: v0.0.5 — namespace-agnostic camt.052/053/054 parsing and one-shot reversing-entry generation, validated against the official ISO 20022
camt.053.001.14schema, for Python 3.10+. See what's new →
camt053 reads ISO 20022 camt cash-management messages — the standardised
bank-to-customer statements (camt.053), account reports (camt.052), and
debit/credit notifications (camt.054) — into a typed model, lets you filter
booked entries by ISO external return reason code, and generates a validated
reversing entry for the matching transactions.
The headline capability is the one-shot reversing-entry workflow: read an incoming camt.053 statement, find the entries carrying a return reason code (e.g. AC04 Closed Account), and emit a validated reversing entry — answering the prompt-engineering dream:
"Read this incoming bank statement XML, parse out the transactions with error code AC04, and automatically generate the reversing entry."
A single shared facade (camt053.services) backs four developer surfaces — the
Python API, the CLI, the REST API, and the companion MCP and LSP servers — so
every interface behaves identically. This package is part of the camt053
suite (all Python 3.10+):
| Package | Role |
|---|---|
camt053 | Core library + Click CLI + FastAPI REST API (this package) |
camt053-mcp | Model Context Protocol server (for AI agents) |
camt053-lsp | Language Server Protocol server (for editors) |
flowchart LR
A["Inbound camt.05x XML"] -->|parse| B["camt053.services"]
B -->|filter by reason code| C["AC04 entries"]
C -->|reverse + validate| D["camt.053.001.14 reversing entry"]
camt053 runs on macOS, Linux, and Windows and requires Python 3.10+ and pip.
python -m pip install camt053
python -m venv venv
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows
python -m pip install -U camt053
from camt053 import services
# An incoming camt.053 statement (truncated for brevity — see examples/).
statement_xml = open("statement.xml", encoding="utf-8").read()
# Find the entries returned AC04 (Closed Account).
ac04 = services.filter_entries(statement_xml, "AC04")
print(f"{len(ac04)} AC04 entr{'y' if len(ac04) == 1 else 'ies'}")
# Generate the reversing entry: parse -> filter -> reverse, in one call.
reversal_xml = services.generate_reversal(statement_xml, reason_code="AC04")
print(reversal_xml) # validated camt.053.001.14 document
Or from the command line:
# Generate a reversing entry for every AC04 entry on a statement
camt053 reverse -i statement.xml -r AC04 -o reversal.xml
# List the entries on a statement (filter by reason, status, date, or amount)
camt053 entries -i statement.xml -r AC04
camt053 entries -i statement.xml --status BOOK --from 2026-06-01 --min 1000
# Export the (filtered) entries as CSV or JSON, to stdout or a file
camt053 entries -i statement.xml --export csv -o entries.csv
camt053 entries -i statement.xml -r AC04 --export json
# Choose the output format: a Rich table (default) or structured JSON
camt053 entries -i statement.xml --format json
camt053 reverse -i statement.xml -r AC04 --format json # JSON envelope
# Inspect the parsed statement as JSON, or validate an identifier
camt053 parse -i statement.xml
camt053 validate-id -k iban -v GB29NWBK60161331926819
# Validate an incoming statement against its official ISO camt XSD
camt053 validate -i statement.xml
parse, entries, reverse, and validate accept -i - to read from stdin,
so they compose in a pipeline.
New to camt.053? The 10-minute quickstart walks the full install, validate, parse, reverse workflow with real commands and output.
.001.01
through .001.14) and real-world bank file is read.services.filter_entries(...) or the camt053 entries
flags.ExternalReturnReason1Code set (the common SEPA / CBPR+ return reasons),
listed via camt053 reasons, with case-insensitive lookup through
services.validate_reason_code(code) -> {"code", "name", "valid"}."return", "retry", or "ignore") via
services.classify_reason(code) -> {"code", "name", "action"}, with a
sensible built-in default (account-level rejections return, transient
conditions such as AM04 / AM05 retry, informational reasons ignore). The
full mapping is services.reason_policy(); both accept an overrides
mapping and a custom default. The camt053 reasons table shows the action
column and camt053 classify -r AC04 classifies a single code.camt053 entries --export {csv,json} [-o file]); CSV columns are reference, amount, currency, credit_debit_indicator, status, booking_date, value_date, reason_code.camt053 entries --format json emits the entries
as a JSON array, and camt053 reverse --format json emits a
{"message_type", "reason_code", "xml"} envelope instead of raw XML
(--format table, the default, keeps the Rich table / raw XML).camt.053.001.14 reversing entry from the matching
entries (credit/debit indicator flipped, RvslInd set, return reason carried
in RtrInf), in one call.camt.053.001.14 XSD bundled with the package.Nm / AddtlInf / party / counterparty names) bound for SWIFT FIN / CBPR+
rails: characters outside the SWIFT X set are transliterated (é → e,
ß → ss, smart quotes / dashes folded) or stripped, and field maximum
lengths are enforced. Enable it on the reversal path with
services.generate_reversal(xml, cleanse=True) /
services.generate(records, cleanse=True) (default off, so existing output
is unchanged), or cleanse records directly with
services.cleanse_records(records) -> {"changed", "fields": [report, ...]},
which returns an audit report of exactly what changed. Cleansed reversals
still validate against the bundled XSD.services.validate_statement(xml) (and the
camt053 validate command) check an inbound camt.052 / camt.053 / camt.054
document against the matching official ISO 20022 XSD, detected from its
namespace, returning {"valid", "message_type", "errors"}.ParsedDocument / Statement
back to a validated camt.053.001.14 document via
services.serialize_statement(xml) (or camt053.serialize_document(doc) /
camt053.serialize_statement(stmt)). The output is deterministic and
round-trip stable: parse_document(serialize_statement(parse_document(xml)))
preserves the account, balances, and entries (references, amounts,
currencies, credit/debit indicators, and return reasons).defusedxml (XXE / billion-laughs
safe); output paths are traversal-checked.camt053.services.Entry.amount_decimal /
Balance.amount_decimal parse the string amount into a Decimal (the
string is kept verbatim for XML fidelity), and
services.validate_currency(code) -> {"code", "valid", "minor_units"}
checks a code against a bundled ISO 4217 set and reports its minor units
(EUR=2, JPY=0, …).--strict) and tested (100% coverage), validated against
the official ISO 20022 business samples.from camt053 import parse_statement, services
# A minimal incoming camt.053 statement: a EUR 1,500 credit transfer that was
# booked, then returned because the beneficiary account was closed (AC04).
statement_xml = """<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:camt.053.001.14">
<BkToCstmrStmt>
<GrpHdr><MsgId>STMT-MSG-0001</MsgId><CreDtTm>2026-06-15T08:00:00</CreDtTm></GrpHdr>
<Stmt>
<Id>STMT-0001</Id><CreDtTm>2026-06-15T08:00:00</CreDtTm>
<Acct><Id><IBAN>GB29NWBK60161331926819</IBAN></Id><Ccy>EUR</Ccy></Acct>
<Bal><Tp><CdOrPrtry><Cd>CLBD</Cd></CdOrPrtry></Tp>
<Amt Ccy="EUR">10000.00</Amt><CdtDbtInd>CRDT</CdtDbtInd>
<Dt><Dt>2026-06-15</Dt></Dt></Bal>
<Ntry>
<NtryRef>NTRY-0001</NtryRef>
<Amt Ccy="EUR">1500.00</Amt><CdtDbtInd>CRDT</CdtDbtInd>
<Sts><Cd>BOOK</Cd></Sts>
<NtryDtls><TxDtls>
<RtrInf><Rsn><Cd>AC04</Cd></Rsn></RtrInf>
</TxDtls></NtryDtls>
</Ntry>
</Stmt>
</BkToCstmrStmt>
</Document>"""
# 1. Parse into the typed model.
statement = parse_statement(statement_xml)
print(statement.account.identifier()) # -> GB29NWBK60161331926819
print(len(statement.entries)) # -> 1
# 2. Select the entries returned AC04 (Closed Account).
ac04 = statement.entries_with_reason("AC04")
print(ac04[0].amount, ac04[0].credit_debit_indicator) # -> 1500.00 CRDT
# 3. Generate the validated reversing entry (the original CRDT becomes DBIT).
reversal_xml = services.generate_reversal(statement_xml, reason_code="AC04")
assert "<RvslInd>true</RvslInd>" in reversal_xml
assert "<CdtDbtInd>DBIT</CdtDbtInd>" in reversal_xml
| Message type | Name | Direction |
|---|---|---|
camt.052.001.14 | Bank To Customer Account Report | read |
camt.053.001.14 | Bank To Customer Statement | read + reverse |
camt.054.001.14 | Bank To Customer Debit Credit Notification | read |
The parser is namespace-agnostic and reads every ISO version of these messages;
the official XSDs for .001.01–.001.14 are bundled under camt053/xsd/.
Reversing entries are emitted as camt.053.001.14.
flowchart TD
CLI["Click CLI"] --> S["camt053.services"]
API["FastAPI REST API"] --> S
MCP["camt053-mcp"] --> S
LSP["camt053-lsp"] --> S
S --> P["parse/ — statement_parser, reason_codes"]
S --> R["reversal/ — reversal builder"]
S --> X["xml/ — template + official ISO XSD"]
S --> V["validation/ — IBAN, BIC, LEI, JSON Schema"]
| Module | Responsibility |
|---|---|
camt053.parse | Namespace-agnostic statement parser and return-reason helpers |
camt053.reversal | Builds flat reversing-entry records from parsed entries |
camt053.xml | Renders the camt.053 reversal template and validates it via the ISO XSD |
camt053.validation | IBAN / BIC / LEI and JSON-Schema validators |
camt053.security | XXE-safe parsing and path-traversal-checked output |
camt053.services | The shared facade backing every interface |
Every exception in camt053.exceptions inherits from
Camt053Error and carries a stable, machine-readable code. These codes are
part of the public API — they are guaranteed unique and will not change across
releases — so you can switch on exc.code (e.g. to map a failure onto an HTTP
status) without depending on the class name or message text.
| Code | Exception | Meaning |
|---|---|---|
CAMT053_ERROR | Camt053Error | Base error for any Camt053 failure |
ACCOUNT_VALIDATION_ERROR | AccountValidationError | Account/input data failed validation |
XML_GENERATION_ERROR | XMLGenerationError | XML rendering or template failure |
CONFIGURATION_ERROR | ConfigurationError | Invalid configuration or CLI arguments |
DATA_SOURCE_ERROR | DataSourceError | A data source could not be read |
SCHEMA_VALIDATION_ERROR | SchemaValidationError | XML did not conform to its ISO 20022 XSD |
INVALID_IBAN_ERROR | InvalidIBANError | IBAN format / checksum validation failed |
INVALID_BIC_ERROR | InvalidBICError | BIC/SWIFT format validation failed |
INVALID_LEI_ERROR | InvalidLEIError | LEI format / checksum validation failed |
MISSING_REQUIRED_FIELD_ERROR | MissingRequiredFieldError | A required field was absent |
STATEMENT_PARSE_ERROR | StatementParseError | An incoming statement could not be parsed |
REVERSAL_GENERATION_ERROR | ReversalGenerationError | A reversing entry could not be generated |
from camt053 import services
from camt053.exceptions import Camt053Error
try:
services.generate_reversal(statement_xml, reason_code="AC04")
except Camt053Error as exc:
log.error("[%s] %s", exc.code, exc)
The statement parser is built for the messy reality of inbound bank files: malformed-but-recoverable statements degrade gracefully rather than failing outright.
None / empty — only the <Document> envelope
wrapping a recognised camt.05x container is mandatory.<camt:Document> root, a missing namespace, or a non-ISO namespace URI all
parse the same way.Genuinely non-well-formed XML (unclosed / mismatched tags, bad entities) still
raises StatementParseError, which carries the 1-based source line (and
column, where reported) so the offending byte can be located. See
camt053/parse/statement_parser.py for
the documented recovery limits.
Runnable, self-contained scripts live in examples/:
| Example | Demonstrates |
|---|---|
reverse_ac04.py | The headline workflow — find AC04 entries and generate the reversing entry |
parse_statement.py | Parsing a statement into the typed model |
services_facade.py | The shared camt053.services facade |
validate_identifiers.py | IBAN / BIC / LEI validation |
rest_api_client.py | Driving the FastAPI REST API in-process |
git clone https://github.com/sebastienrousseau/camt053.git && cd camt053
python examples/reverse_ac04.py
camt053 is the core of a set of independently installable packages
— pick whichever ones your stack needs:
| Package | Role |
|---|---|
camt053 | Core library + CLI + FastAPI REST API (this package) |
camt053-mcp | Model Context Protocol server (for AI agents) |
camt053-lsp | Language Server Protocol server (for editors) |
camt053-writer-xlsx | Excel .xlsx writer for parsed statements |
camt053-loader-mt940 | SWIFT MT940 → camt.053 loader |
The MCP, LSP, writer-xlsx, and loader-mt940 packages are thin wrappers
over the shared camt053.services facade exported here, so every
interface behaves identically.
pain001 — the
sibling library for outbound ISO 20022.git clone https://github.com/sebastienrousseau/camt053.git && cd camt053
mise install
poetry install
poetry shell
A Makefile orchestrates the quality gates (kept in lockstep with CI):
make check # all gates (REQUIRED before commit)
make test # pytest with coverage (100% gate)
make lint # ruff + black --check
make type-check # mypy --strict
make examples # run the example scripts
camt053 is the entry point for untrusted XML from banks for
every consumer in the suite. Defence-in-depth is taken seriously:
parsing uses defusedxml (XXE / billion-laughs neutralised), a
pre-flight xml_guard enforces a configurable byte cap and refuses
inline DOCTYPE / ENTITY declarations, and the REST API additionally
caps request bodies via middleware. Reporting practice, supported
versions, fix-window SLAs, and the full supply-chain posture (PyPI
Trusted Publishing, sigstore attestations, signed tags) live in
SECURITY.md. Vulnerabilities go via GitHub Private
Vulnerability Reporting, not public issues.
README.md — this fileCHANGELOG.md — release notesROADMAP.md — milestones and the explicit Declined/Deferred listGOVERNANCE.md — decision model + becoming a maintainerSECURITY.md — disclosure + supported versions + supply chain + NIST SSDF mappingSUPPORT.md — how to get help, by needMAINTAINERS.md — who can merge and cut releasesCONTRIBUTING.md — submission process + styleSTYLEGUIDE.md — cross-suite style guide (linked from every package README)docs/quickstart.md — 10-minute install, validate, parse, reverse tutorialdocs/deployment-cookbook.md — Docker Compose / systemd / Kubernetes recipes for the REST APIdocs/version-matrix.md — supported camt.05x revisions with the Nov 2026 cliffexamples/ — runnable scripts, exercised in CIdocs/ — extended reference (API, quickstart, deployment cookbook)Licensed under the Apache License, Version 2.0. Any contribution submitted for inclusion shall be licensed as above, without additional terms.
Contributions are welcome — see the contributing instructions. Thanks to all contributors.
Built on Click,
Rich, Jinja2,
xmlschema,
defusedxml, and
FastAPI, against the official ISO 20022
camt.05x schemas.
Run Claude Code as an MCP server so any agent can delegate coding tasks to it
Browser automation using accessibility snapshots instead of screenshots
Google's universal MCP server supporting PostgreSQL, MySQL, MongoDB, Redis, and 10+ databases
Official GitHub integration for repos, issues, PRs, and CI/CD workflows