Workflows

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 API

Short 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

  1. Uppercase; require ^[A-Z]{2}[A-Z0-9]{9}[0-9]$.
  2. Replace each letter with its number: A=10, B=11, …, Z=35, producing a digit string.
  3. 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

python
import re
def 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 False
digits = "".join(str(ord(c) - 55) if c.isalpha() else c for c in s)
total, double = 0, False
for ch in reversed(digits):
n = int(ch)
if double:
n = n * 2
if n > 9:
n -= 9
total += n
double = not double
return total % 10 == 0
assert is_valid_isin("IE00B4L5Y983") and not is_valid_isin("IE00B4L5Y984")

TypeScript

typescript
export 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 digit
select 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.
bash
curl -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]}'

Terms used on this page

Frequently asked questions

Which country codes are funds?

Mostly IE, LU, FR, DE, GB, CH, AT, NL, BE and the Nordics for UCITS; XS for some structured products; US for US-listed funds and, mostly, stocks. The code says where the security was issued, not what it is.

Try it on your own ISINs

One request returns key facts, holdings, risk and performance as JSON. Free plan, no card.