Validate IBAN numbers with one API call
Your payment form accepts international bank transfers. The user types a 22-character IBAN. Before you send that to your payment processor and wait for a failure, you want to catch typos at the form level. IBAN validation involves a modulo-97 check digit algorithm, country-specific length rules, and BBAN structure parsing. You can write it yourself, pull in a library, or let an API handle it.
Botoi's /v1/validate/iban endpoint takes an IBAN string and returns whether it's
valid, the country code, the country name, and a formatted version of the number. One POST
request, no banking SDK, no regex collection to maintain.
The API call
curl -X POST https://api.botoi.com/v1/validate/iban \
-H "Content-Type: application/json" \
-d '{"iban": "DE89370400440532013000"}' Response:
{
"success": true,
"data": {
"valid": true,
"country_code": "DE",
"country": "Germany",
"formatted": "DE89 3704 0044 0532 0130 00"
}
}
The response gives you four fields. valid is the boolean you need for
form-level gating. country_code and country let you auto-detect
the user's bank country for downstream logic like currency selection or tax rules.
formatted returns the IBAN in the standard four-character grouping humans
expect to see on invoices and bank statements.
When the IBAN fails the check digit verification, you still get the parsed fields:
{
"success": true,
"data": {
"valid": false,
"country_code": "DE",
"country": "Germany",
"formatted": "DE00 3704 0044 0532 0130 00"
}
}
The valid: false flag is all you need. No exceptions to catch, no error codes
to look up. Check the boolean and show an error message.
Node.js
const response = await fetch("https://api.botoi.com/v1/validate/iban", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ iban: "GB29NWBK60161331926819" }),
});
const { data } = await response.json();
console.log(data);
// { valid: true, country_code: "GB", country: "United Kingdom", formatted: "GB29 NWBK 6016 1331 9268 19" } Python
import requests
response = requests.post(
"https://api.botoi.com/v1/validate/iban",
json={"iban": "FR7630006000011234567890189"},
)
data = response.json()["data"]
print(data)
# {"valid": True, "country_code": "FR", "country": "France", "formatted": "FR76 3000 6000 0112 3456 7890 189"} IBAN formats by country
IBAN length varies by country. The first two characters are always the ISO 3166-1 country code, followed by two check digits, then the Basic Bank Account Number (BBAN). Here are the five most common formats in European payments:
| Country | Code | Length | Format | Example |
|---|---|---|---|---|
| Germany | DE | 22 | DE + 2 check digits + 8 bank code + 10 account | DE89 3704 0044 0532 0130 00 |
| United Kingdom | GB | 22 | GB + 2 check digits + 4 bank + 6 sort code + 8 account | GB29 NWBK 6016 1331 9268 19 |
| France | FR | 27 | FR + 2 check digits + 10 bank + 11 account + 2 key | FR76 3000 6000 0112 3456 7890 189 |
| Spain | ES | 24 | ES + 2 check digits + 4 bank + 4 branch + 2 control + 10 account | ES91 2100 0418 4502 0005 1332 |
| Netherlands | NL | 18 | NL + 2 check digits + 4 bank + 10 account | NL91 ABNA 0417 1643 00 |
The API handles all 80+ countries in the SWIFT IBAN registry. You don't need to maintain a lookup table of lengths and formats; the endpoint validates against the correct rules for each country code.
React/Preact form validation on blur
The most common integration: validate the IBAN when the user tabs out of the input field. Calling on blur (instead of on every keystroke) keeps API usage low and avoids flashing error messages while the user is still typing.
import { useState, useCallback } from "preact/hooks";
function IbanField({ onValidated }) {
const [iban, setIban] = useState("");
const [status, setStatus] = useState(null);
const [loading, setLoading] = useState(false);
const validate = useCallback(async (value) => {
if (!value.trim()) {
setStatus(null);
return;
}
setLoading(true);
const res = await fetch("https://api.botoi.com/v1/validate/iban", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_API_KEY",
},
body: JSON.stringify({ iban: value }),
});
const { data } = await res.json();
setStatus(data);
setLoading(false);
if (data.valid) {
onValidated(data);
}
}, [onValidated]);
return (
<div>
<label htmlFor="iban-input">IBAN</label>
<input
id="iban-input"
type="text"
value={iban}
onInput={(e) => setIban(e.target.value)}
onBlur={() => validate(iban)}
placeholder="DE89 3704 0044 0532 0130 00"
/>
{loading && <span>Checking...</span>}
{status && !loading && (
<p style={{ color: status.valid ? "green" : "red" }}>
{status.valid
? `Valid IBAN (\${status.country})`
: "Invalid IBAN. Check the number and try again."}
</p>
)}
</div>
);
}
The component calls the API when the field loses focus. If the IBAN is valid, it passes the
parsed data (country code, formatted string) up to the parent form via onValidated.
The parent can then auto-fill currency, adjust routing, or display the formatted IBAN as
confirmation.
Batch validate IBANs from a CSV
Finance teams often need to validate a list of supplier or employee IBANs before running a batch payment. This script reads a CSV, validates each IBAN, and flags invalid entries.
import fs from "node:fs";
import { parse } from "csv-parse/sync";
const file = fs.readFileSync("suppliers.csv", "utf-8");
const rows = parse(file, { columns: true });
async function validateIban(iban) {
const res = await fetch("https://api.botoi.com/v1/validate/iban", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_API_KEY",
},
body: JSON.stringify({ iban }),
});
return res.json();
}
const results = [];
for (const row of rows) {
const { data } = await validateIban(row.iban);
results.push({
supplier: row.name,
iban: row.iban,
valid: data.valid,
country: data.country || "Unknown",
formatted: data.formatted,
});
}
const invalid = results.filter((r) => !r.valid);
console.log(`Checked \${results.length} IBANs. \${invalid.length} invalid.`);
if (invalid.length > 0) {
console.table(invalid);
process.exit(1);
} The script exits with code 1 if any IBAN is invalid, making it usable as a CI step or pre-upload check. For large files (1,000+ rows), add a small delay between requests or use an API key to get higher rate limits.
Stripe Connect onboarding
When onboarding sellers or freelancers via Stripe Connect, you collect their bank details to
set up payouts. Validating the IBAN before calling stripe.accounts.createExternalAccount
avoids a round trip to Stripe's API that would fail anyway. It also lets you extract the
country code to set the correct payout currency.
async function onboardConnectedAccount(accountId, ibanInput) {
// Step 1: validate the IBAN before sending it to Stripe
const res = await fetch("https://api.botoi.com/v1/validate/iban", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: "Bearer YOUR_API_KEY",
},
body: JSON.stringify({ iban: ibanInput }),
});
const { data } = await res.json();
if (!data.valid) {
throw new Error("The IBAN you entered is invalid. Please check it and try again.");
}
// Step 2: create the external account in Stripe
const stripe = require("stripe")(process.env.STRIPE_SECRET_KEY);
const bankAccount = await stripe.accounts.createExternalAccount(accountId, {
external_account: {
object: "bank_account",
country: data.country_code,
currency: getCurrencyForCountry(data.country_code),
account_number: ibanInput.replace(/\s/g, ""),
},
});
return bankAccount;
}
function getCurrencyForCountry(code) {
const map = { DE: "eur", FR: "eur", ES: "eur", NL: "eur", GB: "gbp", CH: "chf" };
return map[code] || "eur";
} The flow is straightforward: validate first, extract the country, map it to a currency, then create the external account. If the IBAN is invalid, the user sees an error immediately instead of waiting for Stripe to reject it seconds later.
Key points
- One POST, four fields. Send an IBAN string to
/v1/validate/ibanand get back validity, country code, country name, and the formatted number. - 80+ countries. The endpoint covers every country in the SWIFT IBAN registry. No country-specific logic on your end.
- Catch errors at the form level. Validate on blur in your payment form to prevent invalid IBANs from reaching your bank or payment processor.
- No storage, no logging. The IBAN is processed in memory and discarded. Nothing is persisted.
- Free tier available. Anonymous access at 5 requests per minute, 100 per day. No API key required for development and testing.
Frequently asked questions
- Does this API verify that the bank account exists?
- No. It validates the IBAN structure and modulo-97 checksum. It confirms the number is correctly formatted but does not contact any bank or check whether the account is open or funded.
- How many countries does the IBAN validation support?
- Over 80 countries that have adopted the IBAN standard, including all EU/EEA member states, the UK, Switzerland, Saudi Arabia, and Brazil. The full list follows the SWIFT IBAN registry.
- Can I send an IBAN with spaces in it?
- Yes. Spaces are stripped automatically before validation. "DE89 3704 0044 0532 0130 00" and "DE89370400440532013000" both return the same result.
- Do I need an API key to validate IBANs?
- No. Anonymous access allows 5 requests per minute with IP-based rate limiting and 100 requests per day. For higher throughput, sign up for an API key at botoi.com/api.
- Is the IBAN I send stored or logged?
- No. The IBAN is validated in memory and discarded immediately. Nothing is persisted or written to any log.
Try this API
IBAN Validation API — interactive playground and code examples
More tutorial posts
Start building with botoi
150+ API endpoints for lookup, text processing, image generation, and developer utilities. Free tier, no credit card.