IBAN validation API
The IBAN validation API at `GET /v1/iban/{iban}` checks ISO 13616 and returns scam, suspicious, safe or unknown. Code must never treat unknown as safe. Bewary returns bank name and BIC where it holds the country's national bank-code list. `GET /v1/datasets` lists those countries. Elsewhere, bank is null. If you pass `claimed_country`, a valid IBAN from another country carries `IBAN_COUNTRY_MISMATCH` in `signals`. The verdict stays unknown because cross-border accounts exist. Bewary never returns the account holder. A call costs 1 credit, a free key gets 1,000 credits a month, no card.
- Endpoint
- GET
https://api.bewary.app/v1/iban/{iban} - Price
- 1 credit
- Free key
- 1,000 credits a month, no card
- Returns
- verdict, p_scam, entities with typed facts, reason codes
- MCP tool
lookup_iban
Try it
Free from this page: 10 lookups a dayCall it
curl "https://api.bewary.app/v1/iban/DE89370400440532013000" import requests
r = requests.get("https://api.bewary.app/v1/iban/DE89370400440532013000", timeout=10)
result = r.json()
print(result["verdict"], result["p_scam"], result["reason_codes"]) const res = await fetch("https://api.bewary.app/v1/iban/DE89370400440532013000")
const result = await res.json()
console.log(result.verdict, result.p_scam, result.reason_codes) Act on the result
Copy-paste code that turns the answer into a decision. It runs with or without BEWARY_API_KEY. TypeScript uses bewary.d.ts.
import os, requests
def check_payee_iban(iban, claimed_country=None):
key = os.environ["BEWARY_API_KEY"] # a free key: bewary.app/account
headers = {"Authorization": f"Bearer {key}"}
r = requests.get(f"https://api.bewary.app/v1/iban/{iban.replace(' ', '')}",
params={"claimed_country": claimed_country} if claimed_country else None,
headers=headers, timeout=10)
r.raise_for_status()
result = r.json()
facts = result["entities"][0]["facts"]
cautions = [s["code"] for s in result["signals"]]
if not facts["valid"] or facts["listed"] or "IBAN_COUNTRY_MISMATCH" in cautions:
return "hold", cautions, facts["bank"]
return "pay", cautions, facts["bank"]
print(check_payee_iban("DE89 3704 0044 0532 0130 00", claimed_country="FR"))
# ('hold', ['IBAN_COUNTRY_MISMATCH'], {'name': 'Commerzbank', 'bic': 'COBADEFFXXX'}) import type { Envelope } from "./bewary"
export async function checkPayeeIban(iban: string, claimedCountry?: string) {
const key = process.env.BEWARY_API_KEY! // a free key: bewary.app/account
const headers: Record<string, string> = { Authorization: `Bearer ${key}` }
const q = claimedCountry ? `?claimed_country=${claimedCountry}` : ""
const res = await fetch(`https://api.bewary.app/v1/iban/${iban.replace(/\s/g, "")}${q}`,
{ headers, signal: AbortSignal.timeout(10_000) })
if (!res.ok) throw new Error(`Bewary ${res.status}`)
const r: Envelope = await res.json()
const e = r.entities[0]
if (e.type !== "iban") throw new Error("unexpected entity")
const cautions = r.signals.map((s) => s.code)
const hold = !e.facts.valid || e.facts.listed || cautions.includes("IBAN_COUNTRY_MISMATCH")
return { action: hold ? "hold" : "pay", cautions, bank: e.facts.bank }
} Response
The fields you will use
entities[0].facts.valid- ISO 13616: the country's length and structure, and the check digits.
entities[0].facts.bank- The bank's name and BIC from its country's list, or null where we hold none.
signals[]- IBAN_COUNTRY_MISMATCH with claimed_country, IBAN_CHECKSUM_INVALID or IBAN_FORMAT_INVALID. They are cautions: the verdict stays unknown.
entities[0].facts.listed- A scam report named this account.
{ "schema_version": "1.7.0", "request_id": "req_01M69WJ58YA0WAZ7MC0NS411PB", "checked_at": "2026-10-31T21:06:31Z", "engine": "bewary-engine/3.0.0", "models": { "calibration": "interim-band-v0" }, "input": { "kind": "lookup", "channel": "unknown", "language": null, "region": null }, "verdict": "unknown", "p_scam": 0.2, "risk_score": 20, "confidence": 0, "classification": null, "reason_codes": [], "entities": [ { "id": "e1", "type": "iban", "value": "DE89370400440532013000", "normalized": "DE89370400440532013000", "role": "payee", "verdict": "unknown", "p_scam": 0.2, "risk_score": 20, "reason_codes": [], "facts": { "country": "DE", "sepa": true, "valid": true, "invalid_reason": null, "print_form": "DE89 3704 0044 0532 0130 00", "bank_code": "37040044", "branch_code": null, "bank": { "name": "Commerzbank", "bic": "COBADEFFXXX", "source": "Quelle: Deutsche Bundesbank", "free_at": "https://www.bundesbank.de/de/aufgaben/unbarer-zahlungsverkehr/serviceangebot/bankleitzahlen/download-bankleitzahlen-602592" }, "listed": false, "list_count": null }, "related": [], "datasets": [ { "dataset": "iban_registry", "coverage": "WW", "in_coverage": true }, { "dataset": "bank_codes", "coverage": "WW", "in_coverage": true }, { "dataset": "community_reports", "coverage": "WW", "in_coverage": true } ] } ], "signals": [], "degraded": false, "degraded_reasons": [], "cached": false, "explanation": null} { "schema_version": "1.7.0", "request_id": "req_01M69WJ7J4C47CPVCBK7N5Y3CS", "checked_at": "2026-10-31T21:06:33Z", "engine": "bewary-engine/3.0.0", "models": { "calibration": "interim-band-v0" }, "input": { "kind": "lookup", "channel": "unknown", "language": null, "region": null }, "verdict": "unknown", "p_scam": 0.2, "risk_score": 20, "confidence": 0, "classification": null, "reason_codes": [], "entities": [ { "id": "e1", "type": "iban", "value": "DE89370400440532013000", "normalized": "DE89370400440532013000", "role": "payee", "verdict": "unknown", "p_scam": 0.2, "risk_score": 20, "reason_codes": [], "facts": { "country": "DE", "sepa": true, "valid": true, "invalid_reason": null, "print_form": "DE89 3704 0044 0532 0130 00", "bank_code": "37040044", "branch_code": null, "bank": { "name": "Commerzbank", "bic": "COBADEFFXXX", "source": "Quelle: Deutsche Bundesbank", "free_at": "https://www.bundesbank.de/de/aufgaben/unbarer-zahlungsverkehr/serviceangebot/bankleitzahlen/download-bankleitzahlen-602592" }, "listed": false, "list_count": null }, "related": [], "datasets": [ { "dataset": "iban_registry", "coverage": "WW", "in_coverage": true }, { "dataset": "bank_codes", "coverage": "WW", "in_coverage": true }, { "dataset": "community_reports", "coverage": "WW", "in_coverage": true } ] } ], "signals": [ { "code": "IBAN_COUNTRY_MISMATCH", "entity": "e1", "severity": "medium", "weight": 30, "source_class": "heuristic", "p": null, "dataset": "iban_registry", "coverage": "WW", "evidence": { "iban_country": "DE", "claimed_country": "FR" } } ], "degraded": false, "degraded_reasons": [], "cached": false, "explanation": null} { "schema_version": "1.7.0", "request_id": "req_01M69WJ9TXBACVPBTJWWTKH66S", "checked_at": "2026-10-31T21:06:36Z", "engine": "bewary-engine/3.0.0", "models": { "calibration": "interim-band-v0" }, "input": { "kind": "lookup", "channel": "unknown", "language": null, "region": null }, "verdict": "unknown", "p_scam": 0.2, "risk_score": 20, "confidence": 0, "classification": null, "reason_codes": [], "entities": [ { "id": "e1", "type": "iban", "value": "DE89370400440532013001", "normalized": "DE89370400440532013001", "role": "payee", "verdict": "unknown", "p_scam": 0.2, "risk_score": 20, "reason_codes": [], "facts": { "country": "DE", "sepa": true, "valid": false, "invalid_reason": "checksum", "print_form": "DE89 3704 0044 0532 0130 01", "bank_code": null, "branch_code": null, "bank": null, "listed": false, "list_count": null }, "related": [], "datasets": [ { "dataset": "iban_registry", "coverage": "WW", "in_coverage": true }, { "dataset": "bank_codes", "coverage": "WW", "in_coverage": true }, { "dataset": "community_reports", "coverage": "WW", "in_coverage": true } ] } ], "signals": [ { "code": "IBAN_CHECKSUM_INVALID", "entity": "e1", "severity": "medium", "weight": 20, "source_class": "heuristic", "p": null, "dataset": "iban_registry", "coverage": "WW", "evidence": { "country": "DE" } } ], "degraded": false, "degraded_reasons": [], "cached": false, "explanation": null} Real responses, captured from the live API on 2026-10-31. Every response has this shape: see Response format.
IBAN facts
Every key is in every response, null when unknown. A key marked Coming stays null until its source ships, so you can code against it now.
| Key | Type | Meaning |
|---|---|---|
country | ISO code or null | From the IBAN's first two letters. |
valid | boolean or null | ISO 13616: a registry country, its length, its structure and the check digits. |
invalid_reason | string or null | The first check that failed. |
print_form | string or null | Groups of four separated by a space. |
sepa | boolean or null | The SWIFT IBAN registry marks the country as SEPA. |
bank_code | string or null | The bank identifier inside the IBAN. null unless valid. |
branch_code | string or null | The branch identifier, where the country has one. |
bank | object or null | The bank's name and BIC from its country's national list, with the publisher's attribution (source) and the page where the list is free (free_at). null where we hold no list for the country. |
listed | boolean or null | A scam report named this account. |
Parameters
| Name | Type | Description |
|---|---|---|
ibanpath, required | string | The IBAN in the electronic form (DE89370400440532013000) or the print form with spaces, URL-encoded. |
claimed_countryquery | string | The country the payee or the invoice claims, ISO 3166-1 alpha-2, e.g. DE. A valid IBAN from another country then carries IBAN_COUNTRY_MISMATCH. |
Questions
How do I validate an IBAN with an API?
GET /v1/iban/{iban} validates IBANs under ISO 13616. It checks registry country, length, BBAN structure and MOD 97-10 check digits. Length or structure failures return IBAN_FORMAT_INVALID, and check-digit failures return IBAN_CHECKSUM_INVALID.
Does the IBAN API return the bank name and BIC?
Yes, where Bewary holds the country's national bank-code list. GET /v1/datasets lists covered countries in entity_countries for the bank_codes dataset. Elsewhere the bank is null, and IBAN validation still runs. The bank lists are free of charge from their publishers, the national central banks and the ECB: facts.bank.source names the publisher and facts.bank.free_at links its list.
Can the IBAN checker API identify the account holder?
No. IBAN validation describes the account number and its bank. Bewary never returns the account holder.
Used for
- Accounts-payable software checks supplier IBAN validity and scam reports before an invoice payment.
- A payments team rechecks a changed supplier IBAN, passing the supplier's claimed country as `claimed_country`.
- Marketplace signup forms validate seller or host payout IBANs before saving bank details.
Reason codes it can return
| Code | Meaning | Severity |
|---|---|---|
LISTED_BLOCKLIST | On scam blocklist | Critical |
LISTED_CAUTION | On a broad threat list that also holds spam and malware hosts | Medium |
PLATFORM_TAKEDOWN | The hosting platform took it down | High |
PLATFORM_WARNING | The platform shows a phishing or malware warning in front of it | High |
IBAN_CHECKSUM_INVALID | The IBAN fails its check digits | Medium |
IBAN_FORMAT_INVALID | Not a valid IBAN for its country | Medium |
IBAN_COUNTRY_MISMATCH | The account is in another country than the payee claims | Medium |
Reserved codes are in the contract, not emitted yet. All reason codes
Updated · This page as Markdown · Changelog