Guide · Python · SDK · REST API

EORI Number Validation in Python — EU Customs Identifier

EORI numbers identify businesses trading across EU borders. Every import declaration, export license, and customs procedure requires one. Here's how to validate them properly in Python — including live checks against the European Commission registry.

1. What is an EORI number?

An EORI (Economic Operators Registration and Identification) number is a unique identifier assigned to businesses and individuals who import or export goods in the European Union. Introduced in 2009 under EU Regulation 312/2009, it replaced the older national trader reference numbers with a single, EU-wide system.

Every customs declaration filed within the EU must include a valid EORI number. Without one, goods cannot clear customs — shipments are held, and delays cost money. The number is issued by the customs authority of the EU member state where the business is established and is valid across all 27 member states.

Since Brexit, the UK issues its own EORI numbers with the GB prefix. Businesses trading with both the EU and the UK may need two separate EORI numbers — one for each customs territory.


2. EORI structure by country

An EORI number always starts with a two-letter ISO 3166-1 alpha-2 country code, followed by a country-specific identifier. For most EU member states, the identifier is the national tax or VAT number. The total length and format vary by country.

PL123456789000000
PL = country code123456789000000 = national identifier
CountryPrefixIdentifier formatExample
GermanyDEUp to 15 digitsDE123456789012345
FranceFRUp to 15 alphanumericFR12345678901234
ItalyITUp to 15 digitsIT12345678901
PolandPLUp to 15 digits (NIP-based)PL123456789000000
NetherlandsNLUp to 15 alphanumericNL123456789
SpainESUp to 15 alphanumericESA12345678
United KingdomGB12 digits or GB+VATGB123456789000
ℹ️The identifier part often matches the national VAT number, but this is not always the case. Some countries append extra digits or use a separate numbering scheme for customs purposes. Never assume EORI = country code + VAT number.

3. Why EORI validation matters

Customs compliance

EU customs authorities require a valid EORI on every import and export declaration. An invalid EORI means your declaration is rejected at the border, causing delays, storage fees, and potential fines.

Supply chain automation

Freight forwarders, logistics platforms, and ERP systems process thousands of declarations daily. Validating EORI numbers at the point of data entry prevents errors from propagating through the supply chain and causing downstream rejections.

Partner onboarding and KYC

When onboarding new suppliers or customers who trade internationally, verifying their EORI number confirms they are registered with customs authorities — an important part of know-your-customer due diligence for cross-border trade.

EC registry verification

Beyond format validation, the European Commission maintains a live registry of active EORI numbers. Checking against this registry confirms the number is not just well-formed but actually assigned and active — critical for high-value shipments and regulatory compliance.


4. The naive approach

A quick regex seems like it should work. After all, EORI numbers are just a country code followed by digits, right?

import re

def validate_eori_naive(eori: str) -> bool:
    """Naive EORI validation — DO NOT use in production."""
    pattern = r'^[A-Z]{2}[0-9A-Z]{1,15}$'
    return bool(re.match(pattern, eori.strip().upper()))

# Seems to work...
print(validate_eori_naive("PL123456789000000"))  # True
print(validate_eori_naive("DE123456789012345"))  # True

# But it also accepts complete nonsense:
print(validate_eori_naive("XX999999999999999"))  # True  (XX is not a country)
print(validate_eori_naive("PL1"))                # True  (too short for Poland)
print(validate_eori_naive("GB12345"))            # True  (invalid GB format)

This approach fails for several reasons:

No country-specific format rules

Each EU member state has its own rules for the identifier part. German identifiers are purely numeric, while Spanish ones start with a letter. A single regex cannot capture 27+ different format rules without becoming unmaintainable.

No live registry check

Format validation alone tells you the number looks correct. It does not tell you if it has actually been issued. A structurally valid EORI that was never registered — or has been revoked — will still pass regex validation.

Maintenance burden

EU regulations evolve. New member states join, format rules change, and the identifier length constraints are updated. Hardcoding validation logic means constantly tracking regulatory changes across 27 countries.


5. The right solution

The IsValid EORI API handles format validation and optional live checks against the European Commission registry in a single GET request. Pass the EORI number and optionally set check=true to query the EC database for real-time status, registered name, and address.

27+
Countries
EU + UK + more
Live
EC registry
optional real-time check
100/day
Free tier
no credit card

Get your free API key at isvalid.dev. The free tier includes 100 calls per day — enough for most development and low-volume production use.

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


6. Python code example

Using the isvalid-sdk Python SDK or the requests library. Install with pip install isvalid-sdk or pip install requests.

# eori_validator.py
import os
from isvalid_sdk import IsValidConfig, create_client

iv = create_client(IsValidConfig(api_key=os.environ["ISVALID_API_KEY"]))

# ── Basic format validation ─────────────────────────────────────────────────

result = iv.eori("PL123456789000000")

if not result["valid"]:
    print("Invalid EORI number")
else:
    print(f"Valid EORI — {result['country']} ({result['countryCode']})")
    print(f"Identifier: {result['identifier']}")
    print(f"Formatted: {result['formatted']}")
    # → Valid EORI — Poland (PL)
    # → Identifier: 123456789000000
    # → Formatted: PL123456789000000

# ── With EC registry check ──────────────────────────────────────────────────

result = iv.eori("PL123456789000000", check=True)

if result.get("ec", {}).get("valid"):
    print(f"EC registry: {result['ec']['statusDescr']}")
    print(f"Name: {result['ec']['name']}")
    # → EC registry: Valid
    # → Name: EXAMPLE SP. Z O.O.

In a customs declaration handler, you might use it like this with Flask:

# app.py (Flask)
import os
import requests
from flask import Flask, request, jsonify

app = Flask(__name__)

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


def validate_eori(eori: str, check: bool = False) -> dict:
    params = {"value": eori}
    if check:
        params["check"] = "true"
    resp = requests.get(
        f"{BASE_URL}/v0/eori",
        params=params,
        headers={"Authorization": f"Bearer {API_KEY}"},
    )
    resp.raise_for_status()
    return resp.json()


@app.post("/customs-declaration")
def customs_declaration():
    data = request.get_json()

    # Validate EORI with live EC check for customs compliance
    try:
        eori_check = validate_eori(data["eori"], check=True)
    except requests.RequestException:
        return jsonify(error="EORI validation service unavailable"), 502

    if not eori_check["valid"]:
        return jsonify(error="Invalid EORI number format"), 400

    ec = eori_check.get("ec", {})
    if ec.get("checked") and not ec.get("valid"):
        return jsonify(
            error="EORI not found in EC registry",
            ecStatus=ec.get("statusDescr"),
        ), 400

    # EORI is valid and registered — proceed with declaration
    declaration = create_declaration(
        eori=eori_check["formatted"],
        country=eori_check["countryCode"],
        trader_name=ec.get("name", data.get("companyName")),
        goods=data["goods"],
    )

    return jsonify(
        success=True,
        declarationId=declaration["id"],
        trader=ec.get("name"),
    )
Use check=True for onboarding and high-value shipments where you need to confirm the EORI is actually registered. For high-throughput form validation where you just need format checks, omit the check parameter to get faster responses without hitting the EC registry.

7. cURL example

Basic format validation of a Polish EORI:

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

With EC registry check:

curl -H "Authorization: Bearer YOUR_API_KEY" \
  "https://api.isvalid.dev/v0/eori?value=PL123456789000000&check=true"

Validate a German EORI:

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

8. Understanding the response

Basic validation response (without EC check):

{
  "valid": true,
  "countryCode": "PL",
  "country": "Poland",
  "identifier": "123456789000000",
  "formatted": "PL123456789000000"
}

Response with EC registry check (check=true):

{
  "valid": true,
  "countryCode": "PL",
  "country": "Poland",
  "identifier": "123456789000000",
  "formatted": "PL123456789000000",
  "ec": {
    "checked": true,
    "valid": true,
    "statusDescr": "Valid",
    "name": "EXAMPLE SP. Z O.O.",
    "street": "UL. PRZYKLADOWA 1",
    "postalCode": "00-001",
    "city": "WARSZAWA",
    "country": "PL"
  }
}

Invalid EORI:

{
  "valid": false
}
FieldTypeDescription
validbooleanWhether the EORI number has a valid format
countryCodestring2-letter ISO 3166-1 country code from the EORI prefix
countrystringFull country name
identifierstringThe national identifier portion (after the country code)
formattedstringNormalised EORI number (uppercase, no spaces)
ec.checkedbooleanWhether the EC registry was queried
ec.validbooleanWhether the EORI is registered and active in the EC database
ec.statusDescrstringHuman-readable status from the EC registry
ec.namestringRegistered business name from the EC database
ec.streetstringRegistered street address
ec.postalCodestringRegistered postal code
ec.citystringRegistered city
⚠️The ec object is only present when you pass check=true. Without it, you only get format validation. For customs compliance, always use the EC check to confirm the EORI is actually registered and active.

9. Edge cases to handle

UK EORI numbers post-Brexit

Since January 2021, UK businesses trading with the EU need both a GB EORI (for UK customs) and an EU EORI (issued by the EU member state of first import). GB EORI numbers are not in the EC registry — they must be validated through the UK's own system.

result = validate_eori("GB123456789000", check=True)

# For GB numbers, ec.checked may be False — the EC registry
# does not cover UK-issued EORI numbers post-Brexit.
if result["countryCode"] == "GB":
    # Format is valid, but EC check is not applicable
    print("GB EORI — format valid, EC registry not available")

EORI vs. VAT number confusion

Many businesses assume their VAT number is their EORI number. In some countries (e.g. Germany), the EORI uses a different identifier than the VAT number. When collecting EORI numbers in your forms, clearly label the field as "EORI number" and provide an example format for the user's country. Consider adding a link to the EC EORI lookup tool so users can verify their own number if unsure.

Input normalisation

Users may enter EORI numbers with spaces, dashes, or mixed case. The API normalises input automatically — it strips whitespace, removes separators, and uppercases the value. Pass the raw user input directly without pre-processing. The formatted field in the response gives you the canonical form.

EC registry downtime

The EC EORI registry is an external service that occasionally experiences downtime. When using check=true, handle the case where the EC check is unavailable gracefully — fall back to format validation and retry the EC check later.

result = validate_eori(eori, check=True)

if result["valid"]:
    ec = result.get("ec", {})
    if ec.get("checked"):
        # Full validation — EC registry responded
        handle_verified_eori(result)
    else:
        # Format valid, but EC registry unavailable
        # Accept provisionally and verify later
        handle_provisional_eori(result)

Northern Ireland protocol

Businesses in Northern Ireland may have EORI numbers starting with XI for EU customs purposes, in addition to their GB EORI. The XI prefix is valid and can be checked against the EC registry, unlike GB numbers.


10. Summary

Do not validate EORI numbers with a single regex — formats differ per country
Do not assume EORI = country code + VAT number
Do not skip the EC registry check for customs-critical workflows
Use the check parameter for live EC registry verification
Handle GB EORI numbers separately — they are not in the EC registry
Store the formatted version and the EC response for audit trails

See also

Validate EORI numbers instantly

Free tier includes 100 API calls per day. No credit card required. Format validation plus optional live checks against the European Commission EORI registry.