Home / Use cases / Vue.js

Vue.js

Vue.js VAT Validation: Real-time Tax ID Checks with Composition API

Integrate real-time EU VAT number validation into Vue.js 3 applications using the Composition API. Custom composable, debounced input, and a server-side proxy pattern to protect your API key.

Vue.js 3's Composition API makes it straightforward to encapsulate VAT validation logic in a reusable composable. The key design principle is the same as any frontend framework: never call the TaxID API directly from the browser. Your API key would be visible in network requests. Instead, build a thin backend endpoint (any server-side framework works — Node.js, PHP, Python, Go) that accepts the VAT number and calls TaxID, then return only the fields your UI needs.

The validation composable manages three reactive states: idle (no input or input too short), loading (API call in flight), and result (one of valid, invalid, or unavailable). Debouncing is essential — without it, every keystroke triggers an API call. A 600ms debounce is the right balance between responsiveness and API quota efficiency.

For the company name confirmation pattern that B2B checkout flows require, bind the composable's company_name and address return values to a read-only confirmation block below the input. Show it only when the state is 'valid'. This gives users the visual confirmation they need before submitting the form: they can see the registered company name and address match what they expect.

Error handling has two distinct cases: format_invalid (user input error — show a format hint immediately) and service_unavailable (VIES is down — show a non-blocking warning and proceed with standard VAT). Never block the user from completing checkout when VIES is unavailable; that creates a hard dependency on an external service with variable uptime.

For server-side rendered applications using Nuxt.js, the same composable works client-side with a Nuxt server route as the backend proxy. The useFetch composable in Nuxt 3 can replace the raw fetch call in the composable, adding SSR support and automatic hydration.

Implementation steps

  1. 1

    Create the server-side proxy endpoint

    Add a backend route that accepts POST with a vatNumber body, calls GET /api/v1/validate/:country/:vat with your server-side API key, and returns only the fields your frontend needs: valid, status, companyName, address. This keeps the API key server-side and gives you a place to add rate limiting.

  2. 2

    Build the useVatValidation composable

    Create a composable that accepts a vatNumber ref, debounces changes by 600ms using watchEffect and a manual setTimeout, calls your proxy endpoint on each debounced change, and returns reactive refs for state ('idle' | 'loading' | 'valid' | 'invalid' | 'unavailable'), companyName, address, and errorMessage.

  3. 3

    Build the VatInput component

    Create a Vue component that takes a modelValue prop (for v-model), emits update:modelValue and vatValidated events, uses useVatValidation internally, and renders inline feedback below the input: a spinner when loading, a green confirmation box with company name when valid, a red error message when invalid, and a yellow warning when unavailable.

  4. 4

    Handle the vatValidated event in the parent form

    Listen for the vatValidated event from VatInput in your checkout form component. When the event fires with a valid result, store the vatNumber and companyName in your form state. When the input is cleared, reset these to null. Submit both to your backend along with the order data so the VAT validation is recorded alongside the order.

  5. 5

    Add Nuxt server route for Nuxt 3 projects

    For Nuxt 3, create server/routes/api/validate-vat.post.ts. Use the event body to get vatNumber, call the TaxID API with useRuntimeConfig().taxidApiKey, and return the filtered result. Register the key in nuxt.config.ts under runtimeConfig.taxidApiKey (not runtimeConfig.public — keeps it server-only).

Code example

Node.js

const res = await fetch(
  'http://localhost:3000/api/v1/validate/DE/DE123456789',
  { headers: { 'Authorization': 'Bearer YOUR_API_KEY' } }
);
const { valid, status, company_name, company_address } = await res.json();

if (valid) {
  console.log(`Valid EU business: ${company_name}`);
} else if (status === 'service_unavailable') {
  // VIES is temporarily down — retry or allow with manual check
  console.log('VIES unavailable — check back in a few minutes');
} else {
  console.log('Invalid VAT number — charge local tax rate');
}

Python

import requests

res = requests.get(
    "http://localhost:3000/api/v1/validate/DE/DE123456789",
    headers={"Authorization": "Bearer YOUR_API_KEY"}
)
data = res.json()

if data["valid"]:
    print(f"Valid: {data['company_name']}")
elif data["status"] == "service_unavailable":
    print("VIES temporarily unavailable")
else:
    print("Invalid VAT number")

API response

The TaxID API returns a consistent JSON response for every validation request:

200 OK (active)valid
{
  "valid": true,
  "status": "active",
  "country_code": "DE",
  "vat_number": "123456789",
  "company_name": "Example GmbH",
  "company_address": "Musterstraße 1, 10115 Berlin",
  "request_date": "2026-05-10T00:00:00.000Z",
  "cached": false,
  "request_id": "req_01j..."
}

Error handling

The API uses a consistent Stripe-style error format. Always handle service_unavailable separately — VIES has occasional downtime and you should not reject valid customers during outages.

active

VAT number is valid and the business is registered

invalid

VAT number format is wrong or not registered in VIES

service_unavailable

VIES or the national system is temporarily down — retry later

Frequently asked questions

Can I use Vue 2 with the Options API instead?

Yes. Extract the debounce and fetch logic from the composable into a mixin, and use data() for the reactive state instead of ref(). The fetch call and response handling are identical — only the Vue-specific wiring changes. Vue 2 with the @vue/composition-api plugin can also use the composable pattern directly.

How do I test the composable with Vue Test Utils?

Mock the fetch call using vi.spyOn(global, 'fetch') in Vitest or jest.spyOn in Jest. Mount a wrapper component that uses the composable, trigger input changes, advance timers past the debounce threshold with vi.runAllTimers(), then assert the reactive state. Avoid making real API calls in tests — they are non-deterministic and consume quota.

Does this work with Pinia or Vuex for global state?

Yes. If VAT validation status needs to be accessible from multiple components (for example, a checkout summary and a billing form), move the composable's state into a Pinia store action. The composable stays as a local helper that calls the store action rather than managing its own state.

Further reading

Evaluating EU VAT APIs? Compare TaxID with:

Ready to implement?

Get your free API key and start validating EU VAT numbers today.

Get free API key

Related use cases

Stripe EU VAT: Validate Tax IDs Before Charging Customers

Stripe EU VAT integration guide: validate EU VAT numbers server-side before applying zero-rate B2B e...

UK VAT validation in Shopify B2B

Validate UK VAT numbers for B2B customers in Shopify. Required under UK Making Tax Digital rules for...

WooCommerce Spain NIF/CIF validation

Validate Spanish NIF and CIF numbers in WooCommerce checkout. Automatically apply B2B tax exemptions...