The API.
Everything this site does, as JSON: validate IBANs and resolve their BICs from the official national registries. Explicit statuses, stable failure codes, and the IBANs you send are never stored.
https://ibantobic.com/api/v1
Authentication
Sign in to create an API key, then send it as a bearer token with every request:
curl -X POST https://ibantobic.com/api/v1/lookup \
-H "Authorization: Bearer ibtb_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"iban": "BE68 5390 0754 7034"}'Keys are shown once and stored only as fingerprints; revoke them any time from the dashboard.
Statuses, not errors
Every lookup outcome is a 200 with an explicit data.status: found (use data.recommended_bic), valid_but_unknown (well-formed, but the bank is not in the directory), or invalid (see data.failure.reason — seven stable codes with user-ready messages). The error envelope is reserved for transport problems.
Look up one IBAN
POST/lookup
The recommended form: the IBAN travels in the body, so it never appears in a URL or an access log — ours or yours.
{
"data": {
"status": "found",
"bank_identifier": "539",
"institution": { "name": "..." },
"recommended_bic": { "bic": "GKCCBEBB", "bic11": "GKCCBEBBXXX" },
...
},
"meta": { "directory": { "country_code": "BE", "imported_at": "...", "as_of": "..." } }
}GET/iban/{iban}
The same lookup with the IBAN in the URL — handy for trying things out, but remember that URLs end up in access logs.
Batch
POST/lookup/batch
Up to 500 IBANs per request, answered in order with a compact row per input plus a summary. Each row consumes one quota unit.
curl -X POST https://ibantobic.com/api/v1/lookup/batch \
-H "Authorization: Bearer ibtb_live_YOUR_KEY" \
-H "Content-Type: application/json" \
-d '{"ibans": ["BE68539007547034", "NL91ABNA0417164300"]}'Validate only
POST/validate
Runs the staged IBAN checks — country, characters, check digits, length, checksum — without touching the bank directory. GET /validate/{iban} exists too. Validations are metered like lookups.
Errors
Transport problems use one envelope: {"error": {"status", "code", "message"}}.
| Status | Code | Meaning |
|---|---|---|
401 | unauthenticated | No key, an unknown key, or a revoked key. |
422 | validation_failed | The request body is malformed; error.errors names the fields. |
404 | not_found | No such endpoint. |
429 | rate_limited | Too many requests this minute — honour Retry-After. |
429 | quota_exceeded | The plan’s quota is used up until X-Quota-Reset. |
429 | quota_insufficient | A batch needs more units than remain in the period. |
500 | server_error | Something went wrong on our side. |
Rate limits and quotas
Every response reports where you stand:
| Header | Meaning |
|---|---|
X-RateLimit-Limit | Requests allowed per minute for your plan. |
X-RateLimit-Remaining | Requests left in the current minute. |
X-Quota-Limit | Metered units per quota period for your plan. |
X-Quota-Remaining | Units left in the current period. |
X-Quota-Reset | When the period resets (ISO 8601). |
At 80% of quota we email you; at 100% the API answers 429 quota_exceeded until the reset.
