Skip to main content

Validate ISO 20022 CBPR+ XML messages against SWIFT usage (business) rules.

Project description

cbpr-usage-rules

Validate ISO 20022 CBPR+ XML messages (the SWIFT cross-border payments profile) against the Usage / Business Rules that apply on top of the XSD schemas. These are the rules that the schema alone cannot express: cross-field constraints, cross-schema checks between the Business Application Header (BAH) and the message Document, conditional presence, code-list restrictions, and field-format algorithms (IBAN, LEI, BIC, country, currency).

Rules are versioned per year (currently 2025 and 2026) and organised by message type (pacs.008, pacs.009 and its COV/ADV variants, pacs.002, pacs.004, pain.001, camt.052, camt.054, and the STP variants).

This package is AI generated. The rule logic was derived, with review, from the published CBPR+ usage-guideline spreadsheets. Treat it as an aid, not as a substitute for the official SWIFT specifications. Verify against the source material before relying on results in production.


Installation

pip install cbpr-usage-rules

Requires Python 3.9+ and depends only on lxml.


Quick start

As a library

import cbpr_rules

result = cbpr_rules.validate_file("payment.xml", year=2025)

if result["valid"]:
    print("Compliant")
else:
    for v in result["violations"]:
        print(f"{v['rule_number']}  line {v['line']}  {v['xpath']}")
        print(f"    {v['description']}")

validate_string is the same but takes XML text:

result = cbpr_rules.validate_string(xml_text, year=2026)

From the command line

# Human-readable (default). Exit code is non-zero if there are violations.
cbpr-validate payment.xml --year 2025

# Compact, one finding per line (compiler-style) — for tools and coding agents
cbpr-validate payment.xml --year 2025 --format compact

# JSON output (full machine-readable result)
cbpr-validate payment.xml --year 2025 --json   # same as --format json

# Read from stdin
cat payment.xml | cbpr-validate --year 2026

# Also list the advisory (non-enforced) rules in full
cbpr-validate payment.xml --year 2025 --advisory

# Additionally schema-validate against an XSD (repeatable, results shown separately)
cbpr-validate payment.xml --year 2025 --xsd pacs.008.001.08.xsd

Each reported violation shows the rule, why this instance failed (Problem:), the offending XML from your file (Found:), and its line + xpath (At:).

For tools and coding agents, prefer --format compact (one finding per line, parseable like a compiler/linter) or --json (the full structured result):

$ cbpr-validate payment.xml --year 2025 --format compact
payment.xml:7: violation [pacs.008:R2] From vs Instructing Agent: ABCDGB2LXXX != GGGGGB2LXXX | at /AppHdr/Fr/FIId/FinInstnId/BICFI
payment.xml:63: violation [pacs.008:VAL-IBAN] invalid IBAN: 'GB29NWBK60161331926818' | at /Document/.../DbtrAcct/Id/IBAN
INVALID: 5 violations (87 rules)

Each line is file:line: severity [rule] message | at xpath, and the final VALID: / INVALID: line is a stable summary to grep for. With --xsd, schema errors appear as schema-error lines and are counted in the summary. Advisory rules are summarised as a count by default; pass --advisory to list them.


Example messages

For every supported message type the package bundles a minimum and a maximum example message — both pre-verified to pass the usage rules and the CBPR+ XSDs:

  • min — only the mandatory fields (smallest valid message);
  • max — every XSD-visible field populated (one representative occurrence; the richest single legal combination, since some fields are mutually exclusive).
# Print to stdout (full AppHdr + Document, wrapped in <Envelope>)
cbpr-validate --example max --year 2025 --type pacs.008
cbpr-validate --example min --year 2026 --type pacs.009_cov > template.xml

# Override the wrapper tag (default: Envelope)
cbpr-validate --example max --year 2025 --type pacs.008 --wrapper BusinessMessage
import cbpr_rules
xml = cbpr_rules.example_message(2025, "pacs.008", "max")   # variant defaults to "max"
cbpr_rules.example_variants(2025, "pacs.008")               # ['min', 'max']

The data is fictitious (synthetic, generated BICs/IBANs/LEIs) and does not reproduce any real sample — use them as starting templates, not real payments.


Generating identifiers

The example values above are produced by a small deterministic ID generator (the "Counting Strings" algorithm — no random/uuid, same seed → same output). Every generated id is structurally valid and passes the tool's own validators.

cbpr-validate --generate iban --country AT --seed s1     # ISO 13616 + mod-97
cbpr-validate --generate lei  --seed acme                # ISO 17442 + check digits
cbpr-validate --generate bic  --bank JSBP --country GB   # -> JSBPGB..XXX
cbpr-validate --generate uuid --count 3                  # RFC 4122 v4 (UETR)
cbpr-validate --generate mid  --country GB               # message identifier
from cbpr_rules import generate_iban, generate_lei, generate_bic, generate_uetr
generate_iban("DE", seed="acme")   # deterministic, valid German IBAN
generate_bic(country="GB", seed="acme")

IBAN generation supports the 27 EU countries plus GB.


The result object

Both validate_file and validate_string return a dictionary:

{
  "valid": False,                     # True only if there are no VIOLATION-severity findings
  "message_type": "pacs.008",         # the rule set that was applied
  "detected_message_type": "pacs.008",# auto-detected from the Document namespace
  "year": 2025,
  "rules_evaluated": 87,
  "violations": [
    {
      "rule_number": "pacs.008:R41",   # unique within the message type
      "name": "CBPR_Interbank_Settlement_Currency_FormalRule",
      "description": "The codes XAU, XAG, XPD and XPT are not allowed ...",
      "detail": "commodity currency 'XAU' not allowed",  # why this instance was flagged
      "found": "<IntrBkSttlmAmt Ccy=\"XAU\">1000.00</IntrBkSttlmAmt>",  # the offending XML
      "xpath": "/RequestPayload/Document/FIToFICstmrCdtTrf/CdtTrfTxInf/IntrBkSttlmAmt",
      "line": 39,                      # 1-based line in the source XML
      "severity": "violation"
    }
  ],
  "advisory": [                        # textual guidance that cannot be mechanically enforced
    {"rule_number": "pacs.008:R4", "name": "...", "description": "..."}
  ]
}

Every violation carries the four required pieces of information: the xpath, the line number, a description, and a unique rule number.


Mid-level usage

Choosing the year

Pass year=2025 or year=2026. Rule loading is per-year and lazy — only the requested year's modules are imported. You can validate against more than one year in the same process simply by calling with different year values.

Message type detection and variants

The message type is auto-detected from the <Document> namespace, so you normally don't pass it. Business variants that share a base namespace — STP (pacs.008/pacs.009), COV and ADV (pacs.009) — cannot be told apart from the XML alone, so select them explicitly when you want their stricter rule sets:

cbpr_rules.validate_file("cover.xml", year=2025, msgtype="pacs.009_cov")
cbpr-validate cover.xml --year 2025 --type pacs.009_cov

Wrapper tags

Messages arrive inside different envelopes (<RequestPayload>, <DataPDU>, SWIFT SAA wrappers, and so on). The validator locates the AppHdr and Document elements wherever they sit in the tree and ignores the surrounding wrapper, so the same input validates identically regardless of envelope. Element matching is by local name, so namespace prefixes never matter.

Severity

  • violation — a usage rule is broken; this makes valid false.
  • info — advisory guidance surfaced for awareness; it never fails validation and appears in the separate advisory list (textual rules that cannot be mechanically checked).

In-depth usage

Discovering the rule set

cbpr_rules.available(2025)              # -> ['camt.052', 'pacs.008', ...]
cbpr_rules.list_rules(2025, "pacs.008") # -> [{rule_number, name, description, severity, enforced}, ...]
cbpr_rules.list_rules(2025, "pacs.008", enforced_only=True)  # only the enforceable rules
cbpr_rules.list_rules(2025, "pacs.008", with_xpaths=True)    # + "xpaths" each rule affects
cbpr-validate --year 2025 --list-types
cbpr-validate --year 2025 --type pacs.008 --list               # all rules
cbpr-validate --year 2025 --type pacs.008 --enforced           # enforceable rules only

The --list / --enforced output (and with_xpaths=True) annotates each rule with the concrete fields it affects, so you can see exactly which element/attribute a rule constrains — handy when several fields share a name (e.g. many Ctry fields, but a rule targets only one):

pacs.008:VAL-IBAN - CBPR_Valid_IBAN
    //IBAN
pacs.008:VAL-BIC - CBPR_Valid_Agent_BIC
    /Document/FIToFICstmrCdtTrf/CdtTrfTxInf/InstgAgt/FinInstnId/BICFI
pacs.008:R41 - CBPR_Interbank_Settlement_Currency_FormalRule
    /Document/FIToFICstmrCdtTrf/CdtTrfTxInf/IntrBkSttlmAmt/@Ccy

Paths are derived by running each rule against the bundled example messages (best-effort): concrete element paths and @attribute targets where present, or a //Name wildcard for a datatype rule whose element isn't in the examples.

Algorithmic field validation

Beyond the published rules, the following formats are validated with their standard algorithms wherever the corresponding fields appear:

Field Standard Check
IBAN ISO 13616 structure + mod-97 check digits
LEI ISO 17442 structure + ISO 7064 mod-97-10 check
BIC ISO 9362 8/11-char structure + valid country
Country ISO 3166-1 alpha-2 membership
Currency ISO 4217 membership

The reference code lists are vendored, so validation never makes a network call.

Some of these check-digit algorithms and reference code lists were derived from Wikipedia (e.g. IBAN, LEI, and the ISO 3166 country codes). Verify against the authoritative ISO/registry sources before relying on them in production.

Optional XSD schema validation

You can additionally validate a message against one or more XSD schemas as a separate second result set. XSD files are not bundled with the package — supply your own path(s):

result = cbpr_rules.validate_file("payment.xml", year=2025, xsd="pacs.008.001.08.xsd")
# or several:  xsd=["head.001.001.02.xsd", "pacs.008.001.08.xsd"]
cbpr-validate payment.xml --year 2025 --xsd pacs.008.001.08.xsd

Each XSD is auto-matched by its targetNamespace: a message schema validates the Document, a head.001 schema validates the AppHdr. When (and only when) an XSD is supplied, the result gains a separate top-level xsd block, and the CLI prints a distinct XSD SCHEMA VALIDATION section:

"xsd": {
  "checked": True,
  "schema_valid": False,                 # all supplied schemas passed?
  "schemas": [
    {
      "file": "pacs.008.001.08.xsd",
      "target_namespace": "urn:iso:std:iso:20022:tech:xsd:pacs.008.001.08",
      "validated_element": "Document",   # or "AppHdr" / "root"
      "valid": False,
      "errors": [{"message": "...", "line": 82, "xpath": "/Document/.../CdtrAgt"}]
    }
  ]
}

Usage-rule results and schema results stay separate: the top-level valid reflects only the usage rules, while schema validity is xsd.schema_valid. The CLI exit code is non-zero if either the usage rules or the schema fail. With no --xsd, nothing about XSD appears in the output.

Handling errors

validate_file / validate_string raise cbpr_rules.engine.ValidationError for unparseable XML or when the message type cannot be determined and was not supplied. The CLI turns these into a message on stderr and exit code 2.

CLI exit codes

Code Meaning
0 Valid (no violations)
1 Invalid (one or more violations)
2 Usage error / could not validate

How the rules are organised

Each (year, message type) has a hand-authored Python module under cbpr_rules/rules/y<year>/. Rules are built from a small library of reusable combinators (presence, conditional presence, mutual exclusion, value matching, length, code lists, address grace-period rules) plus bespoke functions for cross-field and cross-schema logic. The XSD files in the source material are informational only and are not reimplemented here.

Enforced vs advisory

Every published rule is registered. A rule is enforced (can produce a violation) when it can be checked deterministically from the message alone — this includes the formal pseudo-code rules and the mechanizable textual rules, such as:

  • header ↔ message consistency (MsgDefIdr matches the Document definition; BizMsgIdr carries the GroupHeader MsgId);
  • no structured Postal Address value duplicated in an AddressLine;
  • AnyBIC present ⇒ Name/PostalAddress not allowed;
  • Structured Remittance ≤ 9,000 characters;
  • charge rules where instructed and settlement amounts share a currency and differ, and amount-total-equals-sum checks.

A rule stays advisory (severity: info, surfaced as guidance, never failing validation) when it cannot be verified from the message in isolation — for example anything that refers to a related or underlying message not present (Original_*, related-BAH, COV/ADV UETR/E2E), depends on the SWIFT network or a jurisdiction, or expresses a recommendation / bilaterally-agreed practice. Checks are deliberately conservative: each skips when its inputs are absent or ambiguous, so a compliant message is never failed spuriously.

Licence

MIT — see LICENSE.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cbpr_usage_rules-0.6.0.tar.gz (149.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cbpr_usage_rules-0.6.0-py3-none-any.whl (215.5 kB view details)

Uploaded Python 3

File details

Details for the file cbpr_usage_rules-0.6.0.tar.gz.

File metadata

  • Download URL: cbpr_usage_rules-0.6.0.tar.gz
  • Upload date:
  • Size: 149.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.13

File hashes

Hashes for cbpr_usage_rules-0.6.0.tar.gz
Algorithm Hash digest
SHA256 3d0dbdcdebcc03eabdae62e382f85dce3bc43d21a670ab1bd37bef52ee46a0db
MD5 6c1ce650454f7da40de66ffbe4417ce9
BLAKE2b-256 067f4b9845a04ee5bc31635ab13fc9dd2923bbf61beca4a94e6cc2fac8c9b007

See more details on using hashes here.

File details

Details for the file cbpr_usage_rules-0.6.0-py3-none-any.whl.

File metadata

File hashes

Hashes for cbpr_usage_rules-0.6.0-py3-none-any.whl
Algorithm Hash digest
SHA256 8d943c3954d0af76daedca7051d2035de41f4e56eab821f63a1e4a6c11fb83e3
MD5 b3ccb94ba192b23ff158bfeab1dc334b
BLAKE2b-256 4ed9b4c66928712eff039eccde16314cf12da1c3ed62114536bf714105e0b8c8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page