# EU VAT Number Validator & KYB Checker (`act2labs/vies-vat-verification`) Actor

Bulk-validate EU VAT numbers against the official VIES service. Offline checksum pre-filtering, member-state outage detection, consultation numbers for audit evidence, and company-name matching.

- **URL**: https://apify.com/act2labs/vies-vat-verification.md
- **Developed by:** [J Act2Labs](https://apify.com/act2labs) (community)
- **Categories:** Developer tools, Automation, Integrations
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 vat number verifieds

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/platform/actors/running/actors-in-store#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 Validator & KYB Checker

Bulk **EU VAT number validation** against the official **VIES** service, built for finance and compliance teams who need answers they can defend in an audit. Paste a list of VAT numbers or upload customer records, and get back validity, the registered company name and address, a name-match score, and the **VIES consultation number** that proves you performed the check. Covers all 27 EU member states plus Northern Ireland, with offline checksum validation for Great Britain.

### What this VAT validation API does differently

Most VAT checkers make the same mistake: when a national tax database is offline, they report the VAT number as **invalid**. That is not a cosmetic bug. Acting on it means charging VAT to a legitimate B2B customer, or zero-rating a sale you should have taxed. Both are expensive to unwind.

This Actor never conflates the two. Every result is one of five explicit states:

| Status | Meaning |
| --- | --- |
| `valid` | Registered and active in VIES |
| `invalid` | VIES confirms the number is not registered |
| `unverifiable` | No authoritative answer — member state offline, timeout, or throttled. **Not the same as invalid, and never charged.** |
| `malformed` | Failed its country's official checksum. Rejected locally, no API call spent. |
| `not_supported` | Outside VIES coverage (Great Britain since Brexit) |

The `valid` field is `true`, `false`, or `null`. It is only ever `null` when we genuinely do not know.

### Features

- ✅ **All 27 EU member states + XI** (Northern Ireland), with `GR` accepted as an alias for `EL`
- 🔍 **Offline checksum pre-filtering** for 29 jurisdictions — typos are caught before an API call is spent, so you are not billed for them
- 🛡️ **Member-state outage detection** via the VIES status endpoint, so an outage is never reported as an invalid number
- 📜 **Consultation numbers** (`requestIdentifier`) captured as audit evidence under Article 31 of Regulation 904/2010 — this cannot be obtained retroactively
- 🏢 **Company-name matching for KYB**, tolerant of legal-form differences (`GmbH` vs `Gesellschaft mit beschränkter Haftung`), diacritics, punctuation, word order and typos
- 🔁 **Correct throttling behaviour** — VIES limits by concurrency, not requests per second, so calls to a single member state are serialised and transient errors are retried with exponential backoff
- 🧠 **Automatic de-duplication** — the same VAT number twice in one run costs one API call and one charge
- 🚦 **Fails safe on `IP_BLOCKED`** — aborts rather than hammering VIES and getting your address blocked for longer

### What data you get back

| Field | Description |
| --- | --- |
| `normalisedVatId` | Country code + number, uppercase, no separators. Safe as a database key. |
| `status` / `valid` | See the table above |
| `formatCheck` | `pass`, `fail`, or `unknown` (a possible national personal-ID variant, sent to VIES rather than rejected) |
| `companyName` / `companyAddress` | As held by the member state. `null` where the state withholds it — Germany and Spain always do. |
| `nameMatch` / `nameMatchScore` | `match` / `partial` / `mismatch`, plus a 0–1 score |
| `consultationNumber` | VIES proof-of-check identifier |
| `checkedAt` | VIES `requestDate` where available |
| `reference` | Your own row identifier, passed through unchanged |
| `message` | Plain-language explanation whenever the result needs one |

### How to validate EU VAT numbers with this Actor

1. **Simple list** — paste VAT numbers into the *VAT numbers* field, one per line. Prefixes and spacing are optional: `DE136695976`, `FR 40 303.265-045` and `ie6388047v` are all accepted.
2. **KYB mode** — use the *Records* field instead, with objects containing `vatNumber`, `expectedName` and `reference`, to also verify the registered name matches your records.
3. **Audit mode** — fill in *Your own VAT number* to receive a consultation number with every result.
4. Run it, then take the results from the dataset as JSON, CSV or Excel, or via the API.

### Pricing

Pay per event. You are charged for:

- **A verified answer** — VIES confirmed the number as valid or invalid
- **An offline rejection** — a number that failed its country's official checksum, billed at a fraction of the verified rate because no API call was made

You are **not** charged for rows we could not verify (member-state outage, timeout, throttling), or for input outside VIES coverage. Duplicate numbers within a run are charged once.

### Input example

```json
{
  "records": [
    { "vatNumber": "IE6388047V", "expectedName": "Google Ireland Limited", "reference": "CUST-1001" },
    { "vatNumber": "LU26375245", "expectedName": "Amazon Europe Core", "reference": "CUST-1002" }
  ],
  "requesterVatNumber": "DE123456789",
  "maxConcurrency": 4
}
```

### Output example

```json
{
  "input": "IE6388047V",
  "reference": "CUST-1001",
  "countryCode": "IE",
  "vatNumber": "6388047V",
  "normalisedVatId": "IE6388047V",
  "status": "valid",
  "valid": true,
  "formatCheck": "pass",
  "companyName": "GOOGLE IRELAND LIMITED",
  "companyAddress": "3RD FLOOR, GORDON HOUSE, BARROW STREET, DUBLIN 4",
  "expectedName": "Google Ireland Limited",
  "nameMatch": "match",
  "nameMatchScore": 1,
  "consultationNumber": "WAPIAAAAWuJShAAA",
  "checkedAt": "2026-08-12T11:33:23.919Z",
  "source": "vies",
  "message": null
}
```

A German number, by contrast, comes back valid with no name, because Germany withholds it:

```json
{
  "normalisedVatId": "DE123456789",
  "status": "valid",
  "valid": true,
  "companyName": null,
  "companyAddress": null,
  "nameMatch": null,
  "message": "DE does not disclose the registered name and address through VIES for data-protection reasons."
}
```

### FAQ

**Why is `companyName` empty for a valid German or Spanish VAT number?**
Those member states do not disclose the registered name and address through VIES, for data-protection reasons. VIES returns `---`, which this Actor reports as `null` with an explanatory message rather than pretending the name is missing or mismatched.

**What is a consultation number and why should I store it?**
It is the identifier VIES returns as evidence that you verified a counterparty on a specific date, relevant under Article 31 of Regulation 904/2010. VIES only issues it when you supply your own VAT number in the request, and it cannot be recovered later. If VAT compliance is the reason you are running this, fill in *Your own VAT number* and keep the output. The run summary reports how many consultation numbers came back, and the log warns you if you asked for them and got none — so you find out immediately, rather than during an audit.

**Does the name-match verdict come from VIES?**
No. VIES accepts a trader name for comparison, but member states frequently return `NOT_PROCESSED` rather than a verdict — Ireland does so even when the name supplied is character-for-character correct. So `nameMatch` and `nameMatchScore` are computed here, against the registered name VIES returns, after normalising legal-form suffixes, diacritics, punctuation and word order. VIES's own verdict is used only when a member state actually provides one.

**Can I use my UK company's tax number to get consultation numbers?**
No, for two separate reasons. First, a 10-digit number is a Unique Taxpayer Reference (UTR), which is a different identifier from a VAT registration number (9 or 12 digits). Second, even a genuine Great Britain VAT number will not work, because GB left VIES — the requester must be an EU member-state VAT number, or a Northern Ireland (`XI`) one. If you supply something unusable the Actor says so in the log and carries on without it, rather than letting VIES reject every row.

**How many numbers can I check in one run?**
There is no hard cap, but VIES throttles by concurrency and will block an IP that pushes too hard. The defaults are deliberately conservative. For large recurring lists, cache results on your side and re-check on a schedule rather than re-validating the same numbers repeatedly.

**Can it check UK VAT numbers?**
Great Britain left VIES after Brexit, so live GB lookups require the HMRC *Check a UK VAT Number* API, which needs your own OAuth credentials (the open v1 endpoint was withdrawn in February 2025). GB numbers are format- and checksum-validated offline and returned as `not_supported` rather than being wrongly marked invalid. Northern Ireland (`XI`) **is** in VIES and is fully supported.

**Does a `malformed` result mean the number is definitely fake?**
It means the number does not satisfy the published format and check digit for that country. A handful of countries allow a national personal identification number to be used as a VAT number, and those branches are poorly documented, so anything ambiguous is marked `formatCheck: "unknown"` and sent to VIES for the real answer instead of being rejected.

**Is this an official EU service?**
No. It queries the official European Commission VIES service on your behalf. The Commission obtains data from national databases and does not guarantee its accuracy.

### Notes on data protection

VIES is a register of taxable persons, not a consumer database. It is not, however, strictly company-only: several member states (Bulgaria, Czechia, Latvia, Lithuania, Romania, Slovakia) issue VAT numbers to sole traders, and in some of those the number encodes a national personal identification code that embeds a date of birth. Treat stored VAT numbers from those jurisdictions as potentially personal data under the GDPR, and take your own advice on retention.

### Support

Open an issue on the Actor's Issues tab. Bug reports with the input that triggered them get fixed fastest.

# Actor input Schema

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

One VAT number per line, with or without the country prefix and with any spacing you like. Examples: DE136695976, FR 40 303 265 045, ie6388047v. GR is accepted as an alias for EL.

## `records` (type: `array`):

Use this instead of the plain list when you also want to check that the registered company name matches what you have on file. Each object accepts: vatNumber (required), expectedName, reference.

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

Supply your own VAT registration to receive a VIES consultation number (requestIdentifier) with every result. That identifier is the only artifact that later proves you verified a counterparty on a given date, under Article 31 of Regulation 904/2010, and it cannot be obtained retroactively. It must be an EU member-state VAT number or a Northern Ireland (XI) one - Great Britain is not part of VIES. A 10-digit UK number is a Unique Taxpayer Reference, not a VAT number.

## `defaultCountryCode` (type: `string`):

Used for rows that have no country prefix. Leave empty to require a prefix on every number.

## `skipInvalidFormat` (type: `boolean`):

On by default. Numbers that fail their country's checksum are rejected without spending a VIES call, which is faster and cheaper. Turn this off to force every row through VIES regardless.

## `checkMemberStateAvailability` (type: `boolean`):

On by default. Queries the VIES status endpoint before starting so that a national database outage is reported as 'unverifiable' rather than being mistaken for an invalid VAT number.

## `maxConcurrency` (type: `integer`):

How many member states to query in parallel. Calls to a single member state are always serialised, because VIES throttles by concurrency and will block your IP if pushed. Raising this above 8 is rarely useful.

## `maxRetries` (type: `integer`):

Retry attempts, with exponential backoff, when VIES returns a transient error such as a timeout or a concurrency limit.

## Actor input object example

```json
{
  "vatNumbers": [
    "IE6388047V",
    "LU26375245",
    "FR40303265045",
    "DE136695976",
    "DE111111111"
  ],
  "records": [
    {
      "vatNumber": "IE6388047V",
      "expectedName": "Google Ireland Limited",
      "reference": "CUST-1001"
    }
  ],
  "requesterVatNumber": "DE123456789",
  "defaultCountryCode": "",
  "skipInvalidFormat": true,
  "checkMemberStateAvailability": true,
  "maxConcurrency": 4,
  "maxRetries": 4
}
```

# Actor output Schema

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

No description

## `summary` (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": [
        "IE6388047V",
        "LU26375245",
        "FR40303265045",
        "DE136695976",
        "DE111111111"
    ],
    "records": [
        {
            "vatNumber": "IE6388047V",
            "expectedName": "Google Ireland Limited",
            "reference": "CUST-1001"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("act2labs/vies-vat-verification").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": [
        "IE6388047V",
        "LU26375245",
        "FR40303265045",
        "DE136695976",
        "DE111111111",
    ],
    "records": [{
            "vatNumber": "IE6388047V",
            "expectedName": "Google Ireland Limited",
            "reference": "CUST-1001",
        }],
}

# Run the Actor and wait for it to finish
run = client.actor("act2labs/vies-vat-verification").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": [
    "IE6388047V",
    "LU26375245",
    "FR40303265045",
    "DE136695976",
    "DE111111111"
  ],
  "records": [
    {
      "vatNumber": "IE6388047V",
      "expectedName": "Google Ireland Limited",
      "reference": "CUST-1001"
    }
  ]
}' |
apify call act2labs/vies-vat-verification --silent --output-dataset

```

## MCP server setup

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

```

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/pOgpz2UZ1YwcP6btr/builds/HQ3hdJ2ZCFyQZgBM4/openapi.json
