A customer reaches your SaaS checkout, enters a VAT number, and expects the reverse-charge treatment to appear immediately. Your code has a regex, the input looks plausible, and the invoice is issued. Later, the number turns out to be inactive, or the registry was unavailable when your system made the decision. The failure wasn't the missing pattern. It was treating a VAT registration number format as proof of tax status.
Production validation needs two separate decisions. First, is the value structurally plausible for the stated country? Second, does the relevant authority currently recognize it for the transaction? The European Commission's VAT identification number guidance makes the first point clear: EU countries use different national structures rather than one universal pattern.
The practical reference below treats regex as a gate, not a verdict. It covers country-specific patterns, normalization, VIES behavior, retries, caching, and the edge cases that create billing incidents.
Table of Contents
- The Two Layers of VAT Validation You Need to Know
- Why the EU Has 27 Different VAT Number Formats
- Per-Country VAT Registration Number Format and Canonical Regex
- Normalization Rules Before You Hit the API
- Non-EU Tax ID Formats Covered by TaxID
- VIES Limitations and Common Validation Pitfalls
- Integrating the TaxID API for Live VAT Checks
- What Happens When Reverse Charge Goes Wrong
- Pre-Flight VAT Validation Checklist
The Two Layers of VAT Validation You Need to Know
A VAT field in a checkout usually carries more responsibility than its size suggests. It can influence whether your system applies domestic VAT, records an intra-EU business transaction, or prints a reverse-charge note on an invoice. A single malformed character can therefore create either a rejected customer experience or an incorrect tax decision.
Layer one is structural validation
The first layer runs locally and quickly. It removes presentation characters, identifies the country prefix, and tests the remaining value against that country's expected structure. Austria, for example, uses a country prefix followed by a body containing a letter and digits, while Belgium uses a numeric body. Spain permits more than one mixed letter-and-digit arrangement. These differences rule out a universal regex.
A local check catches:
- Wrong length, such as a body that cannot fit the country's structure.
- Unexpected characters, including letters in a strictly numeric body.
- Wrong prefix, including a country code that doesn't match the selected jurisdiction.
- Obvious junk, such as an empty value or a body made only of zeroes.
This check shouldn't call VIES. Sending clearly malformed input to a remote registry wastes time and produces noisy errors that obscure genuine service failures.
Layer two is live registry validation
The second layer asks whether the normalized identifier is recognized by the relevant live validation service. A structurally valid number can still be inactive, deregistered, or temporarily uncheckable. That distinction is central to reliable billing. Format validation answers, “Could this be a valid number?” Registry validation answers, “Did the registry accept it when checked?”
Practical rule: Never let a successful regex check silently become a tax-status decision.
Your application should keep these outcomes separate in its data model. Store the normalized identifier, country code, structural result, remote result, consultation timestamp, and any service availability status. That gives finance a defensible record instead of a boolean whose meaning changes depending on which part of the pipeline happened to run.
The rest of the implementation follows that separation. The country table supplies the local gate, normalization makes input deterministic, and the VIES and API sections deal with remote uncertainty.
Why the EU Has 27 Different VAT Number Formats
VAT identifiers are national identifiers used in a cross-border framework. Each EU country maintains its own numbering convention, so developers inherit several local schemes rather than one centrally designed sequence. The European Commission describes the general model as a country code followed by digits or characters, while explicitly recognizing that member states don't share one universal pattern.
VIES, the VAT Information Exchange System, made those national identifiers usable in cross-border workflows. It didn't erase the underlying differences. Instead, it provides a common validation route that still depends on the correct jurisdictional prefix and the correct national body.

Prefixes carry jurisdictional meaning
The prefix isn't decorative metadata. It tells the validation system which national registry should receive the request. Greece is the familiar trap. VAT workflows commonly use EL, not GR, even though GR is the country's ordinary two-letter code in many other contexts.
Austria creates a different kind of confusion. Its VIES country prefix is AT, but the first letter in the VAT number body is U. A developer who assumes the prefix and the first body character must align can reject a valid Austrian identifier before the request ever leaves the application.
The national body can contain letters, digits, or both. Spain supports mixed layouts, Cyprus includes a letter-plus-digit structure, and Sweden uses a longer numeric body. A table copied without its prefix rules is incomplete because the same body can be interpreted differently when paired with another country.
Why local rules still matter
Cross-border validation needs both a country selection and a national identifier. That affects checkout forms, supplier onboarding, invoice generation, and import pipelines. It also means the frontend shouldn't treat every VAT field as a free-form numeric input.
Keep the country code as a separate value where possible, then derive the canonical identifier server-side. That prevents users from selecting one country while submitting a prefix belonging to another. It also gives your backend a stable key for caching and audit records.
Per-Country VAT Registration Number Format and Canonical Regex
The table below is a developer reference for the 27 EU member states. These expressions validate a normalized, uppercase value that includes the country prefix. They test structure only. They don't prove that a registration is active, and they don't replace a live registry consultation.
| Country | Structure | Canonical regex | Example |
|---|---|---|---|
| Austria | AT plus U and eight digits | ^ATU\d{8}$ |
ATU12345678 |
| Belgium | BE plus ten digits | ^BE\d{10}$ |
BE0123456789 |
| Bulgaria | BG plus nine or ten digits | ^BG\d{9,10}$ |
BG123456789 |
| Croatia | HR plus eleven digits | ^HR\d{11}$ |
HR12345678901 |
| Cyprus | CY plus eight digits and a letter | ^CY\d{8}[A-Z]$ |
CY12345678A |
| Czech Republic | CZ plus eight, nine, or ten digits | ^CZ\d{8,10}$ |
CZ12345678 |
| Denmark | DK plus eight digits | ^DK\d{8}$ |
DK12345678 |
| Estonia | EE plus nine digits | ^EE\d{9}$ |
EE123456789 |
| Finland | FI plus eight digits | ^FI\d{8}$ |
FI12345678 |
| France | FR plus two alphanumeric characters and nine digits | ^FR[A-Z0-9]{2}\d{9}$ |
FRAB123456789 |
| Germany | DE plus nine digits | ^DE\d{9}$ |
DE123456789 |
| Greece | EL plus nine digits | ^EL\d{9}$ |
EL123456789 |
| Hungary | HU plus eight digits | ^HU\d{8}$ |
HU12345678 |
| Ireland | IE plus seven digits and one or two letters | ^IE\d{7}[A-Z]{1,2}$ |
IE1234567A |
| Italy | IT plus eleven digits | ^IT\d{11}$ |
IT12345678901 |
| Latvia | LV plus eleven digits | ^LV\d{11}$ |
LV12345678901 |
| Lithuania | LT plus nine or twelve digits | ^LT\d{9}(\d{3})?$ |
LT123456789 |
| Luxembourg | LU plus eight digits | ^LU\d{8}$ |
LU12345678 |
| Malta | MT plus eight digits | ^MT\d{8}$ |
MT12345678 |
| Netherlands | NL plus nine digits, B, and two digits | ^NL\d{9}B\d{2}$ |
NL123456789B01 |
| Poland | PL plus ten digits | ^PL\d{10}$ |
PL1234567890 |
| Portugal | PT plus nine digits | ^PT\d{9}$ |
PT123456789 |
| Romania | RO plus two to ten digits | ^RO\d{2,10}$ |
RO12345678 |
| Slovakia | SK plus ten digits | ^SK\d{10}$ |
SK1234567890 |
| Slovenia | SI plus eight digits | ^SI\d{8}$ |
SI12345678 |
| Spain | ES plus eight digits and a letter, or seven digits and two letters | ^ES(?:\d{8}[A-Z]|\d{7}[A-Z]{2})$ |
ES12345678A |
| Sweden | SE plus twelve digits | ^SE\d{12}$ |
SE123456789123 |
For a second implementation-oriented reference, compare the examples in VAT number examples by country. The important engineering choice is to anchor every expression with ^ and $, so a valid-looking substring inside a longer string can't pass accidentally.
The patterns most likely to break naive validators
Spain needs alternation because its body can end with a letter or use two letters after seven digits. Don't remove letters during cleanup. They carry meaning in the identifier.
Greece should be canonicalized to EL before matching or sending to VIES. Accepting GR in a user-facing field can be reasonable, but your internal representation should be unambiguous.
Netherlands has a fixed B in the body. Treating every Dutch value as a numeric suffix is a common mistake.
Ireland has a variable letter ending in the pattern shown above. Avoid a numeric-only rule copied from Germany or Italy.
These regexes deliberately stay structural. Some jurisdictions have additional semantic or checksum behavior, so a local pass should never be presented to the customer as authoritative tax confirmation.
Normalization Rules Before You Hit the API
Raw input is designed for humans, not parsers. Customers paste values from invoices, type spaces for readability, or include punctuation from a national presentation style. Normalize once at the server boundary, then use that canonical value for regex checks, API requests, cache keys, invoices, and logs.

A deterministic pipeline
Trim outer whitespace.
" de 123456789 "becomesDE123456789after trimming, uppercasing, and internal cleanup.Remove presentation separators.
ConvertDE 123 456 789,DE-123-456-789, andDE.123.456.789to the same canonical value,DE123456789. Don't remove letters. A Spanish value such asES12345678Amust retain its final letter.Uppercase the value.
nl123456789b01becomesNL123456789B01. This makes matching and cache keys stable.Resolve the country prefix explicitly. If the form collects country separately and the user enters only the body, prepend the selected prefix. If the user supplies a prefix, compare it with the selected country and reject conflicts rather than changing jurisdiction without notification.
Canonicalize Greece.
Convert an accepted GreekGRprefix toELbefore the regex and remote request. Don't sendGRto a VIES workflow that expects the VAT convention used by Greece.
Reject before making a remote request
Empty strings, unsupported prefixes, impossible lengths, and bodies made entirely of zeroes should fail locally. This isn't a substitute for VIES. It protects the remote service from input that your own application already knows is unusable.
Avoid an over-aggressive sanitizer that deletes every non-digit character. That rule passes some numeric countries while corrupting Spain, France, Ireland, Cyprus, Austria, and the Netherlands. Use a two-stage approach instead: remove permitted presentation punctuation, then apply the country-specific character rule.
Store both the original display value and the canonical value. The original helps customer support explain what was entered. The canonical form gives your validator a stable input and prevents duplicate remote consultations caused only by formatting differences.
Non-EU Tax ID Formats Covered by TaxID
Not every tax identifier belongs in VIES. TaxID supports the United Kingdom, Switzerland, Norway, and Australia alongside the EU member states, but those values require country-specific handling and the appropriate national validation route.
| Country | Structural rule | Canonical regex | Validation route |
|---|---|---|---|
| United Kingdom | GB plus nine digits, with a possible branch-trader suffix in supported formats | ^GB\d{9}(?:\d{3})?$ |
UK tax authority route, not VIES |
| Switzerland | CHE plus eight digits and a TVA, IVA, or MST suffix | ^CHE\d{8}(?:TVA|IVA|MST)$ |
Swiss registry or tax authority route |
| Norway | NO plus nine digits and MVA | ^NO\d{9}MVA$ |
Norwegian tax authority route |
| Australia | ABN plus eleven digits, with an optional branch suffix in supported workflows | ^AU\d{11}(?:\d{3})?$ |
Australian Business Register route |
These expressions are useful gates, but they shouldn't be mistaken for full national validation. Australia, for example, uses a weighted checksum in the ABN structure. A regex can confirm the character layout while still accepting a mathematically invalid identifier unless your implementation also performs the checksum.
The United Kingdom deserves particular care because post-EU workflows may contain a GB-prefixed identifier and branch information. Don't route every GB value to VIES. The same country prefix can appear in business contexts where the validation authority, tax treatment, and response semantics differ from an EU member state's VIES response.
Boundary rule: Choose the validation authority from the country and identifier type, not from the fact that the value resembles an EU VAT number.
For teams handling Australian sales, tax registration and identifier validation are only part of the operating model. Guidance on structuring overseas revenue for Australian firms can help finance and engineering agree on which entity, branch, and reporting context the identifier belongs to before automation begins.
VIES Limitations and Common Validation Pitfalls
A clean regex is not a valid VAT number. It only tells you that the string fits a known national shape. The live answer depends on the member state's registry and the availability of the VIES infrastructure that connects cross-border traders to those registries.
VIES is SOAP-based and can respond slowly or return MS_UNAVAILABLE when a member state's service isn't available. A checkout that waits synchronously for every remote response turns a tax dependency into a user-interface dependency.

Design for uncertain responses
The process works like this:
- Format failure: return a field-level error without making a remote call.
- Confirmed invalid: don't apply the B2B exemption, and show a correction path.
- Confirmed valid: store the consultation evidence with the invoice decision.
- Unavailable or timed out: distinguish service failure from invalidity, then apply a documented fallback policy.
The fallback policy depends on your tax advice and transaction risk. What you shouldn't do is convert an outage into an “invalid” response. Customers can't fix a registry maintenance window by retyping the same number.
The opposite mistake is equally damaging. Caching a successful result forever ignores deregistration and status changes. Revalidating before every invoice creates avoidable dependency pressure and can exhaust service allowances. A practical system caches the consultation result, records when it was obtained, retries transient failures with backoff, and periodically rechecks active customer records.
For implementation details around the remote workflow, see how VIES VAT number validation works. Your logs should include a correlation identifier, country, normalized value or a protected hash, result state, response time, and consultation timestamp. Keep sensitive data access controlled, especially when support staff can inspect billing records.
Integrating the TaxID API for Live VAT Checks
A developer-first integration should keep the application contract simpler than the underlying registry. TaxID exposes a REST endpoint for VAT validation, so the calling service can submit a country and VAT number without building its own SOAP client, parser, retry layer, and cache policy. The TaxID VAT API documentation is the implementation reference for the exact request and authentication details.
A representative request shape is:
{
"countryCode": "DE",
"vatNumber": "123456789"
}
A successful response can expose fields such as:
{
"valid": true,
"companyName": "Registered company name",
"address": "Registered address",
"countryCode": "DE",
"validatedAt": "2026-08-17T12:00:00Z"
}
Use the response as a typed domain object, not as an unparsed blob. Persist valid, the returned identity details, the country, and validatedAt alongside the invoice or checkout decision.
Map errors to user and system behavior
Stripe-style error codes make the caller's behavior explicit:
vat_format_errormeans local or service-side structure checks failed. Ask the customer to correct the value.vat_invalidmeans the remote result says the identifier isn't valid. Don't treat it as a transient network failure.service_unavailablemeans the dependency couldn't provide a dependable answer. Retry with backoff or use your approved fallback.rate_limitedmeans the caller must slow down and respect the service's retry guidance.
TaxID uses Redis-backed caching for repeated lookups, with a 24-hour cache window and sub-10ms cached responses, as described in the publisher brief. Those quantitative product claims should be checked against the current service documentation before you use them in an SLA or customer promise. Cache by canonical country plus normalized number, not by the raw text the customer typed.
A checkout can use a cache hit immediately while a cache miss runs through a controlled backend request. For high-value B2B invoices, bypassing or refreshing a cached result may be appropriate under your finance policy. Teams building multilingual billing support can also pair clear validation states with resources on AI for global customer support, so users receive actionable explanations instead of opaque registry errors.
TaxID offers a free tier with 100 validations per month and no credit card, according to the publisher brief. Confirm current limits and paid-volume behavior before selecting a plan for a marketplace or embedded billing platform.
What Happens When Reverse Charge Goes Wrong
Consider a fictional SaaS company that collects a customer VAT number during checkout. The team skips local format validation, calls VIES only when convenient, and treats an unavailable response as permission to continue. During an outage, a deregistered identifier remains in a cached customer profile, so the system applies reverse charge and issues an invoice without VAT.
The customer receives an invoice that looks correct to the billing system. An audit later asks for evidence that the buyer held a valid registration when the transaction was invoiced. The team has neither a reliable consultation result nor a clear timestamp. The result is a tax exposure that the original checkout flow concealed.
Three controls would have changed the outcome:
- A country-specific format gate would have rejected malformed input before it reached the remote service.
- A separate unavailable state would have prevented the outage from being interpreted as confirmation.
- Recorded consultation evidence would have shown what the system knew at invoice time.
The point isn't that every failed lookup should block sales. The point is that your application needs an explicit policy for uncertainty. Finance should decide when reverse charge is allowed, engineering should encode that decision, and the invoice record should preserve the evidence used.
Pre-Flight VAT Validation Checklist
Use this list in a pull request template or billing runbook:
- Select the country first: Validate against the correct national pattern.
- Normalize input: Trim whitespace, remove presentation punctuation, and uppercase.
- Handle prefixes: Canonicalize Greece to
ELand reject country conflicts. - Run local validation: Block impossible structure before a remote request.
- Check live status: Distinguish valid, invalid, unavailable, and rate-limited outcomes.
- Record evidence: Save the normalized value, response, and consultation time.
- Revalidate deliberately: Refresh active customer records under a documented policy.

TaxID provides a single REST-based way to submit tax identifiers and receive structured validation results, company details, address data, country information, and validation time for supported countries. If you're replacing ad hoc regexes and fragile VIES handling in a checkout, invoicing, or supplier workflow, visit TaxID and review the API documentation before shipping your validation layer.