Validate an IBAN: the mod-97 check explained
To validate an IBAN, check its country code and length against the SWIFT IBAN Registry, move the first four characters to the end, replace each letter by two digits (A = 10 … Z = 35), and check that the number modulo 97 equals 1 (ISO 13616, with the mod 97-10 algorithm of ISO/IEC 7064). That catches every single-character error and almost every transposition. It does not prove that the account exists or that the bank accepts SEPA transfers: those need the registry’s BBAN pattern and the EPC list of SEPA countries.
- Structure:
CC(ISO 3166-1 alpha-2) +kk(check digits) + BBAN; total length fixed per country (Norway 15, Malta 31, Russia 33). - Check: move the first four characters to the end, replace letters with numbers (A=10 … Z=35), compute the remainder modulo 97; a valid IBAN gives 1.
- The BBAN has a per-country pattern (digits only, letters, alphanumerics) published in the SWIFT IBAN Registry; a mod-97 pass with the wrong pattern is still invalid.
- SEPA membership: the 27 member states plus Iceland, Liechtenstein, Norway, Switzerland, the UK, Monaco, San Marino, Vatican City, Andorra, Gibraltar; Albania, Moldova, Montenegro and North Macedonia joined on 5 October 2025.
The algorithm on DE89 3704 0044 0532 0130 00
1. Remove spaces and upper-case: DE89370400440532013000 (22 characters, the German length).
2. Move the first four to the end: 370400440532013000DE89.
3. Replace letters: D = 13, E = 14 → 3704004405320130001314 89, written together: 370400440532013000131489.
4. Compute that number modulo 97. Do it in chunks so no integer overflows: take the first 9 digits, remainder mod 97, append the next 7 digits, remainder again, until the end. The remainder is 1, so the IBAN is valid.
To generate the check digits for a new account: put 00 in their place, compute the remainder r the same way, then the check digits are 98 − r, padded to two digits.
export function ibanValid(input) {
const iban = input.toUpperCase().replace(/[\s-]/g, "");
if (!/^[A-Z]{2}[0-9]{2}[A-Z0-9]+$/.test(iban)) return false;
const rearranged = iban.slice(4) + iban.slice(0, 4);
let r = 0;
for (const ch of rearranged) {
const v = ch >= "A" ? String(ch.charCodeAt(0) - 55) : ch; // A=10 … Z=35
for (const d of v) r = (r * 10 + Number(d)) % 97;
}
return r === 1;
}What does mod-97 catch, and what does it miss?
It catches every single wrong character and every swap of two adjacent characters (the two errors people make when typing). It does not catch a wrong country, a wrong length with coincidentally valid digits, a letter where the national format wants a digit, a closed account or a bank that left the SEPA scheme. That is why a real validator does four more things: country known, length right for the country, BBAN pattern from the registry, SEPA flag from the EPC list; and why a payment system then still receives rejects (AC01, account number incorrect) from the bank.
Why do the registry and the SEPA list matter?
The SWIFT IBAN Registry (the ISO 13616 registration authority’s file) lists for each country the IBAN length, the BBAN structure in a compact notation (8!n10!n means eight digits then ten digits), and where the bank and branch identifiers sit. The European Payments Council publishes the list of countries in the SEPA schemes; it is not the same as the EU (Switzerland and the UK are in, Türkiye is not) and it changed in October 2025. The API returns sepa and sepaSince so a payments team can warn before sending.
Can you get the BIC from an IBAN?
The IBAN gives the bank identifier inside the BBAN (the German Bankleitzahl, the French code banque), but mapping it to the BIC and the bank’s name needs the SWIFT BIC Directory or IBAN Plus, which are licensed data products, not open data. We prefer to say so and put BIC lookup on the roadmap than to ship a scraped directory that goes stale. The validation itself needs no BIC: since 2016 (Regulation 260/2012) an IBAN alone suffices for SEPA payments.
Questions
- Does a valid IBAN mean the account exists?
- No. It means the string is well-formed for its country. Account existence is only known to the bank; a payment can still be returned.
- Why does the API answer 200 for an invalid IBAN?
- Because the question was answered: the IBAN is invalid, and
failures[]says why (length, check_digits, bban_pattern, country_unknown, characters). A 4xx is for a malformed request, not for a wrong IBAN. - What about the QR-IBAN and the “IBAN Plus” file?
- Swiss QR-IBANs are ordinary IBANs with a reserved bank-identifier range; they validate like any Swiss IBAN. IBAN Plus is SWIFT’s licensed IBAN-to-BIC file: roadmap.
Checks, identifiers, SEPA flag, in one call.
Read the IBAN endpointSources
- IBAN Registry (ISO 13616) — country lengths, BBAN structures, SEPA membership — SWIFT, ISO 13616 registration authority, consulted 2026-10-24. Transcribed from the public registry; the release number in force on the day checkout opens must be written on the IBAN docs page (owner step).
- ISO 13616-1:2020 — Financial services — International bank account number (IBAN): structure and mod 97-10 check (ISO/IEC 7064) — ISO, consulted 2026-10-24
- EPC list of SEPA scheme countries (geographical scope of the SEPA schemes) — European Payments Council, consulted 2026-10-24. The 2025 additions (Albania, Moldova, Montenegro, North Macedonia, effective 5 October 2025) are flagged `sepaSince` on the row.
- SWIFTRef BIC Directory and IBAN Plus — licensed data products — SWIFT, consulted 2026-10-24. Why BIC lookup is not in v1: the directory is licensed, not open data.