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
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
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"
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)