Guide13 min readAlberto García

EU VAT Validation API: A Comprehensive Guide for B2B SaaS

The EU VAT validation problem is not just a compliance checkbox. It is an architectural decision that affects checkout latency, audit risk, and subscription billing. This guide covers all of it.

vatapieusaasguidevies

An EU VAT validation API translates a SOAP-based, unreliable, latency-sensitive EU government system into a clean REST/JSON endpoint your application can call in two lines of code. The underlying system — VIES, the EU's VAT Information Exchange System — has been operational since 1993 and was built for a different era of software. This guide explains how to integrate an EU VAT validation API for B2B SaaS: what the API abstracts, how to structure your integration, how to handle failure modes, and how to scale from a single checkout to thousands of validations per day.

What the API Abstracts for You

VIES is a SOAP/XML API that routes validation requests to 27 individual national tax authority systems. Calling it directly from a modern application requires a SOAP client library, XML parsing, WSDL management, error handling for SOAP faults that are structurally different from HTTP errors, and retry logic for country-level outages that can last hours without notice. The response format, field names, and error codes vary between member states. Response times average 400ms–1.5 seconds for live calls.

A well-designed EU VAT validation API wraps all of this into a REST endpoint with consistent JSON responses, a predictable error taxonomy, Redis caching for repeated lookups, and explicit status codes for every possible outcome. You get the authoritative VIES answer without touching SOAP, and your validation endpoint stays under 100ms for cached results.

The Complete Status Taxonomy

Building a correct integration requires handling all five possible status values, not just the happy path. Each has a different implication for your checkout and billing flow.

statusMeaningYour action
activeVIES confirmed the number is currently registeredApply zero-rate, store audit record
inactiveNumber exists in VIES but the business has deregisteredCharge standard VAT, notify customer
invalidNumber does not exist or failed format checkShow error, request correction or charge VAT
format_invalidNumber failed local format validation before reaching VIESShow specific format error (no VIES call made, no quota used)
service_unavailableVIES or national node is temporarily unreachableCharge standard VAT, queue for re-validation

Warning

The most dangerous mistake is collapsing `service_unavailable` and `invalid` into the same error path. Blocking a legitimate business customer because Italy's VIES node was down for an hour creates commercial damage. Not distinguishing between them on the zero-rate decision creates a compliance gap. Handle them separately.

Integration Patterns by Use Case

The right integration pattern depends on your product's billing model. The three most common patterns are: real-time checkout validation, async onboarding validation, and recurring subscription re-validation.

**Real-time checkout validation** is the synchronous pattern: the buyer enters a VAT number, your server validates it before completing the order, and the result determines whether tax_exempt: 'reverse' is set on the Stripe customer. Latency matters here — keep the timeout to 5 seconds and display a loading state during the VIES call. For Stripe integration this is the recommended pattern.

**Async onboarding validation** defers the check: the customer provides a VAT number at signup, you queue the validation for a background job, and you send a confirmation email once VIES confirms the registration. Use this pattern when your checkout is optimised for speed and you want to avoid VIES latency in the critical path. The trade-off is that the first invoice may need to be re-issued once validation completes.

**Recurring subscription re-validation** is a monthly background job that re-checks all customers with tax_exempt: 'reverse'. VAT registrations change — build this from day one, not as an afterthought when your first audit happens.

Rate Limits and Caching

VIES imposes query rate limits at the EU and member-state level. The official limit is not published but is approximately 100 requests per hour per source IP for live queries. This becomes a problem for businesses doing bulk onboarding of existing supplier lists or re-validating large subscription bases. A VAT validation API with Redis caching resolves this: repeated lookups for the same VAT number within the cache window (typically 24 hours) are served from cache with sub-10ms latency and do not consume VIES quota.

For bulk validation use cases — importing a CSV of 5,000 supplier VAT numbers from your ERP, or monthly re-validation of a large subscriber base — use the batch endpoint rather than looping through the single-validate endpoint. The TaxID batch endpoint handles VIES rate limiting internally and processes lists of up to 500 numbers per request.

Code Examples in Four Languages

typescriptvalidate.ts
// TypeScript / Node.js
async function validateEuVat(country: string, vat: string) {
  const res = await fetch(
    `https://taxid.dev/api/v1/validate/${country}/${vat}`,
    {
      headers: { Authorization: `Bearer ${process.env.TAXID_API_KEY}` },
      signal: AbortSignal.timeout(5000),
    }
  );
  if (!res.ok) throw new Error(`API error: ${res.status}`);
  return res.json() as Promise<{
    valid: boolean; status: string;
    company_name: string | null; address: string | null;
    cached: boolean; request_id: string;
  }>;
}
pythonvalidate.py
import httpx

def validate_eu_vat(country: str, vat: str, api_key: str) -> dict:
    response = httpx.get(
        f"https://taxid.dev/api/v1/validate/{country}/{vat}",
        headers={"Authorization": f"Bearer {api_key}"},
        timeout=5.0,
    )
    response.raise_for_status()
    return response.json()
phpvalidate.php
<?php
function validateEuVat(string $country, string $vat, string $apiKey): array {
    $ch = curl_init("https://taxid.dev/api/v1/validate/{$country}/{$vat}");
    curl_setopt_array($ch, [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_HTTPHEADER => ["Authorization: Bearer {$apiKey}"],
        CURLOPT_TIMEOUT => 5,
    ]);
    $response = curl_exec($ch);
    curl_close($ch);
    return json_decode($response, true);
}

Scaling Beyond 10,000 Validations Per Day

At high validation volumes, the main constraints shift from VIES latency to API rate limits and cost per validation. At this scale, optimise your integration in three ways: (1) Add a local format check before every API call — format_invalid errors return instantly without consuming quota, and user input data is noisy enough that 20–30% of submissions may have formatting errors. (2) Cache validation results in your own database alongside the VIES cache. A customer's VAT number does not change between monthly invoices — serve from your cache for recurring billing, call the API only for the monthly re-validation job. (3) Use the batch endpoint for bulk operations instead of sequential single-validate calls. The batch endpoint processes up to 500 numbers per request with a single HTTP round-trip.

For country-specific validation details, format specifications, and per-country code examples, see the VAT API country pages. The pages for Germany, France, United Kingdom, Italy, and Spain cover the most commonly validated markets in detail.

AG
Alberto García

Founder, TaxID

Building EU VAT validation tools for developers. Obsessed with compliance automation and developer experience.