Generate SEPA XML (pain.001/008) with an API
What is SEPA XML?
SEPA (Single Euro Payments Area) standardizes euro payments across 36 European countries. Banks accept payment instructions in XML format defined by ISO 20022:
- pain.001, Credit Transfer Initiation: you send money to someone
- pain.008, Direct Debit Initiation: you pull money from someone's account
These XML files are what you upload to your bank's portal or send via their API. Generating them manually is tedious and error-prone, one wrong tag and the bank rejects the entire batch.
Generating a credit transfer (pain.001)
A single API call generates a valid SEPA credit transfer XML:
import httpx
response = httpx.post(
"https://frenchbusinessapi.com/payment/sepa/credit-transfer",
headers={"X-API-Key": "your_api_key"},
json={
"debtor_name": "My Company SAS",
"debtor_iban": "FR7630006000011234567890189",
"debtor_bic": "BNPAFRPP",
"transactions": [
{
"creditor_name": "Supplier Ltd",
"creditor_iban": "DE89370400440532013000",
"amount": 1500.00,
"currency": "EUR",
"reference": "INV-2026-042"
}
]
}
)
print(response.json()["xml"])
The response contains a complete, valid pain.001.001.03 XML ready to upload to your bank.
Generating a direct debit (pain.008)
Direct debits work similarly but require a mandate reference:
response = httpx.post(
"https://frenchbusinessapi.com/payment/sepa/direct-debit",
headers={"X-API-Key": "your_api_key"},
json={
"creditor_name": "My Company SAS",
"creditor_iban": "FR7630006000011234567890189",
"creditor_id": "FR12ZZZ123456",
"transactions": [
{
"debtor_name": "Customer GmbH",
"debtor_iban": "DE89370400440532013000",
"amount": 99.99,
"currency": "EUR",
"mandate_id": "MANDATE-001",
"mandate_date": "2026-01-15",
"reference": "SUB-2026-003"
}
]
}
)
The `creditor_id` is your SEPA Creditor Identifier, assigned by your bank when you set up direct debit capability.
Batch payments
Both endpoints accept multiple transactions in a single request. This is how you process payroll, supplier payments, or subscription billing:
transactions = [
{"creditor_name": "Supplier A", "creditor_iban": "DE89...", "amount": 500.00, "reference": "INV-001"},
{"creditor_name": "Supplier B", "creditor_iban": "FR76...", "amount": 1200.00, "reference": "INV-002"},
{"creditor_name": "Supplier C", "creditor_iban": "NL91...", "amount": 340.50, "reference": "INV-003"},
]
All transactions are grouped into a single pain.001 file with proper batch headers and control sums. Your bank processes them as one batch.
EPC QR codes for instant payment
Beyond XML, the API generates EPC QR codes, the European standard for payment QR codes. Your customer scans the code with their banking app and the payment details are pre-filled:
response = httpx.post(
"https://frenchbusinessapi.com/payment/epc-qr",
headers={"X-API-Key": "your_api_key"},
json={
"beneficiary_name": "My Company SAS",
"iban": "FR7630006000011234567890189",
"amount": 149.99,
"reference": "INV-2026-042"
}
)
# Returns a PNG image of the QR code
EPC QR codes are widely supported by European banking apps (ING, BNP, Deutsche Bank, etc.).
Validating IBANs before generating XML
A rejected SEPA payment because of a wrong IBAN is costly. Validate IBANs before including them in your XML:
response = httpx.get(
"https://frenchbusinessapi.com/iban/validate/DE89370400440532013000",
headers={"X-API-Key": "your_api_key"}
)
data = response.json()
if data["valid"]:
print(f"Bank: {data['bank_name']}, BIC: {data['bic']}")
The IBAN validation endpoint checks the format, MOD-97 checksum, country-specific length, and returns the bank name and BIC. See our IBAN validation guide for details.
Why not generate XML yourself?
You could write your own XML generator. Here's why you probably shouldn't:
- Namespace complexity: pain.001.001.03 has deep nesting with precise namespace requirements. One wrong namespace and the bank rejects the file.
- Character restrictions: SEPA XML has strict character set rules. Accented characters, special symbols, some are allowed, some aren't, and it depends on the bank.
- Amount formatting: amounts must be exactly 2 decimal places, using a dot separator, no thousands separator.
- Control sums: the XML includes checksum fields that must match the sum of all transactions. Get it wrong and the whole file is rejected.
- Schema versions: there are multiple versions of pain.001 and pain.008. Banks support different versions.
The API handles all of this. You pass clean data, you get valid XML.
Common use cases
- SaaS subscription billing: generate pain.008 direct debit files for recurring payments
- Marketplace payouts: generate pain.001 credit transfers to pay sellers
- Invoice payment: attach an EPC QR code to your invoices for one-scan payment
- Payroll: generate monthly salary payment files
- Supplier payments: batch all your supplier invoices into one bank upload
Next steps
- Get your free API key, 100 requests/day
- API documentation, full endpoint reference
- Related guides: Validate EU VAT numbers, Validate IBANs, French company lookup
Ready to build for Europe?
One API for company lookup, VAT validation, IBAN verification, SEPA payments, and more.
Get your API key