BankLint

Guides · Errors and rejections · By Tan Gravam · Updated 2026-10-20

Validate a pain.001 against the XSD on your own machine

Get the XSD of your file’s version from iso20022.org and run xmllint --noout --schema pain.001.001.09.xsd payments.xml: it prints "payments.xml validates", or each error with its line number and "fails to validate". Python (lxml) and Java (javax.xml.validation) do the same in code. A schema-valid file can still break SEPA rules, because the schema does not contain them.

Get the official schema

The ISO 20022 schemas are published by the ISO 20022 Registration Authority on iso20022.org. The copies BankLint uses for pain.001.001.09 and pain.001.001.03 come from its message archive (Payments Initiation message set) and are used unmodified. Validate against the version your file declares, and keep the schema as published: an edited copy no longer checks the standard.

VersionNamespace (`xmlns` of `Document`)Content of `Document`
pain.001.001.09urn:iso:std:iso:20022:tech:xsd:pain.001.001.09CstmrCdtTrfInitn of type CustomerCreditTransferInitiationV09
pain.001.001.03urn:iso:std:iso:20022:tech:xsd:pain.001.001.03CstmrCdtTrfInitn of type CustomerCreditTransferInitiationV03

Each schema declares only Document in its own namespace. A file in another namespace, such as a .03 file checked against the .09 schema, fails at its root element: xmllint and lxml report "No matching global declaration available for the validation root", Java reports cvc-elt.1.a. Check the namespace before reading any other error.

xmllint on the command line

xmllint is the command-line tool of libxml2, the library BankLint’s validator also uses (through lxml). --noout stops it from printing the document itself.

Command
xmllint --noout --schema pain.001.001.09.xsd payments.xml
Output for a valid file
payments.xml validates
Output for a file with an IBAN written with spaces
payments.xml:134: element IBAN: Schemas validity error : Element '{urn:iso:std:iso:20022:tech:xsd:pain.001.001.09}IBAN': [facet 'pattern'] The value 'FR00 NOT AN IBAN' is not accepted by the pattern '[A-Z]{2,2}[0-9]{2,2}[a-zA-Z0-9]{1,30}'.
payments.xml fails to validate

The exit status is 0 for a valid file and 3 for a schema error, so the command can gate a script or a CI job. Try it with the synthetic file with an invalid IBAN pattern.

Python with lxml

lxml uses libxml2, so the messages are the ones xmllint prints. In tests, schema.assertValid(doc) raises etree.DocumentInvalid with the first error instead. The parser settings stop a file from reaching the network, expanding entities or loading a DTD; BankLint parses files with the same three settings.

validate.py
import sys
from lxml import etree

# No network access, no entity expansion, no DTD loading
parser = etree.XMLParser(no_network=True, resolve_entities=False, load_dtd=False)
schema = etree.XMLSchema(etree.parse("pain.001.001.09.xsd", parser))
doc = etree.parse(sys.argv[1], parser)

if schema.validate(doc):
    print("valid")
else:
    for error in schema.error_log:
        print(f"line {error.line}: {error.message}")

Java with javax.xml.validation

The JDK validator starts each message with the W3C XML Schema constraint that failed, such as cvc-pattern-valid or cvc-complex-type.2.4.a. Without an error handler it stops at the first error. Each code is explained, with failing and corrected XML, in pain.001 XSD validation errors.

Validate.java
import java.io.File;
import javax.xml.XMLConstants;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import org.xml.sax.SAXParseException;

public class Validate {
  public static void main(String[] args) throws Exception {
    SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
    Schema schema = factory.newSchema(new File("pain.001.001.09.xsd"));
    Validator validator = schema.newValidator();
    try {
      validator.validate(new StreamSource(new File(args[0])));
      System.out.println("valid");
    } catch (SAXParseException e) {
      System.out.println("line " + e.getLineNumber() + ": " + e.getMessage());
    }
  }
}
Output for the same file
line 134: cvc-pattern-valid: Value 'FR00 NOT AN IBAN' is not facet-valid with respect to pattern '[A-Z]{2,2}[0-9]{2,2}[a-zA-Z0-9]{1,30}' for type 'IBAN2007Identifier'.

What the schema checks

A few of the constraints in the pain.001.001.09 schema:

ElementSchema typeConstraintBankLint rule
MsgId, PmtInfId, InstrId, EndToEndIdMax35Text1 to 35 charactersBNK-ISO-004
Party names (Nm)Max140Text1 to 140 charactersBNK-ISO-004
InstdAmtActiveOrHistoricCurrencyAndAmountDecimal with at most 18 digits, of which at most 5 after the point, not negative; attribute Ccy required, three capital lettersBNK-ISO-007, BNK-ISO-008, BNK-ISO-009
IBANIBAN2007IdentifierPattern [A-Z]{2,2}[0-9]{2,2}[a-zA-Z0-9]{1,30}BNK-ISO-003
BICFIBICFIDec2014IdentifierPattern [A-Z0-9]{4,4}[A-Z]{2,2}[A-Z0-9]{2,2}([A-Z0-9]{3,3}){0,1}BNK-ISO-003
ChrgBrChargeBearerType1CodeOne of DEBT, CRED, SHAR, SLEVBNK-ISO-005
AdrLineMax70TextUp to 7 occurrences of 1 to 70 characters

The schema checks form, not meaning: the IBAN pattern accepts any two-letter country code and wrong check digits, and the currency pattern accepts any three capital letters.

Schema-valid is not SEPA-valid

The SEPA guidelines (EPC132-08 2025 v1.0) restrict many elements the schema allows. A file can pass xmllint and still break them:

ElementISO 20022 schemaSEPABankLint rule
Party namesUp to 140 charactersUp to 70 (items 1.7, 2.22, 2.68, 2.101, 2.117, 2.149)SEPA-SCT-NAME-001, SEPA-SCT-NAME-002, SEPA-SCT-NAME-003
Address lines (AdrLine)Up to 7Up to 2 (items 2.39 and 2.134)SEPA-SCT-ADDR-001
Charge bearer (ChrgBr)DEBT, CRED, SHAR or SLEVSLEV only (items 2.75 and 2.98)SEPA-SCT-CHRGBR-001
Currency (Ccy)Any three capital lettersEUR only (item 2.95)SEPA-SCT-CCY-001
Amount decimalsUp to 5Up to 2 (item 2.95)SEPA-SCT-AMT-002

The BankLint validator checks the schema and the SEPA rules together. Every SEPA rule is listed in SEPA Credit Transfer rules.

Questions

Where do I download the pain.001 XSD?

From the ISO 20022 site, iso20022.org: pain.001.001.09 and pain.001.001.03 are in its message archive under Payments Initiation. Use the version in the namespace of your file.

What does "No matching global declaration available for the validation root" mean?

The root element or its namespace does not belong to the schema, for example a pain.001.001.03 file checked against the pain.001.001.09 schema. Use the schema of the version in the file’s xmlns.

If the XSD validation passes, will the bank accept the file?

Not necessarily. The schema checks structure and formats only. SEPA rules and your bank’s own requirements come on top.

Sources: EPC132-08 2025 Version 1.0 (European Payments Council) and the ISO 20022 message schemas. All sources and dates: Standards and sources.

Validate a pain.001 fileFree, no account needed, files are not stored.

All guides