Statement parser
POST /v1/iso20022/camt.053/parseRequest
multipart camt.053 / camt.054 XML + expected invoice keys
Response
{ "entries": [{ "amount", "end_to_end_id", "remittance", "matched_invoice_id" }] }A headless HTTP API at api.iso-compliant.com. Generate pain.001 / pain.008, parse camt.053 / pain.002, render Swiss QR-bills, validate IBANs. Deterministic XML emission — no LLM in the request path — under a zero-retention contract.
Quickstart
Get a key, POST a payment payload, receive a structured-address compliant pain.001.001.09 back. The same call in curl, TypeScript, and Python.
# 1. Get a key at https://iso-compliant.com/signup (iso_live_… / iso_test_…)
# 2. Emit a structured-address-compliant pain.001:
curl -X POST https://api.iso-compliant.com/v1/iso20022/pain.001 \
-H "Authorization: Bearer iso_live_..." \
-H "Content-Type: application/json" \
-d '{
"debtor": { "name": "Acme AG", "iban": "CH9300762011623852957" },
"creditor":{ "name": "Globex SA", "iban": "DE89370400440532013000",
"postal_address": {
"street_name": "Bahnhofstrasse", "building_number": "45",
"post_code": "8001", "town_name": "Zurich", "country": "CH"
}
},
"amount": "1480.00", "currency": "EUR", "end_to_end_id": "INV-2026-0042"
}'
# → 200 OK: an XSD-valid pain.001.001.09 document.// 1. Get a key at https://iso-compliant.com/signup (iso_live_… / iso_test_…)
// 2. Emit a structured-address-compliant pain.001:
const res = await fetch(
"https://api.iso-compliant.com/v1/iso20022/pain.001",
{
method: "POST",
headers: {
Authorization: `Bearer ${process.env.ISOCOMPLIANT_API_KEY}`, // iso_live_…
"Content-Type": "application/json",
},
body: JSON.stringify({
debtor: { name: "Acme AG", iban: "CH9300762011623852957" },
creditor: {
name: "Globex SA", iban: "DE89370400440532013000",
postal_address: {
street_name: "Bahnhofstrasse", building_number: "45",
post_code: "8001", town_name: "Zurich", country: "CH",
},
},
amount: "1480.00", currency: "EUR", end_to_end_id: "INV-2026-0042",
}),
},
);
const xml = await res.text(); // XSD-valid pain.001.001.09# 1. Get a key at https://iso-compliant.com/signup (iso_live_… / iso_test_…)
# 2. Emit a structured-address-compliant pain.001:
import os, requests
res = requests.post(
"https://api.iso-compliant.com/v1/iso20022/pain.001",
headers={
"Authorization": f"Bearer {os.environ['ISOCOMPLIANT_API_KEY']}", # iso_live_…
"Content-Type": "application/json",
},
json={
"debtor": {"name": "Acme AG", "iban": "CH9300762011623852957"},
"creditor": {
"name": "Globex SA", "iban": "DE89370400440532013000",
"postal_address": {
"street_name": "Bahnhofstrasse", "building_number": "45",
"post_code": "8001", "town_name": "Zurich", "country": "CH",
},
},
"amount": "1480.00", "currency": "EUR", "end_to_end_id": "INV-2026-0042",
},
)
xml = res.text # XSD-valid pain.001.001.09Authentication
Every gated call carries Authorization: Bearer iso_live_…. Live keys are prefixed iso_live_; sandbox keys are prefixed iso_test_ and never touch a real bank rail. Free local-compute endpoints (IBAN validation) work without a key.
Authorization: Bearer iso_live_9f3c… Content-Type: application/json
For high-assurance integrations, sign requests with an Ed25519 key per RFC 9421 (HTTP Message Signatures). Add a Signature-Input and Signature header over the method, path, and a content digest; the API rejects any request whose body does not match the signed digest.
Signature-Input: sig1=("@method" "@path"
"content-digest");keyid="ed25519:…";alg="ed25519"
Signature: sig1=:MEUCIQ…:Zero retention: request and response payloads — IBANs, names, amounts — are processed in memory and never persisted. Audit metadata is counter-only.
Endpoint reference
Each route, with the request body shape and the response shape. Full field-level reference at the API documentation.
POST /v1/iso20022/camt.053/parseRequest
multipart camt.053 / camt.054 XML + expected invoice keys
Response
{ "entries": [{ "amount", "end_to_end_id", "remittance", "matched_invoice_id" }] }POST /v1/iso20022/pain.001Request
{ "debtor", "creditor": { "postal_address": {…} }, "amount", "currency", "end_to_end_id" }Response
XSD-valid pain.001.001.09 XML · ?profile=ch.03 | cgi-mp
POST /v1/iso20022/pain.002/parseRequest
pain.002 XML
Response
{ "rejections": [{ "code": "AC01", "disposition": "retry" | "hitl" }] }POST /v1/qr-billRequest
{ "creditor", "iban", "amount", "currency", "reference", "format": "pdf|svg|png|payload" }Response
SPC v0200 — PDF / SVG / PNG bytes or the 31-line payload string
POST /v1/iban/validateRequest
{ "iban": "CH9300762011623852957" }Response
{ "valid": true, "bank_name", "bic", "qr_iban": false }POST /v1/iso20022/pain.008Request
{ "creditor", "mandates": [{ "debtor", "seq_tp": "FRST|RCUR|OOFF|FNAL" }] }Response
XSD-valid pain.008.001.02 XML — CORE / B2B
Free vs gated
Pure local-compute checks (IBAN mod-97, QRR / SCOR checksums) run without a key. Anything that emits an ISO 20022 document or parses a bank statement is gated behind a bearer token.
POST /v1/iban/validateISO 13616 mod-97 + BIC lookup + QR-IBAN flag.
POST /v1/iso20022/pain.001POST /v1/iso20022/pain.008POST /v1/iso20022/camt.053/parsePOST /v1/iso20022/pain.002/parsePOST /v1/qr-billNo key? You hit the funnel.
Both the REST API and the MCP server route an unkeyed developer to the same place: the sign-up page. The error is explicit, machine-readable, and carries the URL so an agent can surface it inline.
{
"error": "INVALID_API_KEY",
"message": "Missing or invalid bearer token. Gated endpoints require an iso_live_ or iso_test_ key.",
"signup_url": "https://iso-compliant.com/signup",
"docs_url": "https://iso-compliant.com/docs"
}When a developer in Cursor or Claude Desktop invokes a gated MCP tool with no ISOCOMPLIANT_API_KEY, the server returns an isError response with the same /signup prompt — the conversion moment lands right where the developer is working.
Free tier — no card
100 documents/month free. Sandbox iso_test_ keys for CI, live iso_live_ keys for production.