API docs
A JSON API for checking the format and check digits of IBANs, BICs, EU VAT numbers, GTIN/EAN/UPC codes and ISBNs. Base URL: https://checkdigit
Endpoints
| Method and path | What it does | Key |
|---|---|---|
GET /v1/iban/{iban} | IBAN: country, length, BBAN layout, mod-97 check digits | optional |
GET /v1/bic/{bic} | BIC / SWIFT code: structure and country (no bank directory) | optional |
GET /v1/vat/{number} | EU VAT number: format and check digit, offline (no VIES) | optional |
GET /v1/gtin/{code} | EAN-8, UPC-A, EAN-13, GTIN-14 and ISBN-10: check digit and conversions | optional |
GET /v1/isbn/{code} | ISBN-10 or ISBN-13: check digit and ISBN-10/13 conversion | optional |
GET /v1/detect/{code} | Detects the type, then validates it | optional |
POST /v1/{type} | Same as the GET endpoints above (iban, bic, vat, gtin, isbn, detect) with the code in a JSON body {"code": "..."} instead of the URL. Recommended for IBANs and VAT numbers. | optional |
POST /v1/batch | Up to 500 mixed codes in one request | Pro key |
GET /v1/meta | Version, supported countries and formats, limits | optional |
Send the code in the URL path (GET) or in a JSON body (POST, see below). Spaces, hyphens and dots are ignored and letters are upper-cased, so de89 3704 0044 0532 0130 00 works. A leading label is ignored too: IBAN:, VAT:, BIC:, ISBN / ISBN-13: (on /v1/detect and on the matching endpoint). Percent-encode codes in URLs (spaces as %20). Codes can be at most 64 characters.
Every validation returns HTTP 200 with "valid": true or "valid": false. When it is false, reason explains why, for example "checksum fails, expected 7". Other HTTP status codes mean the request itself could not be processed (see Errors).
POST/v1/{type} (iban, bic, vat, gtin, isbn, detect)
Every single-code endpoint also accepts POST with the code in a JSON body. The response is the same as for GET. Use POST for IBANs, VAT numbers and anything else you'd rather not have in URLs: a URL path can end up in request logs, proxies and browser history, a request body normally doesn't. No API key is needed; the limits, quotas and 32 KB body cap are the same as for GET. The free checker on the home page uses POST /v1/detect.
curl -X POST https://checkdigit/v1/iban \
-H "Content-Type: application/json" \
-d '{"code": "DE89 3704 0044 0532 0130 00"}'
Errors: no or empty code gives 400 missing_code, a body that isn't JSON gives 400 invalid_json, and a code in the URL as well (POST /v1/iban/DE89...) gives 400 code_in_url.
GET/v1/iban/{iban}
Checks the country code against the SWIFT IBAN Registry (89 countries), the length and BBAN layout for that country, and the ISO 13616 mod-97 check digits. Returns valid, country, bban, check_digits, formatted plus the bank code and SEPA flag. National check digits inside the BBAN are not verified in this version.
curl https://checkdigit/v1/iban/DE89370400440532013000
{
"type": "iban",
"input": "DE89370400440532013000",
"normalized": "DE89370400440532013000",
"valid": true,
"country": "DE",
"country_name": "Germany",
"sepa": true,
"check_digits": "89",
"bban": "370400440532013000",
"bank_code": "37040044",
"formatted": "DE89 3704 0044 0532 0130 00",
"electronic": "DE89370400440532013000"
}
An invalid IBAN:
{
"type": "iban",
"input": "DE89370400440532013001",
"normalized": "DE89370400440532013001",
"valid": false,
"reason": "checksum fails, expected check digits 62 (mod 97)",
"country": "DE",
"country_name": "Germany",
"sepa": true,
"check_digits": "89",
"bban": "370400440532013001",
"bank_code": "37040044",
"formatted": "DE89 3704 0044 0532 0130 01",
"expected_check_digits": "62"
}
GET/v1/bic/{bic}
Checks the ISO 9362 structure (4-character party prefix, ISO 3166 country code, 2-character location code, optional 3-character branch code). It flags test BICs (location code ending in 0). It does not look up banks or confirm that a BIC is registered.
curl https://checkdigit/v1/bic/DEUTDEFF500
{
"type": "bic",
"input": "DEUTDEFF500",
"normalized": "DEUTDEFF500",
"valid": true,
"bank_code": "DEUT",
"country": "DE",
"country_name": "Germany",
"location_code": "FF",
"branch_code": "500",
"primary_office": false,
"test_bic": false,
"bic8": "DEUTDEFF",
"bic11": "DEUTDEFF500"
}
GET/v1/vat/{number}
Include the country prefix: AT, BE, BG, CY, CZ, DE, DK, EE, EL (Greece; GR is accepted and mapped to EL), ES, FI, FR, HR, HU, IE, IT, LT, LU, LV, MT, NL, PL, PT, RO, SE, SI, SK, plus XI (Northern Ireland). The check runs offline using each country's published check-digit rule. VIES is not queried, so the response says nothing about whether the number is registered or active ("vies_checked": false).
curl https://checkdigit/v1/vat/ATU13585627
{
"type": "vat",
"input": "ATU13585627",
"normalized": "ATU13585627",
"valid": true,
"country": "AT",
"country_name": "Austria",
"number": "U13585627",
"checksum": "passed",
"vies_checked": false
}
{
"type": "vat",
"input": "DE136695977",
"normalized": "DE136695977",
"valid": false,
"reason": "checksum fails, expected 6",
"country": "DE",
"country_name": "Germany",
"number": "136695977",
"expected_check": "6"
}
GET/v1/gtin/{code}
EAN-8, UPC-A (12 digits), EAN-13 and GTIN-14 with the GS1 mod-10 check digit. A 10-character ISBN-10 is accepted too. The response includes the GTIN-14 form, UPC-A to EAN-13 conversion, the ISBN-10/ISBN-13 forms for 978/979 codes, and a note for special GS1 prefixes (for example restricted-circulation or coupon ranges). UPC-E (the 8-digit zero-suppressed UPC) is not supported in this version: an 8-digit code is always checked as EAN-8.
curl https://checkdigit/v1/gtin/036000291452
{
"type": "gtin",
"input": "036000291452",
"normalized": "036000291452",
"valid": true,
"format": "UPC-A",
"check_digit": "2",
"gtin14": "00036000291452",
"ean13": "0036000291452",
"prefix_note": "GS1 US (UPC-compatible, prefix 001-019)"
}
{
"type": "gtin",
"input": "4006381333932",
"normalized": "4006381333932",
"valid": false,
"reason": "checksum fails, expected 1",
"format": "EAN-13",
"check_digit": "2",
"expected_check_digit": "1"
}
GET/v1/isbn/{code}
ISBN-10 (mod 11, check character 0-9 or X) or ISBN-13 (978/979 prefix, mod 10). Converts ISBN-10 to ISBN-13 and back (979 ISBNs have no ISBN-10 form). Hyphens and an ISBN prefix are ignored. Hyphenation into group/publisher parts is not provided.
curl https://checkdigit/v1/isbn/0-306-40615-2
{
"type": "isbn",
"input": "0-306-40615-2",
"normalized": "0306406152",
"valid": true,
"format": "ISBN-10",
"check_digit": "2",
"isbn10": "0306406152",
"isbn13": "9780306406157",
"gtin14": "09780306406157"
}
GET/v1/detect/{code}
Works out which kind of code it is from its shape, runs the matching validators, and returns the best match. detected_as is the type used and candidates lists every type the shape could fit. If none of them validates, the type where the code had the right shape (only the checksum or a content rule failed) is reported, for example FR40303265046 is reported as a French VAT number whose SIREN fails its Luhn check, not as a too-short French IBAN. The free checker on the home page uses the POST form of this endpoint.
curl https://checkdigit/v1/detect/9780306406157
{
"type": "isbn",
"input": "9780306406157",
"normalized": "9780306406157",
"valid": true,
"format": "ISBN-13",
"check_digit": "7",
"isbn13": "9780306406157",
"isbn10": "0306406152",
"gtin14": "09780306406157",
"detected_as": "isbn",
"candidates": [
"isbn",
"gtin"
]
}
{
"type": "vat",
"input": "FR40303265046",
"normalized": "FR40303265046",
"valid": false,
"reason": "SIREN part fails its Luhn checksum",
"country": "FR",
"country_name": "France",
"number": "40303265046",
"detected_as": "vat",
"candidates": [
"iban",
"vat"
]
}
POST/v1/batch
Validate up to 500 codes in one request, for example when cleaning a product feed. Requires a Pro API key. Each code counts as one call against the monthly quota. Items are either strings (auto-detected) or objects with a type of auto, iban, bic, vat, gtin or isbn. Results come back in the same order. The body can be at most 32 KB.
curl -X POST https://checkdigit/v1/batch \
-H "Authorization: Bearer cd_live_..." \
-H "Content-Type: application/json" \
-d '{"codes": ["DE89370400440532013000", {"code": "4006381333931", "type": "gtin"}, "FR40303265046"]}'
{
"count": 3,
"valid_count": 2,
"invalid_count": 1,
"results": [
{
"index": 0,
"type": "iban",
"input": "DE89370400440532013000",
"valid": true,
"...": "(same fields as the single endpoints)"
},
{
"index": 1,
"type": "gtin",
"input": "4006381333931",
"valid": true,
"...": "(same fields as the single endpoints)"
},
{
"index": 2,
"type": "vat",
"input": "FR40303265046",
"valid": false,
"reason": "SIREN part fails its Luhn checksum",
"...": "(same fields as the single endpoints)"
}
]
}
More than 500 codes gives 400 batch_too_large. A body over 32 KB gives 413 payload_too_large.
GET/v1/meta
Service version and build (git commit), supported IBAN and VAT countries, formats and current limits. It doesn't count against your quota.
curl https://checkdigit/v1/meta
API keys and limits
Send a key as Authorization: Bearer cd_live_... or X-Api-Key: cd_live_.... Requests without a key use the anonymous tier. A key that isn't recognised gives 401 invalid_api_key; it never silently falls back to the anonymous tier.
| Tier | Monthly calls | Rate limit | Batch |
|---|---|---|---|
| Anonymous (no key) | no monthly allowance; shared daily capacity | 20 requests/min per IP | no |
| Free key | 1,000 | 300 requests/min per key | no |
| Pro key | 50,000 | 300 requests/min per key | yes, up to 500 codes per request |
Monthly quotas reset at 00:00 UTC on the 1st. Responses to keyed requests carry X-CheckDigit-Plan, X-Quota-Limit and X-Quota-Remaining. A global daily capacity limit protects the service; if it's reached, requests get 429 daily_capacity_reached until 00:00 UTC, and part of the capacity is kept for key holders.
Getting a key: API keys are not self-serve yet and the Pro plan is not on sale. During this preview, keys are issued by the operator on request; the anonymous tier and the POST endpoints need no key.
Errors
Errors use the shape {"error": {"code": "...", "message": "..."}}.
| Status | code | When |
|---|---|---|
| 400 | missing_code, input_too_long, bad_encoding, code_in_url | No code in the path or body, code over 64 characters, broken percent-encoding, or a POST with the code in the URL |
| 400 | invalid_json, invalid_batch, empty_batch, batch_too_large | Body is not JSON, or batch problems (no codes array, empty, more than 500 codes) |
| 401 | invalid_api_key, api_key_required | Unknown key, or batch without a key |
| 403 | paid_plan_required | Batch with a free key |
| 404 / 405 | not_found, method_not_allowed | Unknown path or wrong method |
| 413 | payload_too_large | POST body over 32 KB (32,768 bytes) |
| 429 | rate_limited | Per-minute limit reached (Retry-After: 60), including too many new or unrecognised API keys from one IP |
| 429 | monthly_quota_exceeded | Key's monthly calls used up, or a batch is larger than what's left |
| 429 | daily_capacity_reached | Global daily capacity reached (resets 00:00 UTC) |
| 503 | storage_unavailable | Key lookup temporarily unavailable |
Privacy
Codes are checked in memory and returned only to you. CheckDigit does not store or log them; it keeps request counts only (per key per month, and a global daily total). CheckDigit makes no outbound requests. GET puts the code in the URL, which HTTP clients, proxies, CDN request logs and browsers may record; for IBANs, VAT numbers or anything else sensitive, use the POST form (POST /v1/iban, /v1/vat, /v1/detect and so on, with {"code": "..."}). It's free and needs no API key.
Sources
- IBAN: ISO 13616 and the SWIFT IBAN Registry (country lengths, BBAN structures and example IBANs).
- GTIN/EAN/UPC: GS1 General Specifications check digit calculation (mod 10, weights 3 and 1).
- ISBN: ISO 2108 and the ISBN Users' Manual (International ISBN Agency).
- VAT: the VIES number formats and each member state's published check-digit rule.
- BIC: ISO 9362 structure; country codes from ISO 3166-1.