Guide · Python · Validation

EAN Barcode Validation in Python — GS1 Checksum and Prefix Lookup

EAN barcodes are the backbone of global retail — every product on every shelf has one. Here's how the checksum works, what the prefix encodes, and how to validate barcodes reliably in your Python application.

1. What is an EAN barcode?

EAN stands for European Article Number, now officially called GTIN (Global Trade Item Number) by GS1 — the standards body that manages product identification globally. Despite the rebrand, "EAN" and "barcode" remain the common terms in practice.

Every EAN encodes a product identifier as a sequence of digits with a trailing check digit. When a barcode scanner reads the stripes, it decodes the digits and validates the checksum — ensuring the scan was not corrupted by a dirty or damaged label.

In e-commerce and inventory systems, EAN codes are used to identify products across suppliers and marketplaces, match catalog entries, and feed product databases.


2. EAN-13 vs EAN-8 — when each is used

5901234123457

EAN-13 (13 digits)

The standard for retail products worldwide. Encodes GS1 prefix (2–3 digits), company code, product code, and check digit.

96385074

EAN-8 (8 digits)

Used for small items where a full 13-digit barcode would not fit — cigarette packs, pencils, chewing gum. Assigned only by GS1 on request.

ℹ️UPC-A (the 12-digit barcode used in the US and Canada) is a subset of EAN-13: prepend a zero to any UPC-A to get a valid EAN-13. UPC-A barcodes scan correctly on any EAN-13 reader.

3. GS1 checksum — alternating weights 1 and 3

The checksum algorithm is simpler than Luhn — instead of doubling, it alternates fixed weights of 1 and 3, then computes the complement to 10.

Let's walk through 5901234123457:

Step 1 — Apply alternating weights (1 and 3) to the first 12 digits

Digit5901234123457
Weight131313131313
Product52703294329415?

Step 2 — Sum the products

5 + 27 + 0 + 3 + 2 + 9 + 4 + 3 + 2 + 9 + 4 + 15 = 83

Step 3 — Check digit = (10 − sum mod 10) mod 10

(10 − (83 % 10)) % 10 = (10 − 3) % 10 = 7

Last digit of barcode is 7 ✓ — valid EAN-13

# ean_checksum.py — validate EAN-8 or EAN-13
import re


def validate_ean(raw: str) -> bool:
    digits = re.sub(r"[\s-]", "", raw)
    if not digits.isdigit():
        return False
    if len(digits) not in (8, 13):
        return False

    # EAN-13: weights 1,3,1,3,... — EAN-8: weights 3,1,3,1,...
    weights = (
        [1, 3] * 6 if len(digits) == 13 else [3, 1] * 4
    )

    total = sum(
        int(d) * w for d, w in zip(digits[:-1], weights)
    )

    check_digit = (10 - (total % 10)) % 10
    return check_digit == int(digits[-1])


print(validate_ean("5901234123457"))  # True  ✓  EAN-13
print(validate_ean("96385074"))       # True  ✓  EAN-8
print(validate_ean("5901234123458"))  # False ✗  bad check digit

4. The GS1 prefix — what it actually means

The first 2–3 digits of an EAN-13 are the GS1 prefix, assigned to a GS1 Member Organisation by country. A common misconception is that it indicates the country of origin of the product — it does not. It indicates which GS1 national organisation issued the company number.

⚠️A product with prefix 590 (Poland) can be manufactured anywhere in the world — it only means the company registered its barcodes through GS1 Poland. Prefix is not country of origin.
PrefixGS1 Member Organisation
00–09United States & Canada
30–37France
40–44Germany
45, 49Japan
50United Kingdom
57Denmark
590Poland
690–695China
73Sweden
76Switzerland
80–83Italy
84Spain
87Netherlands
978–979Bookland (ISBN)

5. Special prefixes — ISBN, ISSN, and coupons

978–979 — Bookland (ISBN)

EAN-13 barcodes on books use prefix 978 or 979, encoding the ISBN-13. The digits after the prefix correspond to the book's ISBN (minus the check digit, which is recalculated for EAN). An ISBN-13 and its EAN-13 barcode are the same number.

# Check if an EAN-13 encodes a book (ISBN)
def is_book_ean(ean13: str) -> bool:
    return ean13.startswith("978") or ean13.startswith("979")

977 — ISSN (Serials / Magazines)

Periodicals (magazines, newspapers, journals) use prefix 977, encoding the ISSN. The 8-digit ISSN is embedded in digits 4–10 of the EAN-13.

20–29 — In-store restricted codes

Prefix 20–29 is reserved for in-store use — price-embedded barcodes on weighed goods (deli, fresh produce), store-specific loyalty items, or internal inventory labels. These codes are not globally unique and should not be used in external product databases.


6. The production-ready solution

The IsValid EAN API validates the format (8 or 13 digits), computes the GS1 checksum, and for EAN-13 returns the GS1 prefix and the associated member organisation country.

EAN-8/13
Formats
both variants supported
<15ms
Response time
pure algorithmic check
100/day
Free tier
no credit card

Full parameter reference and response schema: EAN Validation API docs →


7. Python code example

Using the requests library — the de facto standard for HTTP in Python. Install it with pip install requests.

# ean_validator.py
import os
import requests

API_KEY = os.environ["ISVALID_API_KEY"]
BASE_URL = "https://api.isvalid.dev"


def validate_ean(ean: str) -> dict:
    """Validate an EAN barcode using the IsValid API.

    Args:
        ean: EAN-8 or EAN-13 barcode (spaces and hyphens are stripped).

    Returns:
        Validation result as a dictionary.

    Raises:
        requests.HTTPError: If the API returns a non-2xx status.
    """
    response = requests.get(
        f"{BASE_URL}/v0/ean",
        params={"value": ean},
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    response.raise_for_status()
    return response.json()


# ── Example usage ────────────────────────────────────────────────────────────

result = validate_ean("5901234123457")

if not result["valid"]:
    print("Invalid EAN barcode")
else:
    print(f"Format: {result['format']}")            # → 'EAN-13'
    print(f"Prefix: {result['prefix']}")            # → '590'
    print(f"Issued by: {result['prefixCountry']}")  # → 'Poland'

    # Detect books
    if result["prefix"] in ("978", "979"):
        print("This EAN encodes an ISBN (book)")

In a product import pipeline:

# Validate barcodes before inserting into a product catalog
import re


def import_products(rows: list[dict]) -> list[dict]:
    results = []

    for row in rows:
        if not row.get("ean"):
            results.append({**row, "ean_status": "missing"})
            continue

        check = validate_ean(row["ean"])

        if not check["valid"]:
            results.append({**row, "ean_status": "invalid"})
            continue

        results.append({
            **row,
            "ean": re.sub(r"[\s-]", "", row["ean"]),  # store normalised
            "ean_format": check["format"],
            "ean_prefix": check.get("prefix"),
            "ean_country": check.get("prefixCountry"),
            "ean_status": "valid",
        })

    return results
Always store the normalised barcode (digits only, no spaces) in your database. Use the format field to distinguish EAN-8 from EAN-13 — they can look similar in some fonts and your UI should render them differently.

8. cURL example

Validate an EAN-13:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/ean?value=5901234123457"

Validate an EAN-8:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/ean?value=96385074"

Book barcode (ISBN-encoded EAN-13):

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/ean?value=9780141036144"

9. Understanding the response

Valid EAN-13:

{
  "valid": true,
  "format": "EAN-13",
  "prefix": "590",
  "prefixCountry": "Poland"
}

Valid EAN-8 (no prefix lookup for 8-digit codes):

{
  "valid": true,
  "format": "EAN-8"
}

Invalid barcode:

{
  "valid": false
}
FieldTypeDescription
validbooleanCorrect length (8 or 13), all digits, GS1 checksum passes
formatstringEAN-8 or EAN-13
prefixstring2 or 3-digit GS1 prefix (EAN-13 only)
prefixCountrystringGS1 member organisation associated with this prefix (EAN-13 only)

10. Edge cases

UPC-A → EAN-13 conversion

If a user scans a US product with a UPC-A scanner, they may get 12 digits instead of 13. Convert UPC-A to EAN-13 by prepending a zero and re-validating.

def upc_a_to_ean13(upc: str) -> str:
    if len(upc) == 12:
        return "0" + upc
    return upc  # already 13 digits or EAN-8


ean = upc_a_to_ean13("012345678905")
print(ean)  # → '0012345678905'

Distinguishing EAN-13 from ISBN-13

ISBN-13 and EAN-13 are the same format — ISBN-13 is just an EAN-13 with a 978 or 979 prefix. If your application handles both products and books, check the prefix to route the barcode to the correct lookup system (product catalogue vs book database).

Barcode scanner output

Barcode scanners typically emit the digits followed by Enter or Tab. Strip the trailing newline/tab before sending to the API. Some scanners add a prefix/suffix character — configure them to emit raw digits only for cleanest integration.

Leading zeros

EAN codes can start with zeros — 0012345678905 is a valid EAN-13. Do not parse barcodes as integers — store and process them as strings to preserve leading zeros.


Summary

Do not parse EAN codes as integers — leading zeros will be lost
Do not use GS1 prefix as country of origin — it is the issuing GS1 member organisation
Support both EAN-8 and EAN-13 — they use different weight sequences
Convert UPC-A (12 digits) to EAN-13 by prepending a zero
Use prefix 978/979 to detect ISBN-encoded barcodes
Store normalised (digits only) and format for display separately

See also

Validate EAN barcodes instantly

Free tier includes 100 API calls per day. No credit card required. Supports EAN-8 and EAN-13 with GS1 prefix lookup.