Skip to content
tutorial

Validate IBAN numbers with one API call

| 5 min read
International bank building facade
Photo by Sean Pollock on Unsplash

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.

Credit card and financial documents on a desk
Photo by rupixen.com on Unsplash

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/iban and 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.