How do I validate an ISIN before calling an API?
An ISIN is 12 characters: a two-letter country code, nine alphanumerics and a Luhn check digit computed over the letters expanded to digits. Validation code in Python, TypeScript and SQL, and why it saves requests.Updated 12 September 2026 · by FundFacts APIShort answer
Check three things: the length is 12; the first two characters are letters and the next nine are letters or digits; and the last character equals the Luhn check digit computed after expanding every letter to its number (A=10 … Z=35). A syntactically valid ISIN costs a request if it is not a fund (404 fund_not_found), while an invalid one is rejected free (400 invalid_isin), so validating locally mainly saves round trips. The ISIN validator page on this site runs the same check in the browser.
The algorithm
- Uppercase; require
^[A-Z]{2}[A-Z0-9]{9}[0-9]$. - Replace each letter with its number: A=10, B=11, …, Z=35, producing a digit string.
- Apply Luhn to that string: from the right, double every second digit, subtract 9 from doubles above 9, sum everything; the total must be divisible by 10.
Python
pythonimport redef is_valid_isin(s: str) -> bool:s = s.strip().upper()if not re.fullmatch(r"[A-Z]{2}[A-Z0-9]{9}[0-9]", s):return Falsedigits = "".join(str(ord(c) - 55) if c.isalpha() else c for c in s)total, double = 0, Falsefor ch in reversed(digits):n = int(ch)if double:n = n * 2if n > 9:n -= 9total += ndouble = not doublereturn total % 10 == 0assert is_valid_isin("IE00B4L5Y983") and not is_valid_isin("IE00B4L5Y984")
TypeScript
typescriptexport function isValidIsin(input: string): boolean {const s = input.trim().toUpperCase();if (!/^[A-Z]{2}[A-Z0-9]{9}[0-9]$/.test(s)) return false;const digits = [...s].map((c) => (c >= "A" ? String(c.charCodeAt(0) - 55) : c)).join("");let sum = 0, dbl = false;for (let i = digits.length - 1; i >= 0; i--) {let n = Number(digits[i]);if (dbl) { n *= 2; if (n > 9) n -= 9; }sum += n; dbl = !dbl;}return sum % 10 === 0;}
SQL (PostgreSQL)
sql-- format only; use the application for the check digitselect isin from positions where isin !~ '^[A-Z]{2}[A-Z0-9]{9}[0-9]$';
What validation does not tell you
A valid ISIN can be a stock, a bond or an index. Only the API (or a search by name) tells you whether it is a covered fund; a 404 fund_not_found on a valid ISIN is the answer "not a fund", and it is counted. The ISIN validator also computes a missing check digit, and what is an ISIN covers the structure and the national identifiers inside it.
Verify it yourself
The demo endpoint returns the live payload for a fund that is already in the store, without a key. Everything on this page can be checked against it.bashcurl -s https://fundfactsapi.com/api/v1/demo/funds/IE00B4L5Y983 | jq '{name, asOf: .data.dataAsOf, ter: .data.headlineMetrics.ter, risk: .data.riskRating, top: .data.topHoldings[:3]}'