# IBAN validation API

Source: https://veriport.io/iban-validation-api (published 2026-10-28)

An IBAN validation API tells a payout or onboarding system, before money moves, whether an account number is well formed: the right country, the right length for it, check digits that agree with the rest (ISO 7064 mod 97-10), and a national part shaped as the registry says. Veriport answers all five checks separately for 88 countries from the SWIFT IBAN Registry (ISO 13616), returns the bank and branch identifiers where the registry defines them, and flags the 41 SEPA countries; the offline part is also an MIT library.

- Five checks, each reported: `characters`, `country`, `length`, `mod97`, `bbanPattern`; `failures[]` names the rule that broke, so a typo is told apart from an unknown country.
- The bank identifier is read from the position the registry defines (the first four digits in France, eight in Germany); the branch identifier where one exists. No BIC: the directory is licensed data.
- SEPA flag from the EPC list of SEPA scheme countries, the four countries added on 5 October 2025 included, with `sepaSince` when recent.
- Prices: $2.90 to $4.50 per 1,000, against €530 a year at iban.com; the mod-97 check itself is free in the open-source SDK, no network needed.

## The answer, field by field

A German IBAN, all checks passed:

| Field | What it says |
|---|---|
| `valid` | True when the five checks pass. A failed IBAN is a 200 with `valid: false`, never an error. |
| `iban`, `formatted` | The electronic form (upper case, no separators) and the paper form in groups of four. |
| `countryCode`, `countryName`, `expectedLength` | The registry entry the IBAN was checked against. |
| `checkDigits`, `bban` | The two check digits and the national part. |
| `bankIdentifier`, `branchIdentifier` | Cut from the BBAN at the positions the registry defines; null where it defines none. |
| `sepa`, `sepaSince` | Whether the country is in the SEPA schemes, and since when if recent. |
| `checks`, `failures` | The five booleans and the list of failed rules (`characters`, `country_unknown`, `length`, `check_digits`, `bban_pattern`). |
| `registry` | The source of the lengths and structures and the day they were transcribed. |

```json
{
  "valid": true,
  "iban": "DE89370400440532013000",
  "formatted": "DE89 3704 0044 0532 0130 00",
  "countryCode": "DE",
  "countryName": "Germany",
  "checkDigits": "89",
  "bban": "370400440532013000",
  "bankIdentifier": "37040044",
  "branchIdentifier": null,
  "expectedLength": 22,
  "sepa": true,
  "sepaSince": null,
  "checks": {
    "characters": true,
    "country": true,
    "length": true,
    "mod97": true,
    "bbanPattern": true
  },
  "failures": [],
  "registry": {
    "source": "SWIFT IBAN Registry (ISO 13616)",
    "verifiedOn": "2026-10-24"
  }
}
```

## No upstream, so no degraded path

The IBAN check is deterministic: a registry table and an arithmetic rule, both in the service. Nothing is fetched, nothing is cached, the IBAN you send is not stored. The only states are a 200, a 400 for a missing parameter, and your own rate or quota limits (429, 402).

## The same call in six languages

One GET, the key optional for the first 20 calls a day.

```bash
curl "https://veriport.io/api/v1/iban/validate?iban=DE89370400440532013000"
# with a key, once you have one:
# curl -H "Authorization: Bearer vp_live_YOUR_KEY" "https://veriport.io/api/v1/iban/validate?iban=DE89370400440532013000"
```

```javascript
const KEY = process.env.VERIPORT_KEY; // optional for the first calls of the day
const res = await fetch("https://veriport.io/api/v1/iban/validate?iban=DE89370400440532013000", { headers: KEY ? { Authorization: "Bearer " + KEY } : {} });
const body = await res.json();
if (!res.ok) {
  // RFC 9457 problem document: body.code is stable, body.detail is for humans
  throw new Error(body.code + ": " + body.detail);
}
console.log(body.valid, body.countryName, body.sepa);
```

```python
import os, requests

key = os.environ.get("VERIPORT_KEY")  # optional for the first calls of the day
r = requests.get("https://veriport.io/api/v1/iban/validate?iban=DE89370400440532013000", headers={"Authorization": f"Bearer {key}"} if key else {}, timeout=15)
body = r.json()
if not r.ok:
    # RFC 9457 problem document: body["code"] is stable, body["detail"] is for humans
    raise RuntimeError(f"{body.get('code')}: {body.get('detail')}")
print(body["valid"], body["countryName"], body["sepa"])
```

```php
<?php
$key = getenv("VERIPORT_KEY") ?: null; // optional for the first calls of the day
$ch = curl_init("https://veriport.io/api/v1/iban/validate?iban=DE89370400440532013000");
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_HTTPHEADER => $key ? ["Authorization: Bearer $key"] : []]);
$body = json_decode(curl_exec($ch), true);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
curl_close($ch);
if ($status >= 400) {
    // RFC 9457 problem document: $body["code"] is stable, $body["detail"] is for humans
    throw new RuntimeException(($body["code"] ?? "error") . ": " . ($body["detail"] ?? ""));
}
var_dump($body["valid"], $body["countryName"], $body["sepa"]);
```

```go
package main

import (
	"encoding/json"
	"fmt"
	"net/http"
	"os"
)

func main() {
	req, _ := http.NewRequest("GET", "https://veriport.io/api/v1/iban/validate?iban=DE89370400440532013000", nil)
	if key := os.Getenv("VERIPORT_KEY"); key != "" { // optional for the first calls of the day
		req.Header.Set("Authorization", "Bearer "+key)
	}
	res, err := http.DefaultClient.Do(req)
	if err != nil {
		panic(err)
	}
	defer res.Body.Close()
	var body map[string]any
	json.NewDecoder(res.Body).Decode(&body)
	if res.StatusCode >= 400 {
		// RFC 9457 problem document: body["code"] is stable, body["detail"] is for humans
		panic(fmt.Sprintf("%v: %v", body["code"], body["detail"]))
	}
	fmt.Println(body["valid"], body["countryName"], body["sepa"])
}
```

```csharp
using System.Net.Http.Headers;
using System.Text.Json;

var http = new HttpClient();
var key = Environment.GetEnvironmentVariable("VERIPORT_KEY"); // optional for the first calls of the day
if (!string.IsNullOrEmpty(key)) http.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", key);
var res = await http.GetAsync("https://veriport.io/api/v1/iban/validate?iban=DE89370400440532013000");
using var body = JsonDocument.Parse(await res.Content.ReadAsStringAsync());
if (!res.IsSuccessStatusCode)
{
    // RFC 9457 problem document: "code" is stable, "detail" is for humans
    throw new Exception($"{body.RootElement.GetProperty("code")}: {body.RootElement.GetProperty("detail")}");
}
Console.WriteLine(body.RootElement.GetProperty("valid") + " " + body.RootElement.GetProperty("countryName") + " " + body.RootElement.GetProperty("sepa"));
```

## Price per 1,000 calls, against the named alternatives

Competitors’ figures are read from their public pricing pages on 2026-10-28 at full use of the included volume; a range means several tiers. Ours include the tax of your country, charged by Stripe as merchant of record.

| Offer | Price | Included | Per 1,000 | Billing |
|---|---|---|---|---|
| [iban.com](https://www.iban.com/pricing) IBAN Suite | €530 / year | 2,000 validations (entry plan) | ≈ €265 | Annual subscription; bank name and BIC included |
| [ibanapi.com](https://ibanapi.com/) | monthly tiers | per tier | $3.80 | Subscription |
| OpenIBAN | free | unlimited | $0 | Open source, unmaintained, self-hosted |
| **Veriport** 2,000 pack | $9 once | 2,000 calls, 12 months | $4.50 | Prepaid, no renewal |
| **Veriport** 10,000 pack | $29 once | 10,000 calls, 12 months | $2.90 | Prepaid, no renewal |
| **Veriport** monthly plan | $15 / month | 5,000 calls + bulk | $3.00 | Subscription, cancel any time |

> Free before any of this: 100 calls a month with a key, 20 a day without one. A 4xx or 5xx is never counted.

## What it is not

Not a proof that the account exists or belongs to your payee: nothing public can tell that; payee verification is a bank-to-bank service. Not a BIC or bank-name lookup: SWIFT’s BIC Directory and IBAN Plus are licensed products, and the line is on the roadmap. Not a SEPA reachability check.

## Questions

### Why pay for a mod-97 check I can run locally?

You should run it locally, and the SDK does it for free. The API adds what a library drifts on: the registry lengths and BBAN patterns kept current, the SEPA list with its dates, the bank and branch positions, and one place to validate from any language or a no-code tool.

### Which countries are covered?

88 countries and territories of the SWIFT IBAN Registry, each with its own page under IBAN formats; a country outside the registry answers `country_unknown`.

### Is the IBAN stored?

No. The check runs in memory and the request body is not logged; metering records the count, not the value.

## Read next

- [Reference: GET /iban/validate](https://veriport.io/docs/iban-validation)
- [Free IBAN checker](https://veriport.io/tools/iban-checker)
- [IBAN formats by country](https://veriport.io/iban-format)
- [The IBAN mod-97 check, explained](https://veriport.io/guides/validate-iban-mod-97-check-explained)
- [Pricing](https://veriport.io/pricing)

## Sources

- IBAN Registry (ISO 13616) — country lengths, BBAN structures, SEPA membership — https://www.swift.com/standards/data-standards/iban-international-bank-account-number (2026-10-24)
- ISO 13616-1:2020 — Financial services — International bank account number (IBAN): structure and mod 97-10 check (ISO/IEC 7064) — https://www.iso.org/standard/81090.html (2026-10-24)
- EPC list of SEPA scheme countries (geographical scope of the SEPA schemes) — https://www.europeanpaymentscouncil.eu/document-library/other/epc-list-sepa-scheme-countries (2026-10-24)
- iban.com IBAN Suite and BIC validation pricing — https://www.iban.com/pricing (2026-10-24)
- SWIFTRef BIC Directory and IBAN Plus — licensed data products — https://www.swift.com/our-solutions/compliance-and-shared-services/swiftref (2026-10-24)
