If you're building a payment system for European banks, at some point you'll need to generate a SEPA credit transfer file in the pain.001 format. The spec is 200+ pages. The XSD validation errors are cryptic. And most tools that handle this cost money.
Here's a practical guide to generating and validating pain.001 files - with a free tool and no backend required.
What is SEPA pain.001?
pain.001 (Payment Initiation) is the ISO 20022 XML message format used to initiate SEPA credit transfers. It's what your ERP or treasury system sends to your bank to trigger batch payments.
Two versions are in active use:
- pain.001.001.03 - the legacy version, still widely supported
- pain.001.001.09 - the current version, required by most European banks since 2023
The key structural difference between them: v09 uses <BICFI> instead of <BIC>, and drops <MsgDefIdr>.
A Minimal pain.001.001.09 File
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:std:iso:20022:tech:xsd:pain.001.001.09">
<CstmrCdtTrfInitn>
<GrpHdr>
<MsgId>MSG-2026-04-19-001</MsgId>
<CreDtTm>2026-04-19T10:00:00</CreDtTm>
<NbOfTxs>1</NbOfTxs>
<CtrlSum>1500.00</CtrlSum>
<InitgPty>
<Nm>Acme Corp</Nm>
</InitgPty>
</GrpHdr>
<PmtInf>
<PmtInfId>PMT-001</PmtInfId>
<PmtMtd>TRF</PmtMtd>
<NbOfTxs>1</NbOfTxs>
<CtrlSum>1500.00</CtrlSum>
<PmtTpInf>
<SvcLvl><Cd>SEPA</Cd></SvcLvl>
</PmtTpInf>
<ReqdExctnDt>
<Dt>2026-04-22</Dt>
</ReqdExctnDt>
<Dbtr>
<Nm>Acme Corp</Nm>
</Dbtr>
<DbtrAcct>
<Id><IBAN>FR7630006000011234567890189</IBAN></Id>
</DbtrAcct>
<DbtrAgt>
<FinInstnId><BICFI>BNPAFRPP</BICFI></FinInstnId>
</DbtrAgt>
<CdtTrfTxInf>
<PmtId>
<EndToEndId>E2E-001</EndToEndId>
</PmtId>
<Amt>
<InstdAmt Ccy="EUR">1500.00</InstdAmt>
</Amt>
<CdtrAgt>
<FinInstnId><BICFI>DEUTDEDB</BICFI></FinInstnId>
</CdtrAgt>
<Cdtr>
<Nm>Supplier GmbH</Nm>
</Cdtr>
<CdtrAcct>
<Id><IBAN>DE89370400440532013000</IBAN></Id>
</CdtrAcct>
<RmtInf>
<Ustrd>Invoice INV-2026-001</Ustrd>
</RmtInf>
</CdtTrfTxInf>
</PmtInf>
</CstmrCdtTrfInitn>
</Document>
Most Common Validation Errors
After processing thousands of pain.001 files, these are the errors that come up most often:
1. Wrong namespace
v03: urn:iso:std:iso:20022:tech:xsd:pain.001.001.03
v09: urn:iso:std:iso:20022:tech:xsd:pain.001.001.09
Mixing them causes immediate rejection.
2. <BIC> vs <BICFI>
In v03, the BIC element is <BIC>. In v09, it's <BICFI>. Banks that have migrated to v09 will reject files with <BIC>.
3. <MsgDefIdr> in v09
This element existed in v03 but was removed in v09. If your template still includes it, v09 XSD validation will fail.
4. <CtrlSum> mismatch
The control sum must exactly match the sum of all <InstdAmt> values. Off by one cent = rejection.
5. Execution date in the past
<ReqdExctnDt> must be today or a future business day. Banks won't process past dates.
Validate Against the Official XSD
You can validate your file directly in the browser at xmlbridge.com/sepa - paste or upload your XML, select the version (v03 or v09), and click Validate XSD. The validator runs the official ISO 20022 XSD client-side using a WASM XML parser.
For CI/CD pipelines, validate with Python:
from lxml import etree
import requests
def validate_pain001(xml_path, version="09"):
xsd_url = f"https://xmlbridge.com/schemas/pain/pain.001.001.{version}.xsd"
xsd_content = requests.get(xsd_url).content
schema = etree.XMLSchema(etree.fromstring(xsd_content))
with open(xml_path, "rb") as f:
doc = etree.fromstring(f.read())
if schema.validate(doc):
print("Valid pain.001 file")
else:
for error in schema.error_log:
print(f"Line {error.line}: {error.message}")
validate_pain001("payment.xml", version="09")
Generate pain.001 Files Without Coding
If you need to generate a one-off payment file or test a bank integration, xmlbridge.com/sepa lets you fill in a form and download a valid pain.001 XML in seconds - no backend, no signup.
It supports:
- Both v03 and v09
- Single and multi-transaction files
- SEPA EPC QR code generation
- XSD validation in-browser
Key Differences: v03 vs v09
| Element | v03 | v09 |
|---|---|---|
| BIC field | <BIC> |
<BICFI> |
| Message definition |
<MsgDefIdr> present |
removed |
| Namespace | pain.001.001.03 |
pain.001.001.09 |
| Bank support | Legacy, still common | Required by most EU banks |
Conclusion
SEPA pain.001 is well-standardized but the version migration from v03 to v09 catches a lot of teams off guard. The three elements to watch: namespace, <BIC> vs <BICFI>, and <MsgDefIdr> removal.
Validate early, validate often - most banks only tell you a file was rejected, not why.
Try the free generator and validator at xmlbridge.com/sepa.
Building a payment integration? Drop your questions in the comments.
Top comments (0)