IBANforge

Validate IBAN

Validate a single IBAN with full checksum verification, country-specific BBAN structure parsing, automatic BIC/institution lookup, SEPA compliance data, issuer classification (bank vs. EMI/neobank), and risk indicators for compliance agents.

Endpoint

POST https://api.ibanforge.com/v1/iban/validate

Cost: $0.005 USDC per request

Request

Headers

HeaderValueRequired
Content-Typeapplication/jsonYes
AuthorizationBearer ifk_... (free API key)One of the two
X-PAYMENTx402 payment tokenOne of the two

Body

{
  "iban": "CH10 0023 0000 0000 1234 5"
}
FieldTypeDescription
ibanstringThe IBAN to validate. Spaces and hyphens are stripped automatically. Case-insensitive.

Response

Success (200)

{
  "iban": "CH1000230000000012345",
  "valid": true,
  "country": {
    "code": "CH",
    "name": "Switzerland"
  },
  "check_digits": "10",
  "bban": {
    "bank_code": "00230",
    "account_number": "000000012345"
  },
  "bic": {
    "code": "UBSWCHZH",
    "bank_name": "UBS Switzerland AG",
    "city": "Zürich"
  },
  "sepa": {
    "member": true,
    "schemes": ["SCT", "SDD"],
    "vop_required": false,
    "vop_participant": false
  },
  "issuer": {
    "type": "bank",
    "name": "UBS Switzerland AG",
    "classification": "default"
  },
  "bank_code_check": {
    "value": "00230",
    "status": "verified",
    "match": "register",
    "register": "SIX BankMaster (Swiss IID / BC-Nummer register)",
    "authoritative": true,
    "institution": {
      "name": "UBS Switzerland AG",
      "street": "Bahnhofstrasse 45",
      "post_code": "8098",
      "town": "Zürich",
      "country": "CH"
    },
    "as_of": "2026-08"
  },
  "risk_indicators": {
    "issuer_type": "bank",
    "country_risk": "standard",
    "test_bic": false,
    "sepa_reachable": true,
    "sepa_reachable_scope": "country",
    "vop_coverage": false
  },
  "clearing": {
    "iid": "00230",
    "name": "UBS Switzerland AG",
    "type": "bank",
    "town": "Zürich",
    "sic": true,
    "instant_payments_chf": true,
    "eurosic": true,
    "qr_iid": null
  },
  "formatted": "CH10 0023 0000 0000 1234 5",
  "cost_usdc": 0.005,
  "processing_ms": 1.23
}

Response fields

Top-level fields:

FieldTypePresentDescription
ibanstringAlwaysCleaned IBAN (uppercase, no spaces)
validbooleanAlwaysWhether the IBAN passed all validation checks
countryobjectValid IBANsCountry code and name
check_digitsstringValid IBANsThe two-digit check number
bbanobjectValid IBANsParsed BBAN components
bicobject | nullValid IBANsBIC/SWIFT code and institution data (null if no match found)
sepaobjectValid IBANsSEPA membership, schemes, and VoP requirement
issuerobjectValid IBANs with BICInstitution classification
bank_code_checkobjectValid IBANsWhether the bank code resolves in reference data — and how much that answer is worth (see What "verified" means)
next_stepsarrayWhen applicableMachine-readable follow-ups (screen compliance, verify the payee, …), each with the reason it is suggested
risk_indicatorsobjectValid IBANsComposite risk signal for compliance
clearingobject | nullValid CH/LI IBANsSwiss clearing data from the SIX BankMaster (BC-Nummer, rail participation, QR-IID); null when the IID is not listed
formattedstringValid IBANsIBAN with spaces every 4 characters
errorstringInvalid IBANsError code
error_detailstringInvalid IBANsHuman-readable error description
cost_usdcnumberAlwaysCost of this request in USDC
processing_msnumberAlwaysProcessing time in milliseconds

country object:

FieldTypeDescription
codestringISO 3166-1 alpha-2 country code
namestringFull country name in English

bban object:

FieldTypeDescription
bank_codestringBank/institution identifier extracted from BBAN
branch_codestring?Branch code (present for countries like FR, GB, ES, IT)
account_numberstringAccount number extracted from BBAN

bic object (present when a matching BIC is found):

FieldTypeDescription
codestringBIC/SWIFT code (8 characters)
bank_namestring | nullFinancial institution name
citystring | nullCity of the institution

sepa object:

FieldTypeDescription
memberbooleanWhether this country is in the SEPA zone
schemesstring[]Available SEPA schemes: SCT (Credit Transfer), SDD (Direct Debit), SCT_INST (Instant)
vop_requiredbooleanWhether Verification of Payee is mandatory (EU regulation, since Oct 2025 for eurozone)
vop_participantboolean | nullBank-level VoP readiness: true when the resolved institution is listed as ready in the EPC VoP scheme register; null when no institution was resolved

issuer object (present when BIC is resolved):

FieldTypeDescription
typestring | nullbank (traditional), digital_bank (neobank), emi (Electronic Money Institution), payment_institution — or null when no institution could be substantiated (for example, the bank code is not a listed IBAN issuer)
namestringInstitution name — who holds the matching BIC. May be stated even when type is null: naming the BIC holder is a fact, calling it your counterparty's bank would be a guess
classificationstringcurated — the type is a positive identification from maintained EMI/neobank/payment-institution lists; default — the type falls back to bank because most BIC holders are banks. Count on curated; treat default as a presumption
iban_issuerstring?Only for countries that publish a list of IBAN-issuing providers (today: NL). confirmed — the code is on that list; not_listed — it is not, type drops to null, and next_steps tells you the account may not exist

vIBAN detection: If issuer.type is emi, digital_bank, or payment_institution, the IBAN is more likely to be a virtual IBAN (vIBAN). This is useful for AML/CFT compliance under the EU AMLR regulation (July 2027).

bank_code_check object:

FieldTypeDescription
valuestringThe bank code taken from the BBAN, echoed for your logs
statusstringverified — the code resolves to an institution we can name; not_in_register — it does not, in reference data we hold for this country; unavailable — we hold no reference data for this country, no opinion
matchstring | nullregister — exact key in the reference set (deterministic); prefix — fallback BIC-prefix search, possible only where bank codes are letters; check candidates
registerstring | nullHuman name of the reference set consulted
authoritativebooleantrue only where the reference set IS the national register — see below
candidatesnumber?On match: "prefix": how many institutions matched. More than 1 means the answer is indicative
institutionobject?What the national register publishes about the allocated institution — name, seat address, LEI where available. Only on authoritative answers. Depth varies: CH/LI and AT full street address, DE postal code + town (its register has no street), BE name only. Absent fields are null, never guessed. This is the institution holding the bank code, not a branch, and not proof of any account
as_ofstringMonth of the reference data

risk_indicators object:

FieldTypeDescription
issuer_typestring | nullSame as issuer.type; null when no institution was substantiated
country_riskstringstandard, elevated (FATF grey list), or high (FATF black list / EU high-risk)
test_bicbooleanWhether the BIC is a test/sandbox code
sepa_reachablebooleanWhether the account's country is in the SEPA zone
sepa_reachable_scopestringcountry — the reachability statement is about the country's schemes, never about this specific account
vop_coveragebooleanWhether VoP is mandatory for this country

What "verified" means — and what it does not

bank_code_check.status: "verified" means the bank code resolves to an institution we can name in the reference data consulted. How much that is worth is exactly what authoritative says:

  • authoritative: true — the reference set is the national register itself. Today: CH and LI (SIX BankMaster), DE (Bundesbank Bankleitzahlendatei), FI (Finance Finland codes — allocated to banking groups, so a hit confirms the group), AT (OeNB directory), BE (BNB code list). In these countries, not_in_register means the code is not allocated — a strong reason to stop a payment.
  • authoritative: false — the reference set is our composite bank-code map, assembled from BIC directories. A hit names who holds the matching BIC; it does not prove that this institution issues IBANs. Where bank codes are letter-based, match: "prefix" with candidates > 1 means the answer is indicative only.

None of this confirms that the account exists, is open, or belongs to a particular person. A structurally valid IBAN naming a real institution can still be fabricated. For payee verification use the banks' own Verification of Payee flow (sepa.vop_required tells you when it is mandated) or a name check with your counterparty — whenever a response leaves that gap open, next_steps says so explicitly.

Invalid IBAN (200)

When the IBAN is invalid, the response still returns 200 but with valid: false:

{
  "iban": "CH5604835012345678000",
  "valid": false,
  "error": "checksum_failed",
  "error_detail": "Modulo 97 check returned 42, expected 1.",
  "cost_usdc": 0.005
}

Error codes

CodeDescription
invalid_formatIBAN contains invalid characters or is too short
unsupported_countryCountry code is not recognized
wrong_lengthIBAN length does not match expected length for this country
checksum_failedMOD-97 checksum verification failed

Code examples

cURL

curl -X POST https://api.ibanforge.com/v1/iban/validate \
  -H "Content-Type: application/json" \
  -d '{"iban": "DE89 3704 0044 0532 0130 00"}'

Python

import requests
 
response = requests.post(
    "https://api.ibanforge.com/v1/iban/validate",
    json={"iban": "DE89370400440532013000"},
)
 
data = response.json()
if data["valid"]:
    print(f"Bank: {data['bic']['bank_name']}")
    print(f"Country: {data['country']['name']}")
    print(f"SEPA: {data['sepa']['member']}")
    print(f"Issuer type: {data['issuer']['type']}")
    print(f"Risk: {data['risk_indicators']['country_risk']}")
else:
    print(f"Invalid: {data['error_detail']}")

TypeScript

const response = await fetch(
  "https://api.ibanforge.com/v1/iban/validate",
  {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify({ iban: "DE89370400440532013000" }),
  }
);
 
const data = await response.json();
 
if (data.valid) {
  console.log(`Bank: ${data.bic.bank_name}`);
  console.log(`SEPA: ${data.sepa.member}, VoP: ${data.sepa.vop_required}`);
  console.log(`Issuer: ${data.issuer.type} — ${data.issuer.name}`);
  console.log(`Country risk: ${data.risk_indicators.country_risk}`);
} else {
  console.log(`Invalid: ${data.error_detail}`);
}