ex_iso20022

Hex.pmDocsCILicense: MIT

ISO 20022 message parsing for Elixir. Currently covers camt.053 (Bank to Customer Statement), the highest-demand message type. More message types are planned.

Installation

# mix.exs
def deps do
  [
    {:ex_iso20022, "~> 0.1"}
  ]
end

Quick start

xml = File.read!("statement.xml")

case ISO20022.Camt053.parse(xml) do
  {:ok, doc} ->
    Enum.each(doc.statements, fn stmt ->
      IO.puts("Account IBAN : #{stmt.account.iban}")
      IO.puts("Currency     : #{stmt.account.currency}")

      closing = Enum.find(stmt.balances, &(&1.type == :closing_booked))
      IO.puts("Closing bal  : #{closing.amount} #{closing.currency} (#{closing.credit_debit})")

      Enum.each(stmt.entries, fn entry ->
        IO.puts("  #{entry.ref}  #{entry.credit_debit}  #{entry.amount} #{entry.currency}")
      end)
    end)

  {:error, reason} ->
    IO.inspect(reason, label: "parse error")
end

Using the bang variant when you are confident the input is valid:

doc = ISO20022.Camt053.parse!(xml)

Supported message types

Module Message Versions
ISO20022.Camt053 Bank to Customer Statement camt.053.001.02 – 014

More message types (camt.052, camt.054, pain.001, pacs.008, …) will be added in subsequent releases. The top-level ISO20022.parse/1 dispatcher is already in place and will route to the right module automatically once each type is implemented.

Struct reference

ISO20022.Camt053.Document

The top-level struct returned by parse/1.

Field Type Description
group_headerGroupHeader Message-level metadata
statements[Statement] One entry per account per period

ISO20022.Camt053.GroupHeader

Field Type Description
message_idString Unique message identifier (max 35 chars)
created_atDateTime UTC creation timestamp
paginationmap | nil%{page_number: String, last_page: boolean}

ISO20022.Camt053.Statement

Field Type Description
idString Statement identifier
electronic_seq_numberinteger | nil Sequence number for gap detection
created_atDateTime | nil Statement generation time
from_to_datemap | nil%{from: DateTime, to: DateTime}
accountAccount Account identification
balances[Balance] At least opening and closing booked balances
entries[Entry] Booked transactions

ISO20022.Camt053.Account

Field Type Notes
ibanString | nil Present when account is IBAN-identified
other_idString | nil Present when account uses a non-IBAN scheme
other_schemeString | nil Scheme code, e.g. "BBAN"
currencyString | nil ISO 4217 alpha code, e.g. "EUR"
servicer_bicString | nil BIC of the account-servicing institution
nameString | nil Account name

ISO20022.Camt053.Balance

amount is always a positive Decimal. The sign is expressed through credit_debit.

Field Type Description
type atom See balance types below
amountDecimal Positive amount
currencyString ISO 4217 alpha code
credit_debit:credit | :debit:debit means overdraft
dateDate Balance reference date

Balance type atoms:

Atom ISO code Meaning
:opening_bookedOPBD Opening booked (mandatory)
:closing_bookedCLBD Closing booked (mandatory)
:closing_availableCLAV Closing available
:interim_bookedITBD Interim booked
:interim_availableITAV Interim available
:forward_availableFWAV Forward available
{:other, code} any Unrecognised code

ISO20022.Camt053.Entry

Field Type Description
refString Bank-assigned entry reference
amountDecimal Positive amount
currencyString ISO 4217 alpha code
credit_debit:credit | :debit Direction
reversalbooleantrue if this cancels a prior entry
status:booked Always :booked in camt.053
booking_dateDate | nil Date posted to account
value_dateDate | nil Value date (may differ from booking date)
account_servicer_refString | nil Bank's own reference
bank_transaction_codeBankTxCode | nil ISO 20022 transaction classification
additional_infoString | nil Free-text entry description
details[EntryDetails] Transaction-level detail blocks (batch entries)

ISO20022.Camt053.BankTxCode

Field Type Example
domainString | nil"PMNT"
familyString | nil"RCDT", "ICDT", "RDDT"
sub_familyString | nil"XBCT", "ESCT", "SALA"
proprietary_codeString | nil Bank-specific code
proprietary_issuerString | nil Issuer of the proprietary code

ISO20022.Camt053.EntryDetails

Present when an entry groups multiple underlying transactions (batch payments).

Field Type Description
batchmap | nil%{message_id, payment_info_id, number_of_transactions, total_amount}
transaction_details[TransactionDetails] Individual transaction records

ISO20022.Camt053.TransactionDetails

Field Type Description
refsmap | nil%{message_id, end_to_end_id, uetr, …}
amountDecimal | nil Individual transaction amount
currencyString | nil ISO 4217
credit_debit:credit | :debit | nil Direction
related_partiesmap | nil%{debtor, creditor, ultimate_debtor, ultimate_creditor}
related_agentsmap | nil%{debtor_agent, creditor_agent}
remittance_info tuple / nil {:unstructured, text} or {:structured, %{ref, ref_type, …}}
purposeString | nil ISO 20022 purpose code

Error handling

{:ok, %ISO20022.Camt053.Document{}}

# Malformed XML
{:error, {:parse_error, reason}}

# Namespace not recognised as a camt.053 variant
{:error, {:unsupported_version, "urn:iso:std:iso:20022:tech:xsd:pain.001.001.09"}}

# Mandatory field absent from an otherwise valid document
{:error, {:missing_required_field, [:group_header, :message_id]}}
{:error, {:missing_required_field, [:statements, 0, :id]}}

# Field present but value could not be parsed
{:error, {:invalid_amount, "N/A", [:statements, 0, :entries, 1, :amount]}}
{:error, {:invalid_date, "32-01-2024"}}

The path in missing_required_field and invalid_* errors follows the struct hierarchy so it is straightforward to pinpoint the problematic node.

Multi-version support

Real-world bank files use a wide range of schema versions:

urn:iso:std:iso:20022:tech:xsd:camt.053.001.02   # many European banks
urn:iso:std:iso:20022:tech:xsd:camt.053.001.04   # UK, SEPA migration era
urn:iso:std:iso:20022:tech:xsd:camt.053.001.08   # current SWIFT / TARGET2
urn:iso:std:iso:20022:tech:xsd:camt.053.001.11   # newer implementations
urn:iso:std:iso:20022:tech:xsd:camt.053.001.14   # latest ISO (2026)

ex_iso20022 detects the version from the root element's xmlns attribute and normalises to the same struct regardless of input version. All versions 02 – 14 are accepted. Documents lacking a namespace (some older senders) are also accepted.

License

MIT