# EU VAT Number Validation and Trader Lookup (`gubidonius/vat-validation`) Actor

Checks EU VAT numbers against the Commission's own VIES service and returns the registered trader name and address. Tells an invalid number apart from a member state that did not answer, which the raw service does not. No key and no login.

- **URL**: https://apify.com/gubidonius/vat-validation.md
- **Developed by:** [Gregory Bolshakov](https://apify.com/gubidonius) (community)
- **Categories:** Business, MCP servers, Agents
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

Pay per event

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## EU VAT Number Validation and Trader Lookup

Checks EU VAT numbers against the European Commission's own VIES service and returns the
registered trader name and address.

### The thing this does that the raw service does not

VIES answers with a boolean, and that boolean means two completely different things.

```
FR44732829320   isValid false   INVALID           the number is genuinely not real
IE6388047V      isValid false   MS_UNAVAILABLE    Ireland's register did not answer
```

Read the boolean and stop, and on a day when Ireland is offline you tell your finance team
that a real customer's VAT number is fake. The invoice that follows carries VAT that should
have been reverse charged, and nobody finds out until an audit.

So every row here has a **status with three values**: `valid`, `invalid`, or `not-checked`.
The raw boolean is still on the row as `viesIsValid`, next to the status that explains what
it means. The run summary lists every number that could not be checked, so you have a retry
list rather than a wrong answer.

Anything unexpected also lands on `not-checked`. An error code nobody has seen before is not
evidence that a number is bad.

### A busy register is not an answer

Some member states cap how many checks their register answers at once, and that cap counts
every caller in Europe, not just this Actor. France hits it a lot. Measured on 3 September
2026, one request at a time with six seconds between them, France answered "too many
requests" on 6 of 8 tries and gave a real answer on the other 2.

So a number is asked again, up to four times, while VIES says it is busy. Two of three test
runs had the real answer by the second attempt. The `attempts` column says how many it took.
A row is only `not-checked` after those four tries, or when the register is down, which is a
different code and is not retried.

Numbers are never sent to one member state in parallel. **Parallel checks** sets how many
countries run at once. A list that is all French numbers is checked one at a time, because
sending four at once only adds to the queue that is rejecting them.

### Germany returns no name, and that is correct

`DE811569869` is valid and comes back with no trader name and no address. Germany does not
disclose those through VIES. A blank name on a German row is a complete answer, not a failed
lookup, and this Actor does not report it as one.

### Country codes

Greece files under `EL`, not `GR`, and VIES rejects `GR` outright as not a member state.
Type either. If you type `GR` it is corrected and the row says so in
`countryCodeCorrectedFrom`, rather than being changed behind your back.

Northern Ireland is covered as `XI`. Great Britain is not covered at all any more.

Spaces, dots and dashes are stripped, so `DE 811 569 869` works.

### Re-checking a customer list

Turn on **Only numbers whose status changed** and each run returns what moved: a number that
became invalid, or one that is new to your list.

A number VIES could not reach is never counted as a change, and never overwrites the status
it had. An outage is not a customer deregistering, and putting that distinction in your
history is the whole point.

### Access

Free, no key, no login. Four member states are checked in parallel by default, and one
member state is only ever asked one thing at a time.

# Actor input Schema

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

Each with its country code, such as BE0417497106. Spaces, dots and dashes are stripped. GR is corrected to EL, which is the code Greece actually files under.

## `onlyChangedSinceLastRun` (type: `boolean`):

Returns only numbers that are new to the list or whose status moved, for re-checking a customer list on a schedule. A number VIES could not reach is never counted as a change, because an outage is not a deregistration.

## `concurrency` (type: `integer`):

How many member states to check at once. Numbers are never sent to one register in parallel, because the cap that rejects them belongs to that register and counts every caller in Europe. A list that is all one country is checked one number at a time.

## Actor input object example

```json
{
  "vatNumbers": [
    "BE0417497106",
    "DE811569869",
    "IE6388047V",
    "FR44732829320"
  ],
  "onlyChangedSinceLastRun": false,
  "concurrency": 4
}
```

# Actor output Schema

## `checks` (type: `string`):

Status, trader name and address for each VAT number.

## `summary` (type: `string`):

Counts by status, and every number the member state failed to answer for.

# 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 = {
    "vatNumbers": [
        "BE0417497106",
        "DE811569869",
        "IE6388047V",
        "FR44732829320"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("gubidonius/vat-validation").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 = { "vatNumbers": [
        "BE0417497106",
        "DE811569869",
        "IE6388047V",
        "FR44732829320",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("gubidonius/vat-validation").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 '{
  "vatNumbers": [
    "BE0417497106",
    "DE811569869",
    "IE6388047V",
    "FR44732829320"
  ]
}' |
apify call gubidonius/vat-validation --silent --output-dataset

```

## MCP server setup

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

```

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/p3TaO50PMD6dwqwAh/builds/cHz3PbaPxbTrNkf49/openapi.json
