# EU VAT Number Validator - Bulk VIES Check (`lukehunter/eu-vat-number-validator`) Actor

Bulk EU VAT number check against the official VIES service: valid or invalid, registered name and address, optional consultation number. $0.003 per check; malformed numbers and member-state downtime are free.

- **URL**: https://apify.com/lukehunter/eu-vat-number-validator.md
- **Developed by:** [Luke Hunter](https://apify.com/lukehunter) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 vat number 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

## EU VAT Number Validator - Bulk VIES Check

**For finance, procurement, compliance and e-commerce teams that need a reliable EU VAT number check.** Paste a list of VAT numbers in any common format and get one clean row per number from **VIES**, the European Commission's official VAT Information Exchange System: **valid or invalid**, the registered **name and address** as VIES returns them, and, if you give your own VAT number, the **consultation number** that proves the check. A **VAT validation API** you can run in **bulk** from the Console, a schedule, code or an AI agent.

**$0.003 per number that gets a definitive valid or invalid answer. Malformed numbers and member-state downtime are free. 1,000 numbers = $3.**

It calls the free, public VIES REST service directly: no scraping, no login, no proxy, no API key. It is deliberately gentle with the service (one request per second, backoff when a member state is busy) and charges only when VIES gives a real answer.

### What you get

| Field | Meaning |
|---|---|
| `input` | The number exactly as you gave it. |
| `index` | Position in your input (rows are not in input order; sort by this). |
| `countryCode`, `vatNumber` | Normalised parts, e.g. `DE` and `123456789`. Greece is `EL`, Northern Ireland `XI`. |
| `valid` | `true`, `false`, or `null` when there was no answer. |
| `status` | `valid`, `invalid` (both charged) or `malformed`, `member-state-unavailable`, `error`, `not-processed` (all free). |
| `name`, `address` | Registered name and address as returned by VIES. Some member states return `---` instead. |
| `requestDate` | When VIES answered. |
| `consultationNumber` | VIES request identifier, only when you set `requesterVatNumber`. |
| `retryable`, `message` | Whether trying again later may help, and why a row has no answer. |

### Quick start

```json
{
  "vatNumbers": ["DE811128135", "FR40303265045"],
  "maxItems": 5
}
```

Accepted formats: `DE123456789`, `de 123 456 789`, `DE-123.456.789`, `ATU12345678`, `EL123456789` (Greece; `GR` is accepted and converted), `XI123456789` (Northern Ireland), `VAT DE123456789`. Every number is checked against its country's format first, for all 27 EU member states plus XI, so a typo costs nothing and never reaches VIES.

### Recipes

**Validate supplier VAT numbers before invoicing.** Export the VAT numbers from your supplier master data or purchase orders and check them in one run. Stop payment on anything `invalid`, and compare `name` and `address` with your records where the member state returns them:

```json
{
  "vatNumbers": ["DE811128135", "FR40303265045", "NL123456789B01", "ATU12345678"],
  "requesterVatNumber": "DE123456789",
  "maxItems": 500
}
```

With `requesterVatNumber` (your own VAT number) VIES issues a consultation number per check. Keep it with the invoice file as evidence that you checked.

**Check VAT IDs found by the Impressum scraper.** [Impressum Scraper](https://apify.com/lukehunter/impressum-scraper) reads the legal notice of German, Austrian and Swiss company websites and outputs a `vatId`. Chain its dataset straight in, without exporting anything:

```json
{
  "datasetId": "<DEFAULT_DATASET_ID_OF_THE_IMPRESSUM_RUN>",
  "vatField": "vatId",
  "maxItems": 1000
}
```

Items without a VAT ID are skipped. Or pass the items yourself as `rows`: `[{"domain": "example.com", "vatId": "DE123456789"}]`. Join the result back to your list on `input`.

**Re-check on a schedule.** Save your input as a Task and schedule it monthly. Rows with `retryable: true` are the numbers to run again.

### Input

| Field | Default | What it does |
|---|---:|---|
| `vatNumbers` | none | VAT numbers with country prefix. Max 10,000 per run in total. |
| `rows` | none | Objects with a VAT field, merged with `vatNumbers`. |
| `datasetId` | none | An Apify dataset whose items have a VAT field (up to 10,000 items are read). |
| `vatField` | auto | Field name holding the number. Empty tries `vatId`, `vatNumber`, `vat`, `vat_id`, `vatNo`, `ustId`, `taxId`. |
| `requesterVatNumber` | none | Your own VAT number, to get consultation numbers. Not output, not logged. |
| `maxItems` | `100` | Hard cap on charged checks, 1 to 10,000. |
| `delaySeconds` | `1` | Seconds between VIES requests, 1 to 10. It can be raised, never lowered. |

Each distinct number is checked once; duplicates are skipped.

### Example output

```json
{
  "input": "de 123 456 789",
  "index": 1,
  "countryCode": "DE",
  "vatNumber": "123456789",
  "valid": true,
  "status": "valid",
  "name": "BEISPIEL HANDELS GMBH",
  "address": "MUSTERSTRASSE 1\n10115 BERLIN",
  "requestDate": "2026-10-11T10:15:30.123Z",
  "consultationNumber": null,
  "retryable": false,
  "message": null,
  "scrapedAt": "2026-10-11T10:15:00.000Z"
}
```

A malformed number is reported in the same shape, free and never sent to VIES:

```json
{
  "input": "DE12",
  "index": 2,
  "countryCode": "DE",
  "vatNumber": "12",
  "valid": null,
  "status": "malformed",
  "name": null,
  "address": null,
  "requestDate": null,
  "consultationNumber": null,
  "retryable": false,
  "message": "The number does not match the DE VAT number format. Not sent to VIES. Not charged.",
  "scrapedAt": "2026-10-11T10:15:00.000Z"
}
```

The sample is illustrative (an invented company), shaped like the VIES response. Status meanings:

- `valid` / `invalid`: VIES gave a definitive answer. **Charged.**
- `malformed`: the number failed the format check, or VIES rejected the input. Free.
- `member-state-unavailable`: the member state's own VAT system was down, busy or timed out, after several retries. Free and `retryable`. If a country fails three times in a row, its remaining numbers in that run are skipped (also free) so the run does not keep hammering it.
- `error`: VIES itself was busy or unreachable, or answered unexpectedly. Free.
- `not-processed`: not checked because `maxItems`, your spending limit or the run time budget was reached. Free; run those numbers again.

### Use with AI agents (MCP)

This Actor works as a tool for Claude, ChatGPT, Cursor and other MCP clients through [Apify's MCP server](https://mcp.apify.com). Every input property description is short and carries an example, so agents fill it in correctly. None is required, but give it `vatNumbers`, `rows` or a `datasetId`.

**Claude Desktop** (`claude_desktop_config.json`), with your own token from Apify Console > Settings > API & Integrations:

```json
{
  "mcpServers": {
    "apify-eu-vat-number-validator": {
      "command": "npx",
      "args": [
        "-y", "mcp-remote",
        "https://mcp.apify.com?tools=lukehunter/eu-vat-number-validator",
        "--header", "Authorization: Bearer ${APIFY_TOKEN}"
      ],
      "env": { "APIFY_TOKEN": "<YOUR_APIFY_TOKEN>" }
    }
  }
}
```

Clients that support OAuth (Claude.ai, VS Code) can connect with just the URL `https://mcp.apify.com?tools=lukehunter/eu-vat-number-validator` and sign in to Apify. Never paste a real token into a shared file or chat.

**Example.** You ask: "Is this supplier's VAT number FR40303265045 valid, and what name is it registered under?" The agent calls `lukehunter--eu-vat-number-validator` with:

```json
{
  "vatNumbers": ["FR40303265045"],
  "maxItems": 1
}
```

It is charged $0.003 for the one definitive answer, capped by `maxItems`.

### Pricing

Pay-per-event: **$0.003 per check** (event `check`), meaning one number that got a definitive valid or invalid answer from VIES. Malformed numbers, member-state downtime, errors and `not-processed` rows are free.

| Numbers checked | Cost |
|---:|---:|
| 5 | $0.015 |
| 100 (default cap) | $0.30 |
| 1,000 | $3.00 |
| 10,000 (maximum) | $30.00 |

For comparison, other VAT checkers on the Apify Store were listed in October 2026 at between $0.003 and $0.04 per number, some bundled into larger company-enrichment Actors. `maxItems` and your Apify per-run spending limit both stop delivery before the cap is passed: a result is delivered and charged together, and never past your limit. Price changes follow Apify's notice rules; the Pricing tab always shows the rate you are charged.

### Limitations

- **It is the official VIES service, and its availability depends on member-state systems.** VIES forwards each query to the tax administration of the country that issued the number. Those systems have maintenance windows and busy periods. The Actor retries with backoff and then reports `member-state-unavailable` for free; it cannot make a down system answer.
- **Name and address vary by country.** Some member states (Germany and Spain, for example) return `---` instead of the name and address. They are passed through exactly as VIES returns them.
- **Speed.** One request per second by default, so 1,000 numbers take about 17 minutes. This keeps the load on a shared public service low. The delay can be raised but not lowered, and a run stops after 5 hours.
- **VIES confirms registration, not trustworthiness.** A valid result means the number is registered for VAT intra-EU trade in that country at the time of the check. Format checks are syntax only. Northern Ireland (`XI`) numbers are checked for goods only. UK (`GB`) numbers are not in VIES.
- **Business data and personal data.** VIES publishes registered names and addresses, which for sole traders can identify a person. The Actor outputs only what VIES returns, and your own VAT number (if given) is neither logged nor output. Handle the data in line with the GDPR.
- **Not legal or tax advice.** This Actor is independent and not affiliated with the European Commission. Whether a transaction qualifies for a VAT exemption is for you and your adviser to decide; keep the VIES consultation number or your own records where the law requires proof.

### Use it from your code, Make, Zapier or n8n

Built for automated pipelines: small runs, stable field names, one row per item, and you pay only for rows actually delivered.

**One API call that runs the Actor and returns the data** (replace `$APIFY_TOKEN` with your token from Apify Console → Settings → Integrations):

```bash
curl -s -X POST "https://api.apify.com/v2/acts/lukehunter~eu-vat-number-validator/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"vatNumbers": ["DE811128135", "FR40303265045"], "maxItems": 5}'
```

**JavaScript** (`npm i apify-client`):

```js
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('lukehunter~eu-vat-number-validator').call({
  "vatNumbers": [
    "DE811128135",
    "FR40303265045"
  ],
  "maxItems": 5
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

**Python** (`pip install apify-client`):

```python
import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('lukehunter~eu-vat-number-validator').call(run_input={"vatNumbers": ["DE811128135", "FR40303265045"], "maxItems": 5})
items = client.dataset(run['defaultDatasetId']).list_items().items
```

**Schedules, webhooks and no-code tools:** save your input as a Task, add a Schedule (e.g. hourly or daily), and attach a webhook or one of the Integrations tab's connectors (Make, Zapier, n8n, Google Sheets, Slack) to push each run's results into your own system.

**Feedback:** an honest rating on the Store page, good or bad, helps others decide if this Actor fits their needs. Missing a field or found a problem? Open an issue on the Actor page.

### FAQ

**Is this the official VIES service?** It calls the official VIES REST API of the European Commission (`ec.europa.eu/taxation_customs/vies`). The Actor adds bulk handling, format checks, retries and billing only for real answers.

**Why was my number charged although it is invalid?** `invalid` is a definitive answer from VIES (the number is not registered), which is what you asked for. Numbers that fail the format check are `malformed` and free.

**What is a consultation number?** A reference VIES issues when you identify yourself with your own VAT number. Businesses keep it as proof they checked a customer's number. Set `requesterVatNumber` to get one per check.

**A member state is unavailable. What now?** Rows come back as `member-state-unavailable` with `retryable: true`, free. Run those numbers again later, often an hour is enough.

**Does it validate the check digit?** No. Only the country-specific format is checked offline; whether the number is registered is VIES's answer.

**Can I check UK numbers?** Not in VIES after Brexit, except Northern Ireland goods numbers with the `XI` prefix.

### Related Actors

Other data tools from the same developer, built to the same standard: official or public sources, hard cost caps, and honest documentation of limits.

- **[Domain Email Provider Lookup](https://apify.com/lukehunter/email-provider-mx-lookup)**: the email provider (Google Workspace, Microsoft 365, Zoho and more), MX, SPF and DMARC setup of any domain list, with provider-change alerts.
- **[French Company Leads](https://apify.com/lukehunter/french-companies-sirene)**: French companies (entreprises) from the official Sirene-based API, filtered by NAF code, department, headcount and creation date, with SIREN/SIRET, officers and new-company alerts.
- **[Poland KRS Company Lookup](https://apify.com/lukehunter/poland-krs-company-lookup)**: Polish companies from the official KRS API by KRS number: NIP, REGON, address, share capital, PKD and the board as published, with optional VAT status.
- **[Website Contact Scraper](https://apify.com/lukehunter/company-contact-scraper)**: emails, phone numbers and social profile links from any list of company domains, with the page each came from and a changed-contacts-only mode for scheduled runs.
- **[Impressum Scraper](https://apify.com/lukehunter/impressum-scraper)**: legal entity data from the imprint of any company website in Germany, Austria and Switzerland: legal name and form, managing directors, HRB/HRA or FN register, VAT id and address, with a changed-data-only mode for scheduled runs.
- **[Website Tech Stack Detector](https://apify.com/lukehunter/website-tech-stack-detector)**: the technologies a website runs (CMS, ecommerce platform, analytics, ad pixels, chat, payments, CDN, frameworks) from its home page, with the evidence for each match and a changed-technologies-only mode for scheduled runs.
- **[Google Maps Email Extractor & Lead Enricher](https://apify.com/lukehunter/lead-enricher)**: emails, phones, social links, named people and the tech stack of every business in your Google Maps Scraper results, one enriched row per business, with filters such as only Shopify stores with an email.
- **[Federal Register Rule Effective-Date Radar](https://apify.com/lukehunter/federal-rule-effective-date-radar)**: final federal rules about to take effect, with citations, dockets and urgency.

# Actor input Schema

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

EU VAT numbers with country prefix, e.g. \["DE123456789", "ATU12345678", "EL123456789", "XI123456789"]. Spaces, dots and dashes are removed and case is ignored ("de 123 456 789" works). Malformed numbers are free and never sent to VIES. Max 10,000 per run.

## `rows` (type: `array`):

Objects that carry a VAT number, e.g. \[{"domain": "example.com", "vatId": "DE123456789"}]. The number is read from "vatField", or from vatId, vatNumber, vat, vat_id, vatNo, ustId or taxId. Rows without one are skipped. Merged with vatNumbers.

## `datasetId` (type: `string`):

ID of an Apify dataset whose items have a VAT field, to chain runs, e.g. the default dataset of an Impressum Scraper run (field vatId). Read at the start of the run, up to 10,000 items. Merged with vatNumbers and rows.

## `vatField` (type: `string`):

Field that holds the VAT number in rows or the dataset, e.g. "vatId". Leave empty to try vatId, vatNumber, vat, vat_id, vatNo, ustId and taxId in that order.

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

Your own EU VAT number, e.g. "DE123456789". When set, VIES returns a consultation number (requestIdentifier) for each check, which you can keep as proof of the check. Not output and not logged. Leave empty for a plain check.

## `maxItems` (type: `integer`):

Hard cap on charged checks (definitive valid or invalid answers), 1 to 10,000, e.g. 100. At $0.003 per check, 100 checks cost $0.30. Numbers not checked because the cap was reached come back as not-processed rows, free.

## `delaySeconds` (type: `number`):

Pause between VIES requests, 1 to 10, e.g. 2. The default of 1 means one request per second, which keeps the load on the official service low. It can be raised, never lowered. 1,000 numbers take about 17 minutes at 1.

## Actor input object example

```json
{
  "vatNumbers": [
    "DE811128135",
    "FR40303265045"
  ],
  "maxItems": 5,
  "delaySeconds": 1
}
```

# Actor output Schema

## `checks` (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 = {
    "vatNumbers": [
        "DE811128135",
        "FR40303265045"
    ],
    "maxItems": 5
};

// Run the Actor and wait for it to finish
const run = await client.actor("lukehunter/eu-vat-number-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 = {
    "vatNumbers": [
        "DE811128135",
        "FR40303265045",
    ],
    "maxItems": 5,
}

# Run the Actor and wait for it to finish
run = client.actor("lukehunter/eu-vat-number-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 '{
  "vatNumbers": [
    "DE811128135",
    "FR40303265045"
  ],
  "maxItems": 5
}' |
apify call lukehunter/eu-vat-number-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,lukehunter/eu-vat-number-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/c0ZsjKaU3ddRtGGqV/builds/j2keN1eUSSiYMpuTa/openapi.json
