https://github.com/sebastienrousseau/camt053
Read ISO 20022 camt Bank-to-Customer Statements (camt.052/053/054) and generate validated reversing entries by return reason code such as AC04 Closed Account. Core library with a Click CLI and FastAPI REST API.
https://github.com/sebastienrousseau/camt053
bank-statement banking camt camt052 camt053 camt054 cash-management cli fintech iban iso20022 payments python rest-api return-reason reversing-entry treasury xml
Last synced: about 21 hours ago
JSON representation
Read ISO 20022 camt Bank-to-Customer Statements (camt.052/053/054) and generate validated reversing entries by return reason code such as AC04 Closed Account. Core library with a Click CLI and FastAPI REST API.
- Host: GitHub
- URL: https://github.com/sebastienrousseau/camt053
- Owner: sebastienrousseau
- License: apache-2.0
- Created: 2026-06-17T14:08:24.000Z (24 days ago)
- Default Branch: main
- Last Pushed: 2026-06-25T14:26:06.000Z (16 days ago)
- Last Synced: 2026-06-25T16:14:06.195Z (15 days ago)
- Topics: bank-statement, banking, camt, camt052, camt053, camt054, cash-management, cli, fintech, iban, iso20022, payments, python, rest-api, return-reason, reversing-entry, treasury, xml
- Language: Python
- Homepage: https://camt053.com
- Size: 470 KB
- Stars: 1
- Watchers: 0
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
Awesome Lists containing this project
README
# camt053: ISO 20022 Bank Statements and Reversing Entries
[![PyPI Version][pypi-badge]][07]
[![Python Versions][python-versions-badge]][07]
[![License][license-badge]][01]
[![Tests][tests-badge]][tests-url]
[![Quality][quality-badge]][quality-url]
[![OpenSSF Scorecard][scorecard-badge]][scorecard-url]
[![OpenSSF Best Practices][bestpractices-badge]][bestpractices-url]
[![Documentation][docs-badge]][docs-url]
**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.14` schema, for Python 3.10+.
> [See what's new →][release-005]
## Contents
- [Overview](#overview)
- [Install](#install)
- [Quick Start](#quick-start)
- [Features](#features)
- [Usage](#usage)
- [Supported messages](#supported-messages)
- [Architecture](#architecture)
- [Examples](#examples)
- [The camt053 suite](#the-camt053-suite)
- [When not to use camt053](#when-not-to-use-camt053)
- [Development](#development)
- [Security](#security)
- [Documentation](#documentation)
- [License](#license)
- [Contributing](#contributing)
- [Acknowledgements](#acknowledgements)
## Overview
**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."*
- **Documentation:**
- **Source code:**
- **Bug reports:**
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`][mcp-pkg] | Model Context Protocol server (for AI agents) |
| [`camt053-lsp`][lsp-pkg] | Language Server Protocol server (for editors) |
```mermaid
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"]
```
## Install
**camt053** runs on macOS, Linux, and Windows and requires **Python 3.10+** and
**pip**.
```sh
python -m pip install camt053
```
Using an isolated virtual environment (recommended)
```sh
python -m venv venv
source venv/bin/activate # macOS/Linux
venv\Scripts\activate # Windows
python -m pip install -U camt053
```
## Quick Start
```python
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:
```sh
# 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.
## Features
- **Parse** camt.053 / camt.052 / camt.054 into a typed, JSON-serialisable
model. Parsing is **namespace-agnostic**, so every ISO version (`.001.01`
through `.001.14`) and real-world bank file is read.
- **Filter** booked entries by ISO external return reason code (AC04, AC06,
MD07, …), and by **status**, **booking-date range**, and **amount range**
(all ANDed) via `services.filter_entries(...)` or the `camt053 entries`
flags.
- **Return reason codes** — a substantial slice of the ISO 20022
`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"}`.
- **Reason-code action policy** — classify a return reason into a handling
action (`"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.
- **Export** the (filtered) entries to **CSV** or **JSON** (`camt053 entries
--export {csv,json} [-o file]`); CSV columns are `reference, amount,
currency, credit_debit_indicator, status, booking_date, value_date,
reason_code`.
- **Structured output** — `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).
- **Reverse** — generate a `camt.053.001.14` reversing entry from the matching
entries (credit/debit indicator flipped, `RvslInd` set, return reason carried
in `RtrInf`), in one call.
- **Validated output** — generated reversals are checked against the **official
ISO 20022 `camt.053.001.14` XSD** bundled with the package.
- **SWIFT charset cleansing** — opt-in cleansing of the name / narrative fields
(`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.
- **Validate incoming statements** — `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"}`.
- **Re-serialise (round-trip)** — render a parsed `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).
- **Safe by default** — XML is parsed with `defusedxml` (XXE / billion-laughs
safe); output paths are traversal-checked.
- **One facade, four interfaces** — the CLI, REST API, MCP server, and LSP
server all call `camt053.services`.
- **IBAN / BIC / LEI validators** (ISO 13616 / 9362 / 17442).
- **Decimal amounts & ISO 4217 currencies** — `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, …).
- **Typed** (mypy `--strict`) and **tested** (100% coverage), validated against
the official ISO 20022 business samples.
## Usage
```python
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 = """
STMT-MSG-00012026-06-15T08:00:00
STMT-00012026-06-15T08:00:00
GB29NWBK60161331926819EUR
CLBD
10000.00CRDT
NTRY-0001
1500.00CRDT
BOOK
AC04
"""
# 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 "true" in reversal_xml
assert "DBIT" in reversal_xml
```
## Supported messages
| 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`.
## Architecture
```mermaid
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 |
## Error handling
Every exception in [`camt053.exceptions`](camt053/exceptions.py) 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 |
```python
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)
```
## Robustness
The statement parser is built for the messy reality of inbound bank files:
malformed-but-recoverable statements degrade gracefully rather than failing
outright.
- **Missing optional elements** (owner name, currency, balances, booking date,
return reason, ...) read as `None` / empty — only the `` envelope
wrapping a recognised camt.05x container is mandatory.
- **Unknown or extra elements** (vendor extensions, unexpected siblings) are
ignored: children are matched by local name, not by a fixed schema.
- **Unexpected namespaces and prefixes** are tolerated — a prefixed
`` 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`](camt053/parse/statement_parser.py) for
the documented recovery limits.
## Examples
Runnable, self-contained scripts live in [`examples/`](examples/):
| Example | Demonstrates |
|---------|--------------|
| [`reverse_ac04.py`](examples/reverse_ac04.py) | The headline workflow — find AC04 entries and generate the reversing entry |
| [`parse_statement.py`](examples/parse_statement.py) | Parsing a statement into the typed model |
| [`services_facade.py`](examples/services_facade.py) | The shared `camt053.services` facade |
| [`validate_identifiers.py`](examples/validate_identifiers.py) | IBAN / BIC / LEI validation |
| [`rest_api_client.py`](examples/rest_api_client.py) | Driving the FastAPI REST API in-process |
```sh
git clone https://github.com/sebastienrousseau/camt053.git && cd camt053
python examples/reverse_ac04.py
```
## The camt053 suite
`camt053` is the core of a set of independently installable packages
— pick whichever ones your stack needs:
| Package | Role |
| :--- | :--- |
| [`camt053`](https://pypi.org/project/camt053/) | **Core library + CLI + FastAPI REST API (this package)** |
| [`camt053-mcp`](https://pypi.org/project/camt053-mcp/) | Model Context Protocol server (for AI agents) |
| [`camt053-lsp`](https://pypi.org/project/camt053-lsp/) | Language Server Protocol server (for editors) |
| [`camt053-writer-xlsx`](https://pypi.org/project/camt053-writer-xlsx/) | Excel `.xlsx` writer for parsed statements |
| [`camt053-loader-mt940`](https://pypi.org/project/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.
## When not to use camt053
- **You need to generate payment files** (pain.001 Customer Credit
Transfer, pain.008 Direct Debit). Use
[`pain001`](https://github.com/sebastienrousseau/pain001) — the
sibling library for outbound ISO 20022.
- **You need to parse pacs.\* (FI-to-FI) messages.** Out of scope; this
library targets the camt.05x (Bank-to-Customer) family only.
- **You need bank API transport** (PSD2, EBICS, SWIFTNet). camt053
reads files and emits files; it does not move them. Pair with a
dedicated transport library.
- **You need a GUI.** This is a library + CLI + REST API; the closest
thing to a UI is the LSP server's in-editor diagnostics.
- **You need real-time processing** (FedNow / TIPS / RTP). Those rails
use camt.052 intraday reporting, not camt.053 end-of-day statements;
the parser accepts camt.052 but the reversing-entry workflow is
camt.053-shaped.
## Development
**camt053** uses [Poetry](https://python-poetry.org/) and
[mise](https://mise.jdx.dev/).
```bash
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):
```bash
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
```
## Security
`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`](SECURITY.md). Vulnerabilities go via GitHub Private
Vulnerability Reporting, not public issues.
## News / Releases
- **2026-06-22 — [Shipping camt053 v0.0.6 for the 14-16 November 2026 ISO 20022 cliff](docs/posts/2026-06-22-shipping-camt053-v006-for-the-november-2026-cliff.md)** (v0.0.6 release announcement + Nov 2026 cliff narrative).
## Documentation
- [`README.md`](README.md) — this file
- [`CHANGELOG.md`](CHANGELOG.md) — release notes
- [`ROADMAP.md`](ROADMAP.md) — milestones and the explicit Declined/Deferred list
- [`GOVERNANCE.md`](GOVERNANCE.md) — decision model + becoming a maintainer
- [`SECURITY.md`](SECURITY.md) — disclosure + supported versions + supply chain + NIST SSDF mapping
- [`SUPPORT.md`](SUPPORT.md) — how to get help, by need
- [`MAINTAINERS.md`](MAINTAINERS.md) — who can merge and cut releases
- [`CONTRIBUTING.md`](CONTRIBUTING.md) — submission process + style
- [`STYLEGUIDE.md`](STYLEGUIDE.md) — cross-suite style guide (linked from every package README)
- [`docs/version-matrix.md`](docs/version-matrix.md) — supported `camt.05x` revisions with the Nov 2026 cliff
- [`examples/`](examples/) — runnable scripts, exercised in CI
- [`docs/`](docs/) — extended reference (API, quickstart, deployment cookbook)
- Hosted Sphinx docs:
## License
Licensed under the [Apache License, Version 2.0][01]. Any contribution submitted
for inclusion shall be licensed as above, without additional terms.
## Contributing
Contributions are welcome — see the [contributing instructions][04]. Thanks to
all [contributors][05].
## Acknowledgements
Built on [Click](https://click.palletsprojects.com/),
[Rich](https://rich.readthedocs.io/), [Jinja2](https://jinja.palletsprojects.com/),
[xmlschema](https://github.com/sissaschool/xmlschema),
[defusedxml](https://github.com/tiran/defusedxml), and
[FastAPI](https://fastapi.tiangolo.com/), against the official ISO 20022
`camt.05x` schemas.
[01]: https://opensource.org/license/apache-2-0/
[04]: https://github.com/sebastienrousseau/camt053/blob/main/CONTRIBUTING.md
[05]: https://github.com/sebastienrousseau/camt053/graphs/contributors
[07]: https://pypi.org/project/camt053/
[mcp-pkg]: https://github.com/sebastienrousseau/camt053-mcp
[lsp-pkg]: https://github.com/sebastienrousseau/camt053-lsp
[release-005]: https://github.com/sebastienrousseau/camt053/releases/tag/v0.0.5
[docs-badge]: https://img.shields.io/badge/Docs-GitHub%20Pages-blue?style=for-the-badge
[docs-url]: https://sebastienrousseau.github.io/camt053/
[license-badge]: https://img.shields.io/pypi/l/camt053?style=for-the-badge
[pypi-badge]: https://img.shields.io/pypi/v/camt053?style=for-the-badge
[python-versions-badge]: https://img.shields.io/pypi/pyversions/camt053.svg?style=for-the-badge
[quality-badge]: https://img.shields.io/github/actions/workflow/status/sebastienrousseau/camt053/ci.yml?branch=main&label=Quality&style=for-the-badge
[quality-url]: https://github.com/sebastienrousseau/camt053/actions/workflows/ci.yml
[scorecard-badge]: https://api.scorecard.dev/projects/github.com/sebastienrousseau/camt053/badge?style=for-the-badge
[scorecard-url]: https://scorecard.dev/viewer/?uri=github.com/sebastienrousseau/camt053
[bestpractices-badge]: https://www.bestpractices.dev/projects/13373/badge
[bestpractices-url]: https://www.bestpractices.dev/projects/13373
[tests-badge]: https://img.shields.io/github/actions/workflow/status/sebastienrousseau/camt053/ci.yml?branch=main&label=Tests&style=for-the-badge
[tests-url]: https://github.com/sebastienrousseau/camt053/actions/workflows/ci.yml