# EU VAT Validator – VIES Check with Consultation Number (`rowfeed/eu-vat-validator`) Actor

Check EU VAT numbers against the EU's own VIES service. A busy member state is reported as unavailable, never as invalid. Official consultation number, optional trade name, name match.

- **URL**: https://apify.com/rowfeed/eu-vat-validator.md
- **Developed by:** [Rowfeed](https://apify.com/rowfeed) (community)
- **Categories:** Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.50 / 1,000 vat checks

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

Validate EU VAT numbers straight against the European Commission's own VIES service - no key, no
signup. Built for finance and ops teams, invoicing/e-commerce checkout flows, accounting
integrations, and AI agents that need a clean, typed answer instead of a scraped confirmation page.
Every check can carry your own EU VAT number to get a real **consultation number**, the evidence an
auditor asks for.

**Unavailable is never reported as invalid.** When a member state's own VIES system is too busy to
answer, this Actor retries and tells you so - it never turns "we don't know yet" into "invalid",
which is the mistake that makes a buyer wrongly refuse zero-rating to a legitimate business.

### What you get

- **A real answer, not a guess.** `status` is one of `valid`, `invalid`, `service_unavailable` or
  `invalid_input` - `service_unavailable` and `is_valid: null` when a member state's system is
  busy, retried up to 3 times first. Never reported as `invalid`.
- **Consultation number as audit proof.** Supply your own EU VAT number (`requesterVatNumber`) and
  VIES hands back a `consultation_number` - the same evidence an auditor asks for when you claim
  zero-rating on an intra-EU sale.
- **Trader name and address, straight from VIES - or none at all.** `includeTraderDetails` (on by
  default) adds `trader_name`/`trader_address` exactly as VIES publishes them. Turn it off and you
  get only the match result: nothing but `status`, `is_valid` and the name-match fields below.

### Verify a name without ever seeing it

Give an `expectedNames` entry (the name on your invoice) alongside a VAT number and this Actor
tells you how well it matches VIES's own record - `exact`, `strong`, `weak` or `none`, with a
0.0-1.0 `name_match_score` - after normalising both names (case, punctuation, legal forms like
B.V., BV, LTD, LIMITED, GMBH, AB, AS, SA, SRL, OY stripped so "Coolblue BV" matches "Coolblue
B.V."). This works whether or not `includeTraderDetails` is on, so you can wire up a name check
without ever pulling the raw trader name into your own systems if you don't need it.

### Example output row

One real row from a default run (`NL810433941B01`, 2026-09-23, `requesterVatNumber` left blank so
`consultation_number` is null here - see below for a row with one):

```json
{
  "input_vat": "NL810433941B01",
  "country_code": "NL",
  "vat_number": "810433941B01",
  "status": "valid",
  "is_valid": true,
  "service_error": "VALID",
  "request_date": "2026-09-23T12:30:59.208Z",
  "consultation_number": null,
  "name_match": null,
  "name_match_score": null,
  "details_available": true,
  "checked_at": "2026-09-23T12:30:56+00:00",
  "trader_name": "COOLBLUE B.V.",
  "trader_address": "\nWEENA 00664\n3012CN ROTTERDAM\n"
}
```

With `requesterVatNumber: "IE6388047V"` set, the same number instead returns a real
`consultation_number`: `"WAPIAAAAaDOP_yao"`. With `includeTraderDetails: false`, the
`trader_name`/`trader_address` keys are absent from the row entirely - not null, not present.

Some member states (Germany here) don't publish a name/address at all - VIES sends back the
placeholder `"---"` for both, which this Actor turns into `trader_name: null`,
`trader_address: null` and `details_available: false`, never the literal string `"---"`.

You can download the dataset in various formats such as JSON, CSV or Excel from the Apify Console,
or pull it through the API/integrations.

### Input

- **VAT numbers** (`vatNumbers`) - with the 2-letter country code; accepts `"NL810433941B01"`,
  `"NL 8104 33941 B01"` or `"nl810433941b01"`. Default: `NL810433941B01`, `IE6388047V`,
  `DE811907980`.
- **Expected names** (`expectedNames`) - optional, matched positionally to `vatNumbers`; leave an
  entry blank to skip the name check for that number.
- **Your EU VAT number** (`requesterVatNumber`) - optional; set it to get a real
  `consultation_number` back on every row.
- **Include trader name and address** (`includeTraderDetails`) - on by default. Turn off for a
  match-only result with no trader name or address anywhere in the row.
- **Max VAT numbers** (`maxNumbers`) - cap on rows checked, 1-1000. Default 100.

A syntactically impossible VAT number (unrecognised country code, empty) never touches the
network - it comes back as `status: "invalid_input"` with an `errorMessage`, and the run still
exits successfully.

The default input runs with zero edits and returns three real, valid VAT numbers.

### Pricing

Pay-per-event:

- **Actor start** - $0.001, once per run.
- **VAT check** - $0.0025 per VAT number checked (status, optional trader details, optional name
  match, optional consultation number - all in one event).

$2.50 per 1,000 VAT numbers checked. `invalid_input` rows (malformed input, no network call made)
are not charged.

### Reliability

Requests stay under ~2/second. When VIES itself reports a member state's system as busy
(`MS_MAX_CONCURRENT_REQ`, `MS_UNAVAILABLE`, `TIMEOUT`, `SERVICE_UNAVAILABLE`) the check is retried
up to 3 times with exponential backoff before the row is reported `service_unavailable`. Separately,
network errors, HTTP 429 and 5xx responses retry with their own exponential backoff (429 honours
`Retry-After`); 401/403 and other unexpected responses are reported once, not retried. Every
failure is counted by category in the run's `STATS` key-value record.

Trader name and address are never logged and never written anywhere outside the dataset row you
own - not even when `includeTraderDetails` is off, since in that case they are simply left out of
the row.

Not affiliated with the European Commission or VIES.

# Actor input Schema

## `vatNumbers` (type: `array`):

EU VAT numbers to check, with the 2-letter country code. Accepts "NL810433941B01", "NL 8104 33941 B01" or "nl810433941b01" - spacing and case do not matter.

## `expectedNames` (type: `array`):

Company names to match against VIES, one per VAT number in "VAT numbers" (same position). Leave an entry blank ("") to skip the name check for that number. The raw name from VIES is never returned - only whether it matches.

## `requesterVatNumber` (type: `string`):

Your own EU VAT number, e.g. "IE6388047V". When set, VIES returns a consultation number (requestIdentifier) with each check - the evidence an auditor asks for. Leave blank to skip this.

## `includeTraderDetails` (type: `boolean`):

When on, each row gets trader\_name and trader\_address exactly as VIES publishes them. Turn it off to get only the match result (status, is\_valid, name\_match) with no trader name or address in the row.

## `maxNumbers` (type: `integer`):

Maximum number of VAT numbers to check in this run. Each row is one vat-check event ($0.0025).

## Actor input object example

```json
{
  "vatNumbers": [
    "NL810433941B01"
  ],
  "expectedNames": [
    "Coolblue B.V."
  ],
  "requesterVatNumber": "",
  "includeTraderDetails": true,
  "maxNumbers": 100
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {};

// Run the Actor and wait for it to finish
const run = await client.actor("rowfeed/eu-vat-validator").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {}

# Run the Actor and wait for it to finish
run = client.actor("rowfeed/eu-vat-validator").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{}' |
apify call rowfeed/eu-vat-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,rowfeed/eu-vat-validator"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/Xl4PcLHjfK5nSh6qc/builds/gqfyb7KXXt0SXfgfy/openapi.json
