DEV Community

Matías Maquieira
Matías Maquieira

Posted on Originally published at prometiam.com

Get a Company's EU VAT Number from Its Company Number: the Rules for 10 Countries (with Code)

Get a Company's EU VAT Number from Its Company Number: the Rules for 10 Countries (with Code)

Your onboarding form asks for a company number, your invoicing system needs a VAT number, and the customer typed only one of them. In many European countries the VAT number is not a separate secret: it is the company's own identifier in a fixed format. In others there is no rule at all. Here are the rules, a function you can paste, and the one thing a derived number cannot tell you.

The rules

Country Built from VAT number Example
Spain NIF (company letters A–W) ES + NIF ESA46103834
France SIREN (9 digits) FR + 2-digit key + SIREN FR27552032534
Poland NIP (10 digits) PL + NIP PL7740001454
Norway organisasjonsnummer (9 digits) NO + number + MVA NO923609016MVA
Finland Y-tunnus 1234567-8 FI + the 8 digits FI01120389
Sweden organisationsnummer (10 digits) SE + number + 01 SE556810898801
Belgium enterprise number (10 digits) BE + number BE0202239951
Croatia OIB (11 digits) HR + OIB HR18928523252
Denmark CVR (8 digits) DK + CVR DK24256790
Switzerland UID CHE-123.456.789 UID + MWST (TVA/IVA) CHE103867266MWST

No rule exists for the UK (a VAT number is its own registration with HMRC), Ireland, Slovakia (the IČ DPH is not derived from the IČO) or Estonia (the KMKR is not the registry code). There you need the number from the company or from a register that publishes it.

France: the key

The only rule with arithmetic. The two-digit key is (12 + 3 × (SIREN mod 97)) mod 97:

def fr_vat(siren: str) -> str:
    key = (12 + 3 * (int(siren) % 97)) % 97
    return f"FR{key:02d}{siren}"

assert fr_vat("552032534") == "FR27552032534"   # Danone
Enter fullscreen mode Exit fullscreen mode

All ten as one function

import re

def vat_from_company_number(country: str, number: str) -> str | None:
    """The VAT number a company's identifier implies, or None where no rule exists."""
    s = re.sub(r"[\s.\-/]", "", number or "").upper()
    d = re.sub(r"\D", "", s)
    if country == "ES":
        return f"ES{s}" if re.fullmatch(r"[ABCDEFGHJNPQRSUVW]\d{7}[0-9A-J]", s) else None
    if country == "FR" and len(d) == 9:
        return f"FR{(12 + 3 * (int(d) % 97)) % 97:02d}{d}"
    rules = {"PL": (10, "PL", ""), "NO": (9, "NO", "MVA"), "FI": (8, "FI", ""), "SE": (10, "SE", "01"),
             "BE": (10, "BE", ""), "HR": (11, "HR", ""), "DK": (8, "DK", ""), "CH": (9, "CHE", "MWST")}
    if country in rules and len(d) == rules[country][0]:
        n, prefix, suffix = rules[country]
        return f"{prefix}{d}{suffix}"
    return None
Enter fullscreen mode Exit fullscreen mode

Note the Spanish check: a NIF that starts with X, Y or Z is a foreigner's personal NIE, never a company VAT number.

The one thing a derived number cannot tell you

The format is right; whether the company is actually registered for VAT is a separate fact. Plenty of small companies, holding companies and non-profits are not. Before you put a derived number on an invoice or zero-rate a cross-border sale, check it against VIES, the European Commission's validation service:

curl "https://api.prometiam.com/functions/v1/risk-api/vat/FR27552032534" \
  -H "Authorization: Bearer $PROMETIAM_API_KEY"
Enter fullscreen mode Exit fullscreen mode

Two registers answer the registration question themselves. Norway's register flags whether a company is in the VAT register, and Finland's register says so for many companies. In both, a company the register marks as not VAT-registered has no VAT number at all, so building one would be wrong.

Or let the API do it

Every company object the Prometiam Risk API returns now carries vat_number and vat_number_source: register when the register publishes the number (Estonia) or confirms the company is VAT-registered (Norway, Finland, Belgium), rule when it was built with the rules above, and null where the register says the company is not VAT-registered or no rule exists (UK, Ireland, Slovakia). Search, lookup and VIES checks run on the free 14-day trial (1,000 requests; sign up). For a one-off check without code there is a free VAT number checker.

Top comments (0)