ISSN Validation in Python — Format, Check Digit & MOD-11
Every journal, magazine, and ongoing serial publication is assigned an ISSN — a compact eight-digit identifier used by libraries, publishers, and aggregators worldwide. Here's how the ISSN structure works, how the MOD-11 check digit is calculated, and how to validate any ISSN reliably in your Python application.
In this guide
1. What is an ISSN?
ISSN stands for International Standard Serial Number. It is an eight-digit identifier assigned to serial publications — journals, magazines, newspapers, annual reports, monographic series, and any publication issued in successive parts.
The ISSN system is managed by the ISSN International Centre in Paris and coordinated through a network of national centres. Unlike an ISBN, which identifies a specific book edition, an ISSN identifies the title of a serial as a whole, regardless of how many issues have been published.
ISSNs are essential for interlibrary loan systems, union catalogues, citation databases (such as Web of Science and Scopus), and digital preservation workflows. They also appear in EAN-13 barcodes on printed periodicals, using the GS1 prefix 977.
2. ISSN structure
An ISSN consists of eight digits displayed in two groups of four, separated by a hyphen. The last digit is a check digit that may be a numeral (0-9) or the letter X (representing 10).
ISSN format
| Component | Length | Example | Notes |
|---|---|---|---|
| First group | 4 digits | 0378 | No semantic meaning (not a country or publisher code) |
| Second group | 3 digits + check | 5955 | Last character is the MOD-11 check digit (0-9 or X) |
3. The MOD-11 check digit algorithm
The ISSN check digit uses the same MOD-11 weighted algorithm as ISBN-10. The first seven digits are multiplied by descending weights (8 down to 2), summed, and the check digit is chosen so the total is divisible by 11.
Let's walk through 0378-5955:
Step 1 — Multiply each of the first 7 digits by its weight (8 down to 2)
| Digit | 0 | 3 | 7 | 8 | 5 | 9 | 5 | 5 |
| Weight | 8 | 7 | 6 | 5 | 4 | 3 | 2 | — |
| Product | 0 | 21 | 42 | 40 | 20 | 27 | 10 | ? |
Step 2 — Sum the products
0 + 21 + 42 + 40 + 20 + 27 + 10 = 160
Step 3 — Check digit = (11 - sum mod 11) mod 11
(11 - (160 % 11)) % 11 = (11 - 6) % 11 = 5
Last digit is 5 — valid ISSN
0317-847X is a valid ISSN. This is the only non-digit character allowed in an ISSN.# issn_checksum.py — validate an ISSN check digit import re def validate_issn(raw: str) -> bool: digits = re.sub(r"[-\s]", "", raw) if not re.match(r"^\d{7}[\dXx]$", digits): return False total = 0 for i in range(7): total += int(digits[i]) * (8 - i) last = digits[7].upper() check = 10 if last == "X" else int(last) total += check return total % 11 == 0 print(validate_issn("0378-5955")) # True valid ISSN print(validate_issn("0317-847X")) # True X check digit print(validate_issn("0378-5950")) # False bad check digit
4. Why regex isn't enough
A regex like \d{4}-\d{3}[\dXx] can verify that a string looks like an ISSN, but it cannot catch the most common errors:
Transposed digits
Swapping two adjacent digits (e.g. typing 0387 instead of 0378) still matches the format regex but produces an invalid checksum. The MOD-11 algorithm is specifically designed to catch single-digit transpositions — but only if you actually compute it.
Fabricated numbers
Any eight-digit string in the right format will pass a regex check. Without checksum validation, you cannot distinguish a real ISSN from a random sequence of digits that happens to match the pattern.
X check digit handling
The check digit X (representing 10) is only valid in the last position when the MOD-11 calculation actually produces 10. A regex alone cannot determine whether X is the correct check digit for a given set of leading digits.
Input normalisation
ISSNs may appear with or without the hyphen, with spaces, or with a lowercase x. A production validator needs to normalise the input before computing the checksum and return the canonical XXXX-XXXX format consistently.
5. The right solution
The IsValid ISSN API handles format checking, MOD-11 checksum validation, and input normalisation in a single call. Pass any string and get back a definitive answer plus the canonical hyphenated form.
Full parameter reference and response schema: ISSN Validation API docs →
6. Python code example
from isvalid import create_client iv = create_client(api_key=os.environ["ISVALID_API_KEY"]) # ── Example usage ──────────────────────────────────────────────────────────── result = iv.issn("0378-5955") if not result["valid"]: print("Invalid ISSN") else: print("Valid:", result["valid"]) # → True print("ISSN:", result["issn"]) # → '0378-5955'
In a journal metadata pipeline:
# Validate ISSNs before importing journal records def import_journals(rows: list[dict]) -> list[dict]: results = [] for row in rows: if not row.get("issn"): results.append({**row, "issn_status": "missing"}) continue check = iv.issn(row["issn"]) if not check["valid"]: results.append({**row, "issn_status": "invalid"}) continue results.append({ **row, "issn": check["issn"], # canonical XXXX-XXXX format "issn_status": "valid", }) return results
7. cURL example
Validate an ISSN:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/issn?value=0378-5955"
Without hyphen:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/issn?value=03785955"
ISSN with X check digit:
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/issn?value=0317-847X"
Invalid ISSN (bad check digit):
curl -H "Authorization: Bearer YOUR_API_KEY" \ "https://api.isvalid.dev/v0/issn?value=0378-5950"
8. Understanding the response
Valid ISSN:
{ "valid": true, "issn": "0378-5955" }
Valid ISSN with X check digit:
{ "valid": true, "issn": "0317-847X" }
Invalid ISSN:
{ "valid": false }
| Field | Type | Description |
|---|---|---|
| valid | boolean | Whether the ISSN has the correct format and MOD-11 checksum |
| issn | string | Canonical ISSN in XXXX-XXXX format. Present only when valid |
9. Edge cases
(a) ISSN-L — linking ISSNs
A serial publication that exists in multiple media (print and online) receives a separate ISSN for each medium. The ISSN-L (linking ISSN) designates one of these ISSNs as the canonical link across all media versions. An ISSN-L is structurally identical to a regular ISSN — the same eight digits, the same MOD-11 check digit. The API validates ISSN-L values the same way as any other ISSN.
# ISSN-L is validated the same way as a regular ISSN result = iv.issn("1476-4687") # Nature (linking ISSN) print(result["valid"]) # → True print(result["issn"]) # → '1476-4687'
(b) X check digit
When the MOD-11 checksum results in a remainder of 10, the check digit is represented as the letter X. The API accepts both uppercase and lowercase X and always returns uppercase X in the canonical form.
# X check digit — perfectly valid result = iv.issn("0317-847X") print(result["valid"]) # → True print(result["issn"]) # → '0317-847X' # lowercase x is also accepted result2 = iv.issn("0317-847x") print(result2["valid"]) # → True print(result2["issn"]) # → '0317-847X'
(c) Print ISSN vs Electronic ISSN
A journal published in both print and electronic form has two distinct ISSNs — one for each medium. For example, Nature uses 0028-0836 for print and 1476-4687 for online. Both are valid ISSNs with independent check digits. The API validates either one — it does not distinguish between print and electronic ISSNs at the format level.
10. Summary
See also
Validate ISSNs instantly
Free tier includes 100 API calls per day. No credit card required. Full MOD-11 checksum validation with canonical XXXX-XXXX formatting.