Building an IBAN Validation API: REST Endpoint Design and Edge Cases
Architect-level guide to IBAN API design — GET vs POST tradeoffs, error code standards, normalization contracts, KV caching strategy, rate limiting, and country-specific edge cases.
Building an IBAN validation API that handles production traffic requires deliberate decisions around endpoint design, error contracts, normalization behavior, caching strategy, and edge cases that surface only at scale. This guide covers the architectural choices — with the ibanchecker.cash API as a reference implementation — so you can design a robust service from the start rather than patch it after the first incident.
GET vs POST: Which HTTP Method Is Correct?
The first design question is whether the validation endpoint should be GET or POST. Both are used in the wild — the correct choice depends on your threat model.
GET with query parameter (GET /validate?iban=DE89...) is cache-friendly and stateless. However, IBANs in query strings appear in server access logs, CDN logs, browser history, and Referer headers. For applications processing customer bank account numbers, this is a compliance problem under GDPR and PCI-DSS.
POST with JSON body (POST /validate) keeps the IBAN out of logs by default and matches the pattern for sensitive data endpoints. The tradeoff is that POST responses are not HTTP-cacheable at the edge without custom logic.
The ibanchecker.cash API uses POST for all validation endpoints. For a B2B API processing financial data, POST is the correct default. If you choose GET, ensure your logging infrastructure redacts the iban query parameter.
Request and Response Shape
Single IBAN Validation
// Request
POST /api/v1/validate
Content-Type: application/json
{
"iban": "DE89370400440532013000"
}
// Success response — HTTP 200
{
"valid": true,
"iban": "DE89 3704 0044 0532 0130 00",
"country": "Germany",
"countryCode": "DE",
"bic": "DEUTDEDB",
"bankName": "Deutsche Bank",
"checkDigits": "89",
"formatted": "DE89 3704 0044 0532 0130 00"
}
// Invalid IBAN response — HTTP 200 (not 4xx — see below)
{
"valid": false,
"iban": "DE89370400440532013001",
"error": {
"code": "INVALID_CHECK_DIGITS",
"message": "MOD-97 check digit validation failed"
}
}Note: an invalid IBAN is a valid API call — return HTTP 200, not 422. Reserve 4xx for malformed requests (missing body, wrong Content-Type, request too large). A downstream system that treats every non-200 as a network error will mishandle IBAN failures otherwise.
Bulk Validation
// Request
POST /api/v1/validate/bulk
Content-Type: application/json
{
"ibans": [
"DE89370400440532013000",
"GB29NWBK60161331926819",
"INVALID"
]
}
// Response — HTTP 200
{
"results": [
{ "iban": "DE89370400440532013000", "valid": true, "bic": "DEUTDEDB" },
{ "iban": "GB29NWBK60161331926819", "valid": true, "bic": "NWBKGB2L" },
{ "iban": "INVALID", "valid": false, "error": { "code": "INVALID_FORMAT" } }
],
"summary": { "total": 3, "valid": 2, "invalid": 1 }
}Always return a result for every input IBAN in the same order. Never silently skip items. Include a summary block so callers can check counts without iterating the full results array.
Error Code Standardization
Machine-readable error codes let callers take specific action on each failure type. Use a fixed vocabulary — do not return free-text error strings as the primary discriminator.
INVALID_FORMAT— Not alphanumeric or does not start with a two-letter country code. The input cannot be an IBAN at all.UNKNOWN_COUNTRY— Country code is valid ISO 3166-1 but not in the IBAN registry (e.g., US, CA, AU).WRONG_LENGTH— Correct country code but wrong total character count.INVALID_CHECK_DIGITS— Structurally correct but MOD-97 returns a remainder other than 1.BANK_NOT_FOUND— IBAN is mathematically valid but the bank identifier does not match any record in your BIC database. The IBAN may be valid; bank data may just be unavailable.
Return the most specific applicable code. A wrong-length string should return WRONG_LENGTH, not INVALID_CHECK_DIGITS — the latter suggests the digits were at least evaluated.
Normalization Behavior
Define and document your normalization contract explicitly — callers will depend on it. The ibanchecker.cash API normalizes input before validation by:
- Stripping all whitespace (spaces, tabs, newlines).
- Converting to uppercase.
- Stripping hyphens.
The response always echoes back the normalized IBAN and a separate formatted field with canonical 4-character grouping. This makes it safe for callers to display the formatted version directly without re-processing.
// Input with spaces and lowercase — still validates correctly
{
"iban": "de89 3704 0044 0532 0130 00"
}
// Response
{
"valid": true,
"iban": "DE89370400440532013000", // normalized
"formatted": "DE89 3704 0044 0532 0130 00" // display form
}Rate Limiting Architecture
Apply rate limiting at the API key level, not the IP level. IP-based rate limiting punishes legitimate clients behind NAT or corporate proxies, and determined abusers rotate IPs freely.
A token bucket algorithm with a sliding window works better than a fixed window for burst tolerance. The ibanchecker.cash API uses a KV-backed sliding window:
// Conceptual implementation (Cloudflare Workers KV)
async function checkRateLimit(apiKey: string, kv: KVNamespace) {
const key = `ratelimit:${apiKey}`;
const now = Date.now();
const windowMs = 60 * 60 * 1000; // 1 hour
const limit = 1000; // requests per hour
const raw = await kv.get(key, "json") as { requests: number[]; } | null;
const requests: number[] = raw?.requests ?? [];
const recent = requests.filter(ts => now - ts < windowMs);
if (recent.length >= limit) {
return { allowed: false, remaining: 0, resetAt: recent[0] + windowMs };
}
recent.push(now);
await kv.put(key, JSON.stringify({ requests: recent }), { expirationTtl: 3600 });
return { allowed: true, remaining: limit - recent.length, resetAt: now + windowMs };
}Always return rate limit state in response headers so callers can self-throttle:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 847
X-RateLimit-Reset: 1748995200Return HTTP 429 when the limit is exceeded, with a Retry-After header. Never silently drop requests — callers must know the difference between a network timeout and a rate limit.
Caching Strategy
IBAN validation results are deterministic: the same IBAN always produces the same validity result. MOD-97 is a pure mathematical function. This makes validation results safe to cache indefinitely.
Bank metadata (name, BIC, branch) changes occasionally when banks merge or restructure. Cache it with a TTL of 24–72 hours. Use Cloudflare KV or Redis as the cache layer:
async function validateWithCache(iban: string, kv: KVNamespace) {
const cacheKey = `iban:${iban}`;
const cached = await kv.get(cacheKey, "json");
if (cached) return cached;
const result = await performValidation(iban);
// Cache valid IBANs for 24 hours; invalids for 1 hour
const ttl = result.valid ? 86400 : 3600;
await kv.put(cacheKey, JSON.stringify(result), { expirationTtl: ttl });
return result;
}Add a X-Cache: HIT / MISS header to responses so you can measure cache efficiency in your analytics.
Country-Specific Edge Cases
The specification hides several country-specific quirks that your validator must handle:
- Mauritius (MU): 30 characters with the bank code embedded in positions 5–10 as a mix of letters and digits — unusual for IBAN.
- Saint Lucia (LC): 32 characters, the longest IBAN in use.
- Kosovo (XK): Uses the non-standard
XKcountry code (not official ISO 3166-1 alpha-2), registered under the IBAN experimental format. - Brazil (BR): 29 characters, unique in that it uses a currency account type digit.
- Territories vs. countries: French overseas territories (Martinique, Guadeloupe, French Guiana) use the
FRcountry code and 27-character format. Faroe Islands and Greenland useFOandGLrespectively, notDK.
CORS and Authentication Headers
If your API is consumed from browser clients:
// Required CORS headers for browser clients
Access-Control-Allow-Origin: * // or specific allowed origins
Access-Control-Allow-Methods: POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 86400
// Authentication header pattern
Authorization: Bearer <api_key>Handle OPTIONS preflight requests explicitly — return 204 with CORS headers and no body. Many edge runtimes do not handle OPTIONS automatically.
Referencing the ibanchecker.cash API
If you want a production-grade IBAN validation API without building your own, the ibanchecker.cash developer API implements all of the patterns described here: POST endpoints, structured error codes, normalization, KV-backed caching, bulk validation, and rate limiting by API key. It covers all 84 IBAN countries and returns BIC and bank metadata alongside the validity result. The pricing page has plan details.
Last updated: June 2026
Validate an IBAN instantly
Free IBAN checker — MOD-97 verification, bank lookup, and SEPA status across 84 countries.
Open IBAN Checker →Related Articles