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_typeparameter, 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:
- Mandatory element presence: GrpHdr/MsgId, GrpHdr/CreDtTm, GrpHdr/NbOfTxs, GrpHdr/CtrlSum are all required; missing any raises a validation error.
- Type constraints: IBAN format, BIC format (8 or 11 characters), amount precision (maximum 18 digits, 5 decimal places).
- Cardinality: at least one
CdtTrfTxInfperPmtInf; at least onePmtInfper document. - Enumeration values:
PmtMtdmust beTRFfor credit transfers;Ccymust be a valid ISO 4217 currency code.
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 #
- 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
- SWIFT. CBPR+ Usage Guidelines — Customer Credit Transfer Initiation (pain.001). SWIFT Standards, 2023. https://www.swift.com/standards/iso-20022/cbpr-plus-usage-guidelines
- ISO. ISO 20022 — Financial services — Universal financial industry message scheme. ISO.org, 2023. https://www.iso20022.org/
- Rousseau, S. pain001 — ISO 20022 payment file generator. GitHub, 2023. https://github.com/sebastienrousseau/pain001
Last reviewed .
