Quick answer
openIBAN returns bank data for IBANs that are invalid. Feed it a German IBAN with a deliberately broken checksum and you get "valid": false alongside a complete bankData block β "name": "Commerzbank", "bic": "COBADEFFXXX", city, postcode, the lot. The presence of enriched bank details tells you nothing about whether the account number is well-formed, because the bank is resolved from the bank code portion of the IBAN, which is still perfectly readable in a string whose check digits are wrong. Any integration that treats "we got bank data back" as "the IBAN is good" will pass invalid account numbers straight into a payment file.
Does openIBAN return an error for a bad IBAN? π
No. It answers HTTP 200 with JSON for everything β valid IBANs, broken checksums, malformed strings, unsupported countries. There is no 4xx to catch, so response.raise_for_status() will never once fire on a bad input. The verdict lives in the body:
{
"valid": false,
"messages": [
"Validation failed.",
"Bank code valid: 37040044"
],
"iban": "DE89370400440532013001",
"bankData": {
"bankCode": "37040044",
"name": "Commerzbank",
"zip": "50447",
"city": "KΓΆln",
"bic": "COBADEFFXXX"
}
}
That is a real response to a real IBAN with one digit changed at the end. Look closely at messages: it contains a failure line and a success line at the same time. "Validation failed." refers to the IBAN's own check digits. "Bank code valid: 37040044" refers to the bank-code lookup, which succeeded independently. Both are true. Neither is the whole answer.
This is a sane design once you see it β the API is reporting the outcome of two separate checks rather than collapsing them into one boolean β but it punishes the obvious integration. Reading messages[0] and matching on the word "valid" gets you the wrong answer roughly half the time.
So which field do I actually trust?
valid. It is the only field that answers "is this IBAN well-formed". Everything else is enrichment that happens to be resolvable whether or not the IBAN passes.
There is a second trap in the other direction, and it matters more for money movement than the first. When you request validateBankCode=true, a bank-code lookup miss can flip valid to false on its own, even when the IBAN's checksum is perfectly fine. So valid: false does not always mean "the customer typed their IBAN wrong" β it can mean "the checksum is fine and we don't have that bank in the directory". Those two need different handling: one is a data-entry error you bounce back to the user, the other is a coverage gap you should not be bouncing at all.
That is why the openIBAN Validator passes the raw messages[] array through onto every row instead of inventing a tidier checksumValid boolean it would have to guess at:
iban: str
valid: bool | None
bankName: str | None
bic: str | None
bankCodeValid: bool | None
messages: list[str]
skipped: bool
bankCodeValid is carried separately precisely so the two failure modes stay distinguishable, and messages is preserved verbatim so you can audit any decision the API made.
What about country coverage?
Checksum validation works for every IBAN-format country β that part is pure arithmetic (ISO 13616 mod-97) and needs no directory at all.
Bank-directory enrichment is much narrower, and this is where honest documentation matters more than a marketing claim. Confirmed live returning full bank details: DE and NL. Confirmed explicitly checksum-only, with the API itself returning "No information available": FR, GB, ES, IT, PL. A handful of others were tested without a directory hit, which proves neither support nor absence.
If you need bank names across the whole EU, no free endpoint will give you that, and you should be suspicious of anything claiming otherwise. If you need to know whether 40,000 IBANs in a migration file are well-formed, the checksum path covers all of them.
FAQ
Does a valid IBAN mean the account exists?
No, and nothing keyless can tell you that. IBAN validation is a structural check β country code, length, mod-97 check digits, and optionally whether the bank code resolves. Whether that specific account is open, and whether the name on it matches your counterparty, are separate questions answered only by bank-side services.
Can I validate in bulk?
Yes. The API is one lookup per IBAN, so bulk validation is just controlled concurrency over that endpoint. Be considerate with the request rate against a free public service.
Are spaces in an IBAN a problem?
No. DE89 3704 0044 0532 0130 00 and DE89370400440532013000 both validate β humans write IBANs in groups of four and the API copes. Normalising whitespace before you send is still worth doing so your own deduplication works.
Why would an invalid IBAN still return a BIC?
Because the BIC comes from the bank code β digits 5 through 12 in a German IBAN β which is unaffected by a wrong check digit. The bank is real; the account number is not.
The short version
Everything is HTTP 200, so the status code is never your answer. valid is the only validity field. bankData is enrichment, not evidence. And valid: false has two distinct causes worth telling apart before you bounce a payment back at a customer.
Responses in this post were pulled live from openiban.com on 2026-09-09.
Top comments (0)