# Bank Statement Formats & Reconciliation Research Research for Phase 6 (bank reconciliation): parsing bank statements (camt.053, MT940) and matching statement lines against ledger cash movements. All findings below are sourced from web research performed on **2026-08-05**; nothing is from memory. --- ## 1. ISO 20022 camt.053 — BankToCustomerStatement camt.053 is the ISO 20022 XML end-of-day bank account statement (the "MX" successor to MT940). It is part of the Bank-to-Customer Cash Management message set maintained by the ISO 20022 Registration Authority (iso20022.org). ### 1.1 Document root and namespace - Root element: `` with a version-specific default namespace: `urn:iso:std:iso:20022:tech:xsd:camt.053.001.NN` (e.g. `camt.053.001.02`, `camt.053.001.08`). **The version is identified via the XML namespace**, so the parser must read the namespace, not assume one. - Inside: `` (BankToCustomerStatement), containing one `` and one or more `` blocks (one per account/period). ### 1.2 Structure (elements the AIR parser must handle) ``` Document @xmlns = urn:iso:std:iso:20022:tech:xsd:camt.053.001.NN └── BkToCstmrStmt ├── GrpHdr │ ├── MsgId unique message id │ └── CreDtTm file creation timestamp (ISO 8601) └── Stmt [1..n] one per account statement ├── Id statement id ├── ElctrncSeqNb electronic sequence number (optional) ├── CreDtTm statement creation timestamp ├── Acct │ ├── Id/IBAN or Id/Othr/Id account identifier (IBAN or domestic) │ └── Ccy account currency (ISO 4217) ├── Bal [1..n] balances │ ├── Tp/CdOrPrtry/Cd OPBD=opening booked, CLBD=closing booked, │ │ CLAV=closing available, OIBD/CIBD=interim (multi-page) │ ├── Amt @Ccy decimal amount, currency as XML attribute │ ├── CdtDbtInd CRDT | DBIT (sign of the balance) │ └── Dt/Dt balance date └── Ntry [0..n] statement entries (transactions) ├── NtryRef entry reference (optional) ├── Amt @Ccy ALWAYS positive decimal; currency attribute mandatory ├── CdtDbtInd CRDT (money in) | DBIT (money out) — gives the sign ├── RvslInd true if this entry reverses a previous one (optional) ├── Sts BOOK (booked) | PDNG (pending) | INFO ├── BookgDt/Dt (or /DtTm) booking (posting) date ├── ValDt/Dt (or /DtTm) value date ├── AcctSvcrRef bank's own reference (optional) ├── BkTxCd bank transaction code (Domn/Fmly/SubFmlyCd or Prtry/Cd) ├── NtryDtls [0..n] │ ├── Btch batch info (NbOfTxs, TtlAmt) when 1 entry = n transactions │ └── TxDtls [0..n] underlying transactions │ ├── Refs/EndToEndId payer-assigned end-to-end reference (invoice/PO ref) │ ├── Refs/TxId, InstrId, MndtId │ ├── AmtDtls InstdAmt / TxAmt (useful for FX) │ ├── RltdPties Dbtr / Cdtr names and accounts │ └── RmtInf/Ustrd unstructured remittance info (free text) │ RmtInf/Strd structured remittance (creditor reference etc.) └── AddtlNtryInf free-text description of the entry ``` Key semantics: - **`Amt` carries no sign**; direction comes exclusively from `CdtDbtInd` (`CRDT` = credit to the account = money received; `DBIT` = money out). - The currency is the mandatory `Ccy` **attribute** on `Amt`. - One `Ntry` may aggregate many underlying transactions (batch/lump-sum entries): `NtryDtls/Btch` + repeated `TxDtls`. This is the structural basis for one-to-many reconciliation matching. - `EndToEndId` (max 35 chars) travels unaltered from the payment initiation (pain.001) to the statement — it is the best deterministic matching key. ### 1.3 Version differences (brief) - Common versions in the wild: `.02` (2009 base, still the most widely delivered by banks, e.g. Nordea, Dutch banks), `.06`, `.08` (2019 — the CBPR+/SEPA-aligned version banks are converging on), `.10`/`.11`/`.13` (latest, Feb 2025). - Newer versions mostly **add optional fields without removing existing ones**; the structural core (`GrpHdr` / `Stmt` / `Bal` / `Ntry`) is stable across versions. Notable mechanical difference: in `.02` `Sts` is a simple code (`BOOK`); in `.08+` it becomes a complex element (`BOOK`). Party identification blocks also gain structure in `.08+`. - Practical parser strategy (used by open-source parsers): parse namespace-tolerantly, target the common core, handle the `Sts` shape difference. Sources: iso20022.org Message Definition Report Part 2 (Bank-to-Customer Cash Management), Payments Canada camt.053.001.08 usage guideline, Nordea camt.053.001.02 standard, ValidateFin structural guide, darko-mijic camt.053 parser spec (URLs in §7). --- ## 2. MT940 — SWIFT Customer Statement Message MT940 is the legacy SWIFT FIN "MT" end-of-day customer statement: a line/tag-based text format. Still extremely widespread in bank connectivity (EBICS, host-to-host, e-banking exports) even though it is deprecated by SWIFT (see §3). ### 2.1 Tag structure | Tag | Name | Format | Notes | |---|---|---|---| | `:20:` | Transaction Reference Number | 16x | Sender's reference for the message | | `:25:` | Account Identification | 35x | Account number (option `P` adds BIC) | | `:28C:` | Statement Number/Sequence | 5n[/5n] | e.g. `151/1` | | `:60F:` | Opening Balance (First) | 1!a6!n3!a15d | D/C mark + date YYMMDD + currency + amount | | `:61:` | Statement Line | see §2.2 | Repeats per transaction | | `:86:` | Information to Account Owner | 6*65x | Optional; up to 6 lines × 65 chars; must follow its `:61:` | | `:62F:` | Closing Balance (Final) | 1!a6!n3!a15d | Same layout as `:60F:` | | `:60M:`/`:62M:` | Intermediate opening/closing balances | same | Used when a statement spans multiple messages | | `:64:`/`:65:` | Closing available / forward available balance | same layout | Optional | Constraint: a `:86:` must be preceded by a `:61:` and belongs to it; when a period spans several messages, all but the last carry `:62M:` and the last carries `:62F:`. ### 2.2 The `:61:` statement line — full format specification ``` :61: 6!n [4!n] 2a [1!a] 15d 1!a3!c 16x [//16x] [34x] │ │ │ │ │ │ │ │ └─ supplementary details (optional, new line) │ │ │ │ │ │ │ └─ bank reference (optional, after //) │ │ │ │ │ │ └─ customer reference (16x; NONREF if none) │ │ │ │ │ └─ transaction type code: 1 letter + 3 chars │ │ │ │ │ S103 = via SWIFT MT103, NTRF = non-SWIFT transfer, │ │ │ │ │ NMSC = misc, NCHK = cheque, NDDT = direct debit... │ │ │ │ └─ amount: max 15 digits, COMMA as decimal separator, │ │ │ │ no thousands separators, ALWAYS UNSIGNED │ │ │ └─ funds code (optional, 3rd char of currency) │ │ └─ debit/credit mark: D | C | RD (reversal of debit) | RC (reversal of credit) │ └─ entry (booking) date MMDD (optional; year inferred from value date) └─ value date YYMMDD ``` Key semantics: - **Amount is unsigned; the D/C mark gives direction** (D = debit = money out, C = credit = money in; `RD`/`RC` mark reversals). - **Decimal separator is a comma** (`1150,00`), never a dot; the currency is NOT on the `:61:` line — it comes from the `:60F:`/`:62F:` balance lines. - Value date is `YYMMDD` (2-digit year → pivot logic needed); entry date is `MMDD` with the year inferred from the value date (beware year boundaries). - The customer reference (`16x`) is the counterpart of camt's `EndToEndId`, but is often `NONREF`; the free-text `:86:` line then carries the useful description (bank-proprietary sub-formats exist inside `:86:`, e.g. German `/GVC/` and `?20`-style subtags — treat `:86:` as opaque text in v1). - Integrity check available to the parser: `:60F: balance ± Σ(:61: lines) = :62F: balance` — AIR should verify this. Sources: Paiementor MT940 detailed analysis, National Bank of Canada MT940 user guide, Citi MT940 export guide, Huntington developer portal (URLs in §7). --- ## 3. Adoption status: camt.053 vs MT940 (MT→MX migration) - **22 November 2025**: SWIFT ended MT/ISO 20022 coexistence for cross-border *payment instruction* messages (MT103/MT202 retired for FI-to-FI traffic). - **Cash-management / reporting messages (MT9xx) got an extended timeline**: MT940/MT942/MT950/MT900/MT910 are *deprecated and no longer maintained* by SWIFT but not yet withdrawn; their removal in favour of camt.052/camt.053/camt.054 is being phased in roughly **2027–2028**, and major banks tell clients they must be able to *receive* camt messages by **November 2027**. - Practical consequence (2026): **both formats are current reality.** camt.053 is the strategic/current standard; MT940 remains the most widely deployed corporate statement format worldwide (bank portals, ERP integrations, EBICS). → AIR must parse **both**, normalizing into one internal model (§6). Sources: SWIFT ISO 20022 programme pages, J.P. Morgan and Citi migration FAQs, RedCompass Labs 2026 deadline analysis, PaymentExpert on the Nov-2025 cutover (URLs in §7). --- ## 4. Bank reconciliation matching practice How mainstream systems match statement lines to ledger (book) cash entries: - **Exact match first**: same amount, same currency, same/near date. This is the universal baseline (QuickBooks Online "matches", Xero "suggested matches"). - **Date tolerance windows**: book date and bank date rarely coincide (cheques clear days later; card settlements lag 1–3 days). Treasury/ERP tools (e.g. Oracle Fusion reconciliation) expose *configurable* date tolerance rules; date tolerances exist mainly for instruments with clearing lag. - **Amount tolerance**: used for FX rounding differences or bank fees embedded in the statement line. QuickBooks does **not** natively do tolerance-based matching; Oracle-class treasury tools do (with a write-off/adjustment posting for the difference). Xero handles the fee case via "adjustments" during reconciliation. - **Reference matching**: strongest deterministic signal — cheque number, invoice number in remittance text, and in camt.053 the `EndToEndId` / `AcctSvcrRef`. Xero bank rules can condition on bank text fields, direction ("Received"/"Spent") and amount; a rule sets contact/account/tax code. - **One-to-many / many-to-one (batch) matching**: one bank deposit = many customer receipts (or one payroll debit = many payslips). Xero supports "Match > multiple items"; QuickBooks Online is weak here natively; camt.053 expresses it structurally (`NtryDtls/Btch` + several `TxDtls`). Treasury reconciliation engines support 1:1, 1:n, n:1 and n:m rule tiers, applied in priority order (exact reference → exact amount+date → amount+date window → aggregate/batch), each with confidence scoring. - **Output convention**: after matching, both sides' residues are reported — unmatched statement lines (bank has it, books don't: missing entry / fee / fraud) and unmatched ledger entries (books have it, bank doesn't: outstanding cheque / deposit in transit). This two-sided exception report *is* the classical bank reconciliation statement. Sources: Oracle Fusion tolerance-rule docs, Xero reconciliation guides, QuickBooks Online reconciliation guides (URLs in §7). --- ## 5. Aggregator APIs (future ingestion frontends) ### 5.1 Plaid (`/transactions/sync`, `/transactions/get`) JSON transaction objects; key fields: - `amount` — decimal number. **Sign convention (Plaid docs): "Positive values when money moves out of the account; negative values when money moves in."** I.e. a debit-card purchase is **positive**, a deposit/refund is **negative** — the *inverse* of a naive signed-bank-balance convention. Two decimal places. - `iso_currency_code` (ISO 4217; null when `unofficial_currency_code` is used). - `date` (YYYY-MM-DD; posting date for posted transactions, occurrence date for pending), plus `authorized_date` / `datetime` / `authorized_datetime`. - `name` (raw description) and `merchant_name` (Plaid-enriched, cleaner). - `pending` (bool — details may change at settlement; the posted transaction arrives with a new `transaction_id` and a `pending_transaction_id` link). - `transaction_id` (unique, case-sensitive), `payment_channel` (`online` / `in store` / `other`), `personal_finance_category`. ⚠ Plaid delivers `amount` as a JSON **number** → the AIR parser must decode it via decimal-preserving parsing (e.g. `json.loads(..., parse_float=Decimal)`), never through binary floats. ### 5.2 Flinks (Canadian aggregator, `/GetAccountsDetail`) Transactions array per account; each item: ```json { "Date": "2025-01-31", "Code": null, "Description": "PAYROLL - Stripe Paycheck", "Debit": 1000.4, "Credit": 1500.25, "Balance": 5105.6, "Id": "94584aed-7c98-42a4-9836-9f8557db63f5" } ``` - Direction is expressed by **separate `Debit` / `Credit` fields** (one populated, the other null in real responses), not a signed amount — a third sign convention to normalize. `Balance` is the running balance after the transaction; `Id` is Flinks' unique transaction id. - Same JSON-number caveat as Plaid: parse into Decimal, never float. Sources: Plaid Transactions API reference, Flinks GetAccountsDetail docs (URLs in §7). --- ## 6. Example fixtures (small, structurally correct) Both fixtures describe the same statement: CAD account, opening balance 25,000.00, one outgoing supplier payment of 1,150.00 on 2026-08-01, one incoming customer payment of 3,449.93 on 2026-08-04, closing balance 27,299.93 (25,000.00 − 1,150.00 + 3,449.93 = 27,299.93 ✓). ### 6.1 camt.053 (version 053.001.02) ```xml AIR-STMT-20260805-001 2026-08-05T06:00:00 STMT-2026-0151 151 2026-08-05T06:00:00 00112233445 CAD OPBD 25000.00 CRDT
2026-07-31
CLBD 27299.93 CRDT
2026-08-04
BKREF001 1150.00 DBIT BOOK
2026-08-01
2026-08-01
NTRF INV-2026-0042 PAYMENT ACME INC INVOICE INV-2026-0042 Supplier payment Acme Inc.
BKREF002 3449.93 CRDT BOOK
2026-08-04
2026-08-04
NTRF E2E-SALE-7781 CUSTOMER CUST 123 SALE 7781 Customer payment, sale 7781
``` (For `.08+` fixtures the only mechanical change in these fields is `BOOK` and the namespace suffix.) ### 6.2 MT940 ``` :20:AIR-STMT-0805 :25:BOFCCAM2/00112233445 :28C:151/1 :60F:C260731CAD25000,00 :61:2608010801D1150,00NTRFINV-2026-0042//BKREF001 :86:PAYMENT ACME INC INVOICE INV-2026-0042 :61:2608040804C3449,93NTRFE2E-SALE-7781//BKREF002 :86:CUSTOMER CUST 123 SALE 7781 :62F:C260804CAD27299,93 ``` Reading `:61:2608010801D1150,00NTRFINV-2026-0042//BKREF001`: value date `260801` (2026-08-01), entry date `0801`, `D` = debit, amount `1150,00` (comma decimal), transaction type `NTRF`, customer reference `INV-2026-0042`, bank reference `BKREF001`. --- ## 7. Design decisions for AIR 1. **Common internal model.** Both parsers (and future Plaid/Flinks ingesters) produce the same `BankTransaction`: - `date` (booking date, ISO date; value date kept separately if present) - `amount` — **signed decimal serialized as a string** (positive = money in to the account, negative = money out; AIR convention, normalized from CdtDbtInd / D-C mark / Plaid inverse sign / Flinks Debit-Credit columns) - `currency` (ISO 4217; from `Amt@Ccy` in camt, from `:60F:` in MT940) - `description` (from `AddtlNtryInf`/`RmtInf/Ustrd` or `:86:`) - `reference` (from `EndToEndId` / `NtryRef` / `:61:` customer+bank refs; `NONREF` normalized to null) - plus provenance: source format, statement id, raw line/entry. 2. **Amounts are never floats.** camt/MT940 amounts are parsed from text into `Decimal`; MT940 comma decimals converted textually (`1150,00` → `1150.00`); aggregator JSON parsed with `parse_float=Decimal`. 3. **Statement integrity check at parse time**: opening balance + Σ signed entries must equal closing balance (`:60F:`/`:62F:`, `OPBD`/`CLBD`); mismatch is a compiler-grade diagnostic, not a warning. 4. **Matching algorithm (v1)**: match = **amount + currency exact**, with a **configurable date tolerance window** (default e.g. ±3 days, policy-driven via ALSL, never hard-coded); reference/`EndToEndId` equality is used first as a higher-priority deterministic tier and as a tie-breaker when several candidates share amount+date. One-to-many (batch) matching deferred to a later iteration but the model keeps `TxDtls` multiplicity so it stays possible. 5. **Two-sided residue reporting**: the reconciliation result lists matched pairs *and* unmatched items on both sides (unmatched bank lines, unmatched ledger entries), mirroring standard reconciliation-statement practice. 6. **Version-tolerant camt parser**: read the namespace to detect the version, target the stable core (§1.2), handle the `.02` vs `.08` `Sts` shape. 7. **Both formats are required** (§3): camt.053 is strategic, MT940 remains ubiquitous until at least 2027-2028. --- ## 8. Sources (all consulted 2026-08-05) ### camt.053 / ISO 20022 - ISO 20022 MDR Part 2 — Bank-to-Customer Cash Management (iso20022.org): https://www.iso20022.org/sites/default/files/2020-12/ISO20022_MDRPart2_BankToCustomerCashManagement_2020_2021_v1_ForSEGReview.pdf - Payments Canada — Usage Guideline camt.053.001.08: https://www.payments.ca/sites/default/files/Bank%20to%20customer%20statement%20V08%20(camt.053.001.08)(pdf).pdf - Nordea — camt.053.001.02 Account Statement Standard: https://www.nordea.com/en/doc/caar-camt-053-001-02-account-statement-standard.pdf - Betaalvereniging Nederland — IG Bank-to-Customer Statement camt.053: https://www.betaalvereniging.nl/wp-content/uploads/2026/03/IG-Bank-to-Customer-Statement-CAMT-053-v1-1.pdf - ValidateFin — Reading a camt.053 bank statement: https://validatefin.com/en/blog/camt053-bank-statement - darko-mijic — iso-20022-camt-053-parser spec: https://github.com/darko-mijic/iso-20022-camt-053-parser/blob/main/docs/iso20022-camt-053-spec.md - Huntington Developer Portal — CAMT.053: https://developer.huntington.com/enterprisepayments/docs/camt053 ### MT940 - Paiementor — SWIFT MT940 Customer Statement detailed analysis: https://www.paiementor.com/swift-mt940-customer-statement-detailed-analysis/ - National Bank of Canada — MT940/MT101 user guide: https://www.nbc.ca/content/dam/bnc/outils-apps/entreprises/guides/user-guide-mt940-mt101.pdf - Citi Handlowy — SWIFT MT940 Export User Guide: https://www.citibank.pl/poland/files/SWIFT_MT940_Export_User_Guide_EN.pdf - Huntington Developer Portal — SWIFT MT940: https://developer.huntington.com/enterprisepayments/docs/swift-mt-940 - Danske Bank — MT940 with structured :86: https://danskeci.com/-/media/pdf/danskeci-com/swift-mt/reconciliation/mt940_structured.pdf ### MT → MX migration - SWIFT — ISO 20022 for Financial Institutions: https://www.swift.com/standards/iso-20022/iso-20022-financial-institutions-focus-payments-instructions - J.P. Morgan — ISO 20022 Migration guidance: https://www.jpmorgan.com/insights/payments/fx-cross-border/iso-20022-migration - Citi — ISO 20022 Migration FAQ: https://www.citibank.com/tts/sa/iso-20022-migration/assets/docs/ISO-20022-FAQs.pdf - RedCompass Labs — ISO 20022 deadlines in 2026 onward: https://www.redcompasslabs.com/insights/what-now-iso-20022-deadlines-in-2026-onwards/ - PaymentExpert — Swift's ISO 20022 cutover (Nov 2025): https://paymentexpert.com/2025/11/21/swifts-iso-20022-cutover-the-end-of-mt-and-a-20-year-promise/ - ING — FAQ Swift ISO 20022: https://www.ingwb.com/en/service/payments-and-collections/swift-iso20022/faq-swift-iso-20022 ### Reconciliation practice - Oracle Fusion Cloud Financials — Overview of Tolerance Rules: https://docs.oracle.com/en/cloud/saas/financials/25d/fairp/overview-of-tolerance-rules.html - FHP Accounting — Xero bank reconciliation rules & cash coding: https://fhpaccounting.co.uk/xero-bank-reconciliation-like-a-pro-master-rules-cash-coding-and-error-prevention-techniques/ - The IQ Suite — Bank reconciliation in QuickBooks Online / Xero guides: https://bankreconciler.app/blogQuickBooksReconciliation , https://bankreconciler.app/blogXeroReconciliation ### Aggregator APIs - Plaid — Transactions API reference: https://plaid.com/docs/api/products/transactions/ - Flinks — /GetAccountsDetail: https://docs.flinks.com/api/connect/endpoints/account-linking/get-accounts-detail - Flinks — Data types in GetAccountsDetail: https://help.flinks.com/support/solutions/articles/43000705311-data-types-in-getaccountsdetail --- *End of research note — AIR project — Simon-Pierre Boucher — contact@spboucher.ai*