REST API for converting Excel/CSV files to ISO 20022 PAIN.001 XML and NACHA/ACH files. This page covers authentication, the available endpoints, and request/response examples.
All API requests must include the x-api-key header.
Use TEST_KEY_123 to test in our sandbox environment — it's a public, always-on key that works against every endpoint below with no signup. It never touches billing, rate limits, or your real data: it returns a realistic mock response for each endpoint instead of running the actual conversion/validation engine. Try it right now in the Playground — it's the API Key field's default value.
/api/v1/convertConverts a file using a saved mapping profile. Create a profile once in the dashboard by mapping your columns to get a profile_id, then send the raw file plus that ID — no column mapping, bank type, or company info needs to be repeated in the request. bankType, column mapping, and company details are all read from the profile. If the profile's bank type is a NACHA bank (NACHA_PPD / NACHA_CCD), this endpoint generates a NACHA/ACH file instead of XML automatically — check the format field in the response to see which one you got.
Request (multipart/form-data)
| Field | Type | Description |
|---|---|---|
| file* | File | XLSX or CSV file of transactions. Max 5MB. |
| profile_id* | string | Saved mapping profile ID from your dashboard, e.g. prof_8k2m9.... |
| bankType | string | Optional override for the profile's saved bank type. |
Response 200
| Field | Type | Description |
|---|---|---|
| status | "success" | Always "success" on 200. |
| format | "XML" | "NACHA" | Which kind of file was generated, based on the profile's bank type. |
| data | string | The generated PAIN.001 XML, or raw fixed-width NACHA text, as a string. |
curl -X POST https://www.exceltopain001.com/api/v1/convert \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@data.xlsx" \
-F "profile_id=prof_8k2m9..."import requests
url = "https://www.exceltopain001.com/api/v1/convert"
headers = {"x-api-key": "YOUR_API_KEY"}
files = {"file": open("data.xlsx", "rb")}
data = {"profile_id": "prof_8k2m9..."}
response = requests.post(url, files=files, data=data, headers=headers)
print(response.json())const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const url = 'https://www.exceltopain001.com/api/v1/convert';
const headers = { 'x-api-key': 'YOUR_API_KEY' };
const form = new FormData();
form.append('file', fs.createReadStream('data.xlsx'));
form.append('profile_id', 'prof_8k2m9...');
axios.post(url, form, {
headers: { ...headers, ...form.getHeaders() }
})
.then(res => console.log(res.data))
.catch(err => console.error(err));Max file size 5MB. Returns 400 if profile_id is missing or unknown, or if the file is missing required columns per the profile's mapping.
/api/v1/sepa/generateGenerates a PAIN.001 XML file from an explicit JSON payload or an uploaded file with a manually supplied bankType. Use this endpoint when you don't want to create a mapping profile up front. See the code examples below for the request format. As with /api/v1/convert, a NACHA bankType generates a NACHA/ACH file instead — same format field in the response, and the same NACHA request fields documented under /api/v1/nacha/generate below (company details, nacha* overrides) also apply here.
/api/v1/nacha/generateNACHA/ACH-only sibling of /api/v1/sepa/generate: same JSON-or-file request style, no profile_id needed. bankType must be NACHA_PPD (consumer/payroll) or NACHA_CCD (corporate/vendor) — anything else returns a 400.
File-level bank details — ODFI routing number, company identification, immediate destination/origin — are read from your account's Company Settings, not the request (a 400 names exactly which field is missing if they aren't configured yet).
Request (JSON body or multipart/form-data)
| Field | Type | Description |
|---|---|---|
| bankType* | "NACHA_PPD" | "NACHA_CCD" | Determines SEC code and validation rules. |
| companyName | string | Overrides Company Settings' company name for this file. |
| transactions* | array | Row objects — see field list below. Required if no file is attached. |
| transactions[].receivingRoutingNumber* | string | Receiving bank's 9-digit ABA routing number. |
| transactions[].receivingAccountNumber* | string | Receiving account number. |
| transactions[].amount* | number | Transaction amount in USD. |
| transactions[].creditorName* | string | Receiver name. |
| transactions[].individualId | string | Optional identifier (e.g. employee ID). |
| transactions[].remittanceInfo | string | Optional free-text memo. |
| columnMapping | object | File mode only. Maps your file's column headers to the field names above — NACHA headers are not auto-detected. |
| nachaCompanyEntryDescription | string | Optional per-request override of the batch entry description. |
| nachaEffectiveEntryDate | string | Optional per-request override, YYYY-MM-DD. |
| nachaTransactionType | "credit" | "debit" | Optional. Allowed values: "credit" or "debit" (case-insensitive, surrounding spaces ignored). Defaults to "credit" when omitted. Any other value, including an empty string or null, is rejected with 400. |
| nachaAccountType | "checking" | "savings" | Optional. Allowed values: "checking" or "savings" (case-insensitive, surrounding spaces ignored). Defaults to "checking" when omitted. Any other value, including an empty string or null, is rejected with 400. |
Response 200 (Content-Type: text/plain)
| Field | Type | Description |
|---|---|---|
| (body) | string | The raw fixed-width NACHA file itself — unlike /convert and /sepa/generate, a success response is not JSON. |
Error response (JSON)
| Field | Type | Description |
|---|---|---|
| status | "error" | Always "error". |
| message | string | Present for a single problem, e.g. missing Company Settings. |
| errors | string[] | Present instead of message for per-row validation failures. |
curl -X POST https://www.exceltopain001.com/api/v1/nacha/generate \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"bankType": "NACHA_PPD",
"companyName": "Acme Corp",
"transactions": [
{
"receivingRoutingNumber": "021000021",
"receivingAccountNumber": "1234567890",
"amount": 1500.00,
"creditorName": "John Doe",
"individualId": "EMP001",
"remittanceInfo": "July Salary"
}
]
}'import requests
url = "https://www.exceltopain001.com/api/v1/nacha/generate"
headers = {"Content-Type": "application/json", "x-api-key": "YOUR_API_KEY"}
payload = {
"bankType": "NACHA_PPD",
"companyName": "Acme Corp",
"transactions": [{
"receivingRoutingNumber": "021000021",
"receivingAccountNumber": "1234567890",
"amount": 1500.00,
"creditorName": "John Doe"
}]
}
response = requests.post(url, json=payload, headers=headers)
print(response.text) # raw fixed-width NACHA file, not JSONconst axios = require('axios');
const url = 'https://www.exceltopain001.com/api/v1/nacha/generate';
const headers = { 'x-api-key': 'YOUR_API_KEY' };
const payload = {
bankType: 'NACHA_PPD',
companyName: 'Acme Corp',
transactions: [{
receivingRoutingNumber: '021000021',
receivingAccountNumber: '1234567890',
amount: 1500.00,
creditorName: 'John Doe'
}]
};
axios.post(url, payload, { headers })
.then(res => console.log(res.data)) // raw NACHA text
.catch(err => console.error(err));/api/v1/validateValidates an existing PAIN.001/PAIN.008 XML file — structural checks, IBAN mod-97 checksum, EndToEndId length, and full XSD schema validation against the ISO 20022 schema, run server-side, plus bank-specific compatibility rules for every bank listed above. The same engine behind the XML Validator tool.
Request (multipart/form-data)
| Field | Type | Description |
|---|---|---|
| file* | File | PAIN.001/PAIN.008 XML file to validate. Max 5MB. |
Response 200
| Field | Type | Description |
|---|---|---|
| generic.valid | boolean | Overall XSD/structural validity, independent of any specific bank. |
| generic.errors | string[] | Human-readable structural/XSD error messages. |
| generic.structuredErrors | object[] | Machine-parseable form of the same errors (path, rule, message). |
| compatibility | array | One entry per supported bank: { bank, status: "pass" | "warning" | "fail", warnings }. |
| summary | object | Aggregate counts across the compatibility array. |
curl -X POST https://www.exceltopain001.com/api/v1/validate \
-H "x-api-key: YOUR_API_KEY" \
-F "file=@payment.xml"import requests
url = "https://www.exceltopain001.com/api/v1/validate"
headers = {"x-api-key": "YOUR_API_KEY"}
files = {"file": open("payment.xml", "rb")}
response = requests.post(url, files=files, headers=headers)
print(response.json())const axios = require('axios');
const FormData = require('form-data');
const fs = require('fs');
const url = 'https://www.exceltopain001.com/api/v1/validate';
const headers = { 'x-api-key': 'YOUR_API_KEY' };
const form = new FormData();
form.append('file', fs.createReadStream('payment.xml'));
axios.post(url, form, {
headers: { ...headers, ...form.getHeaders() }
})
.then(res => console.log(res.data))
.catch(err => console.error(err));Max file size 5MB.
Default ISO-20022 PAIN.001.001.03 format guaranteed to work with any standard European bank.
We automatically handle character limits, encoding (ISO-8859-1), and required fields unique to these institutions:
Both endpoints already support the structured-address fields required by the SEPA structured address mandate, which is being phased in with no fixed date yet. Address columns are detected, parsed, and mapped into the structured XML fields automatically — no changes needed on your side whenever it takes effect.
Examples for /api/v1/sepa/generate:
curl -X POST https://www.exceltopain001.com/api/v1/sepa/generate \
-H "Content-Type: application/json" \
-H "x-api-key: YOUR_API_KEY" \
-d '{
"bankType": "DEUTSCHE_BANK",
"companyName": "Acme Corp",
"transactions": [
{
"debtorIban": "DE12345678901234567890",
"creditorName": "John Doe",
"creditorIban": "DE98765432109876543210",
"amount": 100.5,
"currency": "EUR",
"remittanceInfo": "Invoice #123"
}
]
}'import requests
url = "https://www.exceltopain001.com/api/v1/sepa/generate"
headers = {
"Content-Type": "application/json",
"x-api-key": "YOUR_API_KEY"
}
payload = {
"bankType": "DEUTSCHE_BANK",
"companyName": "Acme Corp",
"transactions": [
{
"debtorIban": "DE12345678901234567890",
"creditorName": "John Doe",
"creditorIban": "DE98765432109876543210",
"amount": 100.5,
"currency": "EUR",
"remittanceInfo": "Invoice #123"
}
]
}
response = requests.post(url, json=payload, headers=headers)
print(response.json())const axios = require('axios');
const url = 'https://www.exceltopain001.com/api/v1/sepa/generate';
const headers = { 'x-api-key': 'YOUR_API_KEY' };
const payload = {
"bankType": "DEUTSCHE_BANK",
"companyName": "Acme Corp",
"transactions": [
{
"debtorIban": "DE12345678901234567890",
"creditorName": "John Doe",
"creditorIban": "DE98765432109876543210",
"amount": 100.5,
"currency": "EUR",
"remittanceInfo": "Invoice #123"
}
]
};
axios.post(url, payload, { headers })
.then(res => console.log(res.data))
.catch(err => console.error(err));For /api/v1/convert and /api/v1/sepa/generate (/api/v1/nacha/generate and /api/v1/validate have their own response shapes, documented in their sections above):
{
"status": "success",
"format": "XML",
"data": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<Document xmlns=\"urn:iso:std:iso:20022:tech:xsd:pain.001.001.03\">\n <CstmrCdtTrfInitn>\n <GrpHdr>\n <MsgId>ABC-123456</MsgId>\n ...\n </GrpHdr>\n </CstmrCdtTrfInitn>\n</Document>"
}format is "NACHA" instead of "XML" when bankType resolves to a NACHA bank (NACHA_PPD / NACHA_CCD) — in that case data is the raw fixed-width NACHA text, not XML.
{
"status": "error",
"message": "Missing mandatory field 'creditorIban' in row 2. Please map it.",
"errors": ["Row 2: creditorIban: Required"]
}Returned when the x-api-key is missing or invalid.
Defaults to the public sandbox key TEST_KEY_123 — swap in a real key to hit your own account/data.