# 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.

Updated 2026-10-30.

## At a glance

- 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

## 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.

## Call it

curl:

```bash
curl "https://api.bewary.app/v1/iban/DE89370400440532013000"
```

Python:

```python
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"])
```

JavaScript:

```javascript
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

Python:

```python
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'})
```

TypeScript, with the types from https://bewary.app/types/bewary.d.ts:

```typescript
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 }
}
```

## 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.

## Example response: unknown

Request: `GET /v1/iban/DE89370400440532013000`. Captured from the live API on 2026-10-31.

```json
{
  "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
}
```

## Example response: unknown

Request: `GET /v1/iban/DE89370400440532013000?claimed_country=FR`. Captured from the live API on 2026-10-31.

```json
{
  "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
}
```

## Example response: unknown

Request: `GET /v1/iban/DE89370400440532013001`. Captured from the live API on 2026-10-31.

```json
{
  "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
}
```

## IBAN facts

Every key is in every response, null when unknown. Keys marked coming stay null until their source ships.

| 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 | In | Type | Required | Description |
|---|---|---|---|---|
| `iban` | path | string | yes | The IBAN in the electronic form (DE89370400440532013000) or the print form with spaces, URL-encoded. |
| `claimed_country` | query | string | no | 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.

## Reason codes it can return

`LISTED_BLOCKLIST`, `LISTED_CAUTION`, `PLATFORM_TAKEDOWN`, `PLATFORM_WARNING`, `IBAN_CHECKSUM_INVALID`, `IBAN_FORMAT_INVALID`, `IBAN_COUNTRY_MISMATCH`
