Developer Portal & API Reference

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.

API Documentation

Authentication

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.

Endpoints

POST/api/v1/convert

Converts 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)

FieldTypeDescription
file*FileXLSX or CSV file of transactions. Max 5MB.
profile_id*stringSaved mapping profile ID from your dashboard, e.g. prof_8k2m9....
bankTypestringOptional override for the profile's saved bank type.

Response 200

FieldTypeDescription
status"success"Always "success" on 200.
format"XML" | "NACHA"Which kind of file was generated, based on the profile's bank type.
datastringThe generated PAIN.001 XML, or raw fixed-width NACHA text, as a string.
bash
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..."
python
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())
javascript
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.

POST/api/v1/sepa/generate

Generates 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.

POST/api/v1/nacha/generate

NACHA/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)

FieldTypeDescription
bankType*"NACHA_PPD" | "NACHA_CCD"Determines SEC code and validation rules.
companyNamestringOverrides Company Settings' company name for this file.
transactions*arrayRow objects — see field list below. Required if no file is attached.
transactions[].receivingRoutingNumber*stringReceiving bank's 9-digit ABA routing number.
transactions[].receivingAccountNumber*stringReceiving account number.
transactions[].amount*numberTransaction amount in USD.
transactions[].creditorName*stringReceiver name.
transactions[].individualIdstringOptional identifier (e.g. employee ID).
transactions[].remittanceInfostringOptional free-text memo.
columnMappingobjectFile mode only. Maps your file's column headers to the field names above — NACHA headers are not auto-detected.
nachaCompanyEntryDescriptionstringOptional per-request override of the batch entry description.
nachaEffectiveEntryDatestringOptional 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)

FieldTypeDescription
(body)stringThe raw fixed-width NACHA file itself — unlike /convert and /sepa/generate, a success response is not JSON.

Error response (JSON)

FieldTypeDescription
status"error"Always "error".
messagestringPresent for a single problem, e.g. missing Company Settings.
errorsstring[]Present instead of message for per-row validation failures.
bash
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"
      }
    ]
  }'
python
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 JSON
javascript
const 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));
POST/api/v1/validate

Validates 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)

FieldTypeDescription
file*FilePAIN.001/PAIN.008 XML file to validate. Max 5MB.

Response 200

FieldTypeDescription
generic.validbooleanOverall XSD/structural validity, independent of any specific bank.
generic.errorsstring[]Human-readable structural/XSD error messages.
generic.structuredErrorsobject[]Machine-parseable form of the same errors (path, rule, message).
compatibilityarrayOne entry per supported bank: { bank, status: "pass" | "warning" | "fail", warnings }.
summaryobjectAggregate counts across the compatibility array.
bash
curl -X POST https://www.exceltopain001.com/api/v1/validate \
  -H "x-api-key: YOUR_API_KEY" \
  -F "file=@payment.xml"
python
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())
javascript
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.

Supported Banks

Universal SEPA (All 27+ EU Countries)

Default ISO-20022 PAIN.001.001.03 format guaranteed to work with any standard European bank.

Standard Compliant

Bank-Specific Rules & Quirks

We automatically handle character limits, encoding (ISO-8859-1), and required fields unique to these institutions:

  • Citibank
  • Deutsche Bank
  • HSBC
  • ING
  • BNP Paribas
  • Soc. Générale
  • Santander
  • BBVA
  • Sparkasse
  • Intesa Sanpaolo
  • UniCredit
  • Erste Bank

Address Compliance

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.

Code Examples

Examples for /api/v1/sepa/generate:

bash
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"
    }
  ]
}'
python
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())
javascript
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));

Responses & Error Codes

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):

json
{
  "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.

json
{
  "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.

API Playground

Defaults to the public sandbox key TEST_KEY_123 — swap in a real key to hit your own account/data.

Select Bank