Sebastien Rousseau

Automating ISO 20022 Payment Files Creation with pain001

ISO 20022 payment automation and wholesale-payments engineering with pain001.

6 min read
Banner for: Automating ISO 20022 Payment Files Creation with pain001

Executive Summary / Key Takeaways

  • ISO 20022 pain.001 (CustomerCreditTransferInitiation) is the structured XML message format used to initiate credit transfers under SEPA (EPC SCT rulebook) and CBPR+ (SWIFT's cross-border messaging standard, mandatory for correspondent banks from November 2025).
  • pain001 ⧉ reads payment data from CSV or SQLite, maps rows to the pain.001.001.09 message hierarchy (GrpHdr → PmtInf → CdtTrfTxInf), and renders a conformant XML file via a templated generator — three lines of Python from data to validated XML.
  • XSD validation runs on every generated file before output is written; the library raises a descriptive exception identifying the failing element, cardinality, or type mismatch, so errors are caught at generation time rather than at bank submission.
  • CtrlSum and NbOfTxs are computed from the transaction set, not entered manually — eliminating the single most common payment file rejection cause at SEPA and CBPR+ processing gateways.
  • Both SEPA Credit Transfer (EUR, within the SEPA zone) and CBPR+ (cross-border, multi-currency) message variants are supported through the message_type parameter, with field-level validation differences handled internally.

pain001 ⧉ is an open-source Python library for generating ISO 20022 payment initiation files. It reads payment data from a structured input (CSV or SQLite), validates the data, renders a conformant pain.001.001.09 XML document, and validates the output against the ISO 20022 XSD schema — all in a single function call.

This article describes the ISO 20022 pain.001 message structure, how pain001 maps input data to message elements, the validation pipeline, and the SEPA versus CBPR+ configuration options.

ISO 20022 pain.001 Message Structure #

The ISO 20022 pain.001.001.09 (CustomerCreditTransferInitiation) message has three levels:

GrpHdr (Group Header) — one per file:

Element Description Example
MsgId Unique message identifier ACME20240115-001
CreDtTm Creation date and time 2024-01-15T09:00:00
NbOfTxs Total number of transactions 3
CtrlSum Sum of all instructed amounts 15000.00
InitgPty/Nm Initiating party name Acme Corp

PmtInf (Payment Information) — one or more per file, groups transactions by debtor account and payment date:

Element Description
PmtInfId Payment information identifier
PmtMtd Payment method — always TRF for credit transfer
ReqdExctnDt/Dt Requested execution date
Dbtr/Nm Debtor (sender) name
DbtrAcct/Id/IBAN Debtor IBAN
DbtrAgt/FinInstnId/BICFI Debtor bank BIC

CdtTrfTxInf (Credit Transfer Transaction Information) — one or more per PmtInf block:

Element Description
PmtId/EndToEndId End-to-end reference (preserved through the chain)
Amt/InstdAmt Instructed amount with currency attribute
CdtrAgt/FinInstnId/BICFI Creditor bank BIC
Cdtr/Nm Creditor (receiver) name
CdtrAcct/Id/IBAN Creditor IBAN
RmtInf/Ustrd Unstructured remittance information (invoice reference etc.)

Generating XML from CSV #

A minimal pain001 invocation:

from pain001 import create_xml_v9

create_xml_v9(
    data_file="payments.csv",
    data_file_type="csv",
    xml_file_path="output/pain001.xml"
)

The CSV file maps column names to message fields. A minimal example:

id,date,nb_of_txs,ctrl_sum,initiating_party_name,debtor_name,debtor_account_IBAN,debtor_agent_BIC,creditor_name,creditor_account_IBAN,creditor_agent_BIC,instd_amt,instd_amt_ccy,end_to_end_id,remittance_info
1,2024-01-15,1,1000.00,Acme Corp,Acme Corp,GB29NWBK60161331926819,NWBKGB2L,Supplier Ltd,DE89370400440532013000,COBADEFFXXX,1000.00,EUR,ACME20240115001,INV-2024-0042

The library reads ctrl_sum and nb_of_txs from the CSV row for single-row files. For multi-row files (multiple transactions in one batch), pain001 computes these values from the transaction set rather than trusting the input values, which prevents mismatches.

The SQLite interface uses the same column-name convention. Pass data_file_type="sqlite" and the data_file path to a SQLite database file; pain001 reads the payment table by default.

Generated XML Structure #

A correctly rendered pain.001.001.09 document for the CSV row above:

<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09">
  <CstmrCdtTrfInitn>
    <GrpHdr>
      <MsgId>ACME20240115-001</MsgId>
      <CreDtTm>2024-01-15T09:00:00</CreDtTm>
      <NbOfTxs>1</NbOfTxs>
      <CtrlSum>1000.00</CtrlSum>
      <InitgPty><Nm>Acme Corp</Nm></InitgPty>
    </GrpHdr>
    <PmtInf>
      <PmtInfId>ACME20240115-PMT-001</PmtInfId>
      <PmtMtd>TRF</PmtMtd>
      <ReqdExctnDt><Dt>2024-01-16</Dt></ReqdExctnDt>
      <Dbtr><Nm>Acme Corp</Nm></Dbtr>
      <DbtrAcct><Id><IBAN>GB29NWBK60161331926819</IBAN></Id></DbtrAcct>
      <DbtrAgt><FinInstnId><BICFI>NWBKGB2L</BICFI></FinInstnId></DbtrAgt>
      <CdtTrfTxInf>
        <PmtId><EndToEndId>ACME20240115001</EndToEndId></PmtId>
        <Amt><InstdAmt Ccy="EUR">1000.00</InstdAmt></Amt>
        <CdtrAgt><FinInstnId><BICFI>COBADEFFXXX</BICFI></FinInstnId></CdtrAgt>
        <Cdtr><Nm>Supplier Ltd</Nm></Cdtr>
        <CdtrAcct><Id><IBAN>DE89370400440532013000</IBAN></Id></CdtrAcct>
        <RmtInf><Ustrd>INV-2024-0042</Ustrd></RmtInf>
      </CdtTrfTxInf>
    </PmtInf>
  </CstmrCdtTrfInitn>
</Document>

XSD Validation Pipeline #

After rendering, pain001 validates the output against the ISO 20022 pain.001.001.09 XSD schema. Validation checks:

When validation fails, pain001 raises a ValidationError with the lxml error message identifying the failing XPath expression, element name, and constraint. This surfaces misconfigurations at generation time rather than at bank submission, where rejection codes are typically less descriptive.

SEPA vs CBPR+ Configuration #

SEPA Credit Transfer (ISO 20022 pain.001.001.09 under the EPC SCT rulebook) and CBPR+ (SWIFT's Cross-Border Payments and Reporting Plus standard) use the same message schema but differ in mandatory field sets and value constraints:

Aspect SEPA SCT CBPR+
Currency EUR only Multi-currency
IBAN mandatory Yes Yes (creditor)
BIC mandatory No (SEPA zone routing) Yes
Charge bearer (ChrgBr) SLEV DEBT, CRED, or SHAR
Scope SEPA zone (36 countries) Global correspondent banking

Configure the message type via the payment_initiation_message_type parameter:

create_xml_v9(
    data_file="payments.csv",
    data_file_type="csv",
    xml_file_path="output/pain001.xml",
    payment_initiation_message_type="pain.001.001.09"  # default; also accepts "pain.001.001.03" for legacy SEPA
)

CBPR+ compliance became mandatory for SWIFT correspondent banking in November 2023 for inbound messages and November 2025 for outbound. Generating CBPR+-conformant pain.001 files requires that the BIC field is populated and that the ChrgBr element is present.

Frequently Asked Questions #

What is the difference between pain.001 and pain.008? pain.001 (CustomerCreditTransferInitiation) initiates a credit transfer — the sender's bank debits the sender's account and credits the receiver. pain.008 (CustomerDirectDebitInitiation) initiates a direct debit — the creditor's bank collects funds from the debtor. pain001 the library generates pain.001 files only.

Which ISO 20022 version does pain001 target? The primary target is pain.001.001.09, the version required for CBPR+ and mandated by the EPC for new SEPA implementations. The library also supports pain.001.001.03 (the legacy SEPA version) via the payment_initiation_message_type parameter for organisations still using older bank interfaces.

Can pain001 handle multiple debtor accounts in a single file? Yes. Multiple PmtInf blocks with different debtor IBANs can be produced by grouping CSV rows with different debtor account values. pain001 creates one PmtInf block per unique (debtor IBAN, execution date) combination, with all matching transactions nested as CdtTrfTxInf children.

What happens when XSD validation fails? pain001 raises a pain001.exceptions.ValidationError with the lxml validation message. The XML file is not written to disk when validation fails, so only valid files reach the output path. Common failure causes are: IBAN in wrong format, BIC not 8 or 11 characters, currency code not in ISO 4217, or missing mandatory elements when a required CSV column is absent.

References #

  1. European Payments Council. SEPA Credit Transfer Scheme Customer-to-Bank Implementation Guidelines (v1.1). EPC, 2023. https://www.europeanpaymentscouncil.eu/document-library/implementation-guidelines/sepa-credit-transfer-scheme-customer-bank
  2. SWIFT. CBPR+ Usage Guidelines — Customer Credit Transfer Initiation (pain.001). SWIFT Standards, 2023. https://www.swift.com/standards/iso-20022/cbpr-plus-usage-guidelines
  3. ISO. ISO 20022 — Financial services — Universal financial industry message scheme. ISO.org, 2023. https://www.iso20022.org/
  4. Rousseau, S. pain001 — ISO 20022 payment file generator. GitHub, 2023. https://github.com/sebastienrousseau/pain001

Last reviewed .