Validate an IBAN in Python with MOD-97
A complete, working validate_iban() function — input cleaning, a country length table, letter transliteration, and the ISO 7064 MOD-97 checksum — with every step explained and the code actually run against real IBANs.
What an IBAN checksum actually verifies
An IBAN (International Bank Account Number, defined by ISO 13616) starts with a two-letter country code, then two check digits, then a country-specific string of up to 30 characters identifying the bank and account. Those two check digits are computed from everything else in the number using the ISO/IEC 7064 MOD 97-10 scheme. When you validate an IBAN you rerun that computation and confirm the result is what it should be. If a digit was mistyped or two characters were swapped, the math no longer lands where it should and you can reject the number before it ever reaches a payment system.
MOD-97 is a strong check: a prime modulus of 97 plus two check digits catches every single-character error and every single transposition. For the theory behind why 97 works so well, see how check digits work and our methodology. To just paste in one IBAN and see the answer, the IBAN checker does that in the browser.
The algorithm in five steps
The full validation procedure is short:
- Clean the input: remove spaces and punctuation, force uppercase.
- Check the shape: two letters, two digits, then the body; total length 15–34.
- Check the country length: each country fixes an exact length (Germany is always 22, France 27, and so on).
- Rearrange and transliterate: move the first four characters to the end, then replace every letter with two digits (A=10, B=11, … Z=35).
- Take mod 97 of the resulting integer. The IBAN is valid if and only if the remainder is 1.
The country length table
You do not strictly need country lengths — the checksum alone catches almost everything — but the table turns a vague "invalid" into a precise reason and rejects obviously-wrong lengths early. Here are the major SEPA countries. The full ISO 13616 registry covers roughly 90 countries; add any you need from it.
IBAN_LENGTHS = {
"AD": 24, "AT": 20, "BE": 16, "BG": 22, "CH": 21, "CY": 28,
"CZ": 24, "DE": 22, "DK": 18, "EE": 20, "ES": 24, "FI": 18,
"FR": 27, "GB": 22, "GR": 27, "HR": 21, "HU": 28, "IE": 22,
"IS": 26, "IT": 27, "LI": 21, "LT": 20, "LU": 20, "LV": 21,
"MC": 27, "MT": 31, "NL": 18, "NO": 15, "PL": 28, "PT": 25,
"RO": 24, "SE": 24, "SI": 19, "SK": 24, "SM": 27,
}
The complete function
This is the whole validator. It has no dependencies — standard-library Python only.
def validate_iban(iban: str) -> bool:
# 1. Clean: keep only letters and digits, force uppercase
s = "".join(ch for ch in iban.upper() if ch.isalnum())
# 2. Reject impossible lengths (IBANs are 15-34 chars)
if len(s) < 15 or len(s) > 34:
return False
# 3. First two chars must be a country code, next two the check digits
if not (s[:2].isalpha() and s[2:4].isdigit()):
return False
# 4. If we know this country, enforce its exact length
cc = s[:2]
if cc in IBAN_LENGTHS and len(s) != IBAN_LENGTHS[cc]:
return False
# 5. Move the first four characters to the end
rearranged = s[4:] + s[:4]
# 6. Transliterate letters: A -> 10, B -> 11, ... Z -> 35
digits = "".join(
ch if ch.isdigit() else str(ord(ch) - 55)
for ch in rearranged
)
# 7. Valid if and only if the big integer is congruent to 1 mod 97
return int(digits) % 97 == 1
Why each step is there
Step 1 is the part people forget. Real IBANs are printed with spaces every four characters (DE89 3704 0044 0532 0130 00) and are sometimes pasted in lowercase. ch.isalnum() keeps only letters and digits, so spaces, tabs and stray punctuation vanish; .upper() normalises the case before anything else runs. Steps 2–4 reject garbage cheaply so the checksum only sees plausible input. Step 5 is the MOD-97 rotation: the country code and check digits move to the back. Step 6 is the transliteration — a letter's value is its position in the alphabet plus 9, which is exactly ord(ch) - 55 because ord('A') is 65 and 65 − 55 = 10. Step 7 is where Python shines: int(digits) can be 20+ digits long, far past a 64-bit integer, but Python integers have arbitrary precision, so % 97 just works with no big-number library.
Worked example: DE89370400440532013000
Take the German test IBAN DE89370400440532013000. After cleaning it is already 22 characters, and DE expects 22, so we continue.
Rearrange — move DE89 to the end:
370400440532013000DE89
Transliterate — D=13, E=14, and the digits stay as they are:
370400440532013000131489
Take mod 97 of that 24-digit integer:
>>> validate_iban("DE89370400440532013000")
True
>>> validate_iban("DE89 3704 0044 0532 0130 00") # spaces are fine
True
>>> 370400440532013000131489 % 97
1
The remainder is 1, so the IBAN passes. Flip one digit — DE89370400440532013001 — and the same function returns False, because the altered number no longer leaves a remainder of 1.
Edge cases and the classic mistakes
- Leading zeros are meaningful — keep the IBAN a string. Never store or read an IBAN as an integer. Many valid IBANs and account bodies begin with
0, andint("00420")silently becomes420, corrupting the value and the checksum. Read from CSV, JSON or a database as text, and treat the IBAN as text end to end. - Spaces and lowercase. Input from humans is messy. The cleaning step handles both, but only if you actually run it — skipping it is the most common reason a perfectly good IBAN "fails".
- Wrong length for the country. A 21-character string starting
DEis invalid even if the leftover characters happen to make the checksum pass, which is why the length guard runs first. - Check digits are computed, not chosen. A hand-written placeholder like
00will be rejected. To generate valid check digits, put00in positions 3–4, run the same rearrange-and-transliterate on the rest, and set the check digits to98 - (remainder). - The 64-bit trap in other languages. Python's arbitrary-precision integers hide a problem that bites C, Java, Go and JavaScript: the transliterated number overflows a 64-bit integer. There you must use a big-integer type or fold the number in chunks, carrying the running remainder. (For the JavaScript version of exactly this, see validate an IBAN in JavaScript.)
Checksum vs. registry: what MOD-97 cannot tell you
A passing checksum means the IBAN is self-consistent — it was almost certainly not mistyped. It does not mean the account exists, is open, or belongs to the person you think. A closed account, a valid-but-unassigned number, or a real IBAN for the wrong beneficiary all pass MOD-97 happily. Structural validation is the cheap first gate; confirming an account is real and payable needs a bank-level or confirmation-of-payee check. Always verify bank details directly with the account holder before sending money.
Validating at scale?
If you are checking IBANs one at a time in a script, the function above is all you need — copy it and go. If you are validating thousands of them inside an app, or want the same answer from a language without a clean big-integer type, the CodeClassify API runs this exact MOD-97 check and returns structured JSON. Send one IBAN or a batch of up to 100:
curl -X POST https://codeclassify-api.rosariovitale0096.workers.dev/v1/iban/validate \
-H "X-Api-Key: ccl_your_key_here" \
-H "Content-Type: application/json" \
-d '{"iban":"DE89370400440532013000"}'
Response:
{
"ok": true,
"count": 1,
"valid": 1,
"results": [
{ "input": "DE89370400440532013000",
"iban": "DE89370400440532013000",
"country": "DE",
"valid": true }
]
}
Send a list with {"ibans":["DE89...","GB82..."]} to check up to 100 per call. The free tier includes 10 calls a month; see the API page for keys and pricing, and the bulk barcode validator if your spreadsheet is full of GTINs rather than IBANs.
FAQ
Why can Python validate an IBAN without a big-number library?
Python integers have arbitrary precision. After transliteration an IBAN becomes a number with 20 or more digits, which overflows a 64-bit integer in languages like C or Java. In Python, int(digits) % 97 just works because int grows as large as it needs to. In other languages you either use a big-integer type or compute the remainder piecewise, taking the string in chunks and carrying the running remainder forward.
Do I need the length of every country to validate an IBAN?
No. The MOD-97 checksum alone catches almost every typo, and it works without a country table. The length table is a cheap extra guard: it rejects a string that has the wrong number of characters for its country before you run the checksum, which turns a vague failure into a clear reason. The official ISO 13616 registry lists lengths for about 90 countries; the table in this guide covers the major SEPA ones, and you can add more from the registry.
Does a valid IBAN checksum mean the bank account exists?
No. Passing MOD-97 only proves the IBAN is internally consistent and was probably not mistyped. It does not prove the account is open, that it belongs to the person you expect, or that the bank code is currently in service. Checksum validation is a first filter. To confirm an account is real and payable you need a bank-level check or a confirmation-of-payee service, not a formula.
Check an IBAN without writing code
Paste any IBAN into the IBAN checker to see its country, length and MOD-97 result instantly — no Python required.
This guide is for general information only. IBAN checksum validation confirms internal consistency of a number, not that a bank account is registered, open, or in use. Always confirm bank details directly with the account holder or bank before sending payments. See data sources for the standards referenced here.