# EU TIN & VAT Validator (`mikee368/eu-tin-vat-validator`) Actor

Check VAT numbers for every EU country, Northern Ireland, Norway and Switzerland. Personal numbers are format-checked only and are never sent to a register. Existence checks use VIES, Brønnøysund, the Swiss UID register, the Polish VAT white list and the French recherche-entreprises API.

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

## Pricing

$20.00 / 1,000 number checkeds

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

Check **VAT numbers for every EU country, Northern Ireland (XI), Norway and Switzerland**, plus Belgian, Luxembourg, Dutch, German, Polish and French company numbers. **Personal numbers are format-checked only.** Existence checks use **VIES**, **Brønnøysund**, the **Swiss UID** register, the **PL white list** (about 100 searches/day) and **FR recherche-entreprises**.

Each line you submit becomes one dataset row. A bad number or a source that is down does not fail the run. You pay **$0.02** per `number-checked` event, which is one number.

### What does EU TIN & VAT Validator do?

It checks the format and check digit of a tax or company number, then asks an official source whether that number exists when the law allows an existence check.

Personal identifiers stay on the machine that runs the Actor. They are **format-checked only** and are never sent to VIES or a register. Their `existence` value is `not_possible_for_personal_ids`.

Company and VAT numbers can be confirmed against public registers:

- **VIES** (European Commission VAT REST API) for every EU member state and Northern Ireland. Greece is sent as **EL**, which is the VIES code; the row country stays **GR**.
- The **Polish Ministry of Finance VAT white list** (`wl-api.mf.gov.pl`) for a NIP, and for a REGON when the list returns it. The white list allows **about 100 searches/day**. This Actor calls it one number at a time and does not try to get around that limit.
- **FR recherche-entreprises** (`recherche-entreprises.api.gouv.fr`) for a French SIREN or SIRET.
- The Polish KRS open API for a 10-digit KRS number.
- **Brønnøysund** (`data.brreg.no`) for a Norwegian MVA number. It exists only when the organisation is flagged as VAT-registered.
- The Swiss **UID** public service `ValidateVatNumber` for a CHE number. That service allows **20 requests per minute**. This Actor waits between calls and honours `Retry-After`.

This is not the European Commission's TIN-on-the-web form, and it does not confirm that a person exists.

### Why use it?

- One input list covers EU VAT, Norway, Switzerland, and the company and personal formats already supported for Belgium, Luxembourg, the Netherlands, Germany, Poland and France.
- Check digits are computed locally, so an impossible number is rejected before any HTTP call.
- VIES member-state outages become `source_unavailable` on that row. The rest of the list still runs.
- You get a public business name and address when the register returns them. Personal identifiers, bank accounts and representative names from the Polish white list are not copied into the dataset.
- Schedule it, call it from the Apify API, or download the dataset as JSON, CSV, Excel or HTML.

### What does each check return?

| Field | Meaning |
|---|---|
| `normalized` | Canonical number, with a country prefix for VAT |
| `country` | ISO code, or `XI` for Northern Ireland. Greece is `GR` even when the VAT prefix is `EL` |
| `coverageLevel` | Best free coverage in this repo for that country: `full`, `id-only`, `vat-only` or `lei-only` |
| `detectedType` | `individual`, `company` or `ambiguous` |
| `formatValid` / `checkDigitValid` | Local format and check digit. Check digit is null when that country publishes none |
| `existence` | `exists`, `not_found`, `source_unavailable` or `not_possible_for_personal_ids` |
| `source` | Register that answered, or `check-digit` when the number was rejected locally |
| `name` / `address` | Public business name and address, when the source returned them |

### How to check a number

1. Open the Actor and paste one number per line into **Numbers**.
2. Optionally prefix a line with a country (`AT`, `EL` or `GR` for Greece, `XI`, `NO`, `CH`, and the same for the other states) and a type (`individual`, `company` or `auto`).
3. Start the run. Open the dataset when it finishes.

A 9-digit Dutch number is ambiguous: it can be a personal BSN or a company RSIN. With type `individual` or `auto` it is format-checked only. With type `company` it is treated as an RSIN and reported `source_unavailable`, because there is no free RSIN API. A full Dutch VAT number (`NL#########B##`) is sent to VIES.

### How much does a check cost?

Pricing is pay per event. The event name is `number-checked` and the price is **$0.02 per number**. Empty input does not charge. A personal number, an invalid number and a number whose register is down each still count as one checked number, because the Actor did the check and returned a row.

Platform compute is separate from the event price. A short list fits in the default 256 MB run.

### Input

See the **Input** tab for the full schema. `numbers` is a list of strings:

- `PL7740001454`
- `BE 0403170701`
- `NL individual 123456782`

`123456782` above is a synthetic elfproef example, not a real person.

### Output

You can download the dataset in JSON, HTML, CSV or Excel.

```json
{
  "input": "PL7740001454",
  "normalized": "PL7740001454",
  "country": "PL",
  "detectedType": "company",
  "numberKind": "nip",
  "formatValid": true,
  "checkDigitValid": true,
  "existence": "exists",
  "source": "wl-api.mf.gov.pl",
  "name": "ORLEN SPÓŁKA AKCYJNA",
  "address": "CHEMIKÓW 7, 09-411 PŁOCK"
}
```

A personal number looks like this:

```json
{
  "input": "NL individual 123456782",
  "country": "NL",
  "detectedType": "individual",
  "numberKind": "bsn",
  "formatValid": true,
  "checkDigitValid": true,
  "existence": "not_possible_for_personal_ids",
  "source": null
}
```

### Sources and limits

| Number | Format | Existence |
|---|---|---|
| VAT (all EU states and XI) | Local rules. The states outside BE, LU, NL, DE, PL and FR use python-stdnum | VIES. Greece is queried as EL |
| NO MVA | Norwegian MVA check digit | Brønnøysund VAT-register flag |
| CH UID / MWST / TVA / IVA | Swiss UID check digit | UID `ValidateVatNumber`, 20 requests/minute |
| PL NIP | NIP check digit | PL white list, then VIES if the list has no row |
| PL REGON | REGON check digit | PL white list (GUS BIR needs a key, so it is not used) |
| PL KRS | 10 digits starting with 000 | Ministry of Justice KRS open API |
| FR SIREN / SIRET | Luhn | FR recherche-entreprises, then VIES |
| BSN, RSIN without a VAT suffix, Belgian national number, Luxembourg matricule, German IdNr, French SPI, PESEL, Latvian personal code, Spanish DNI/NIE, Romanian CNP | Local rules | Not queried. Format-checked only |

VIES answers such as `MS_UNAVAILABLE` and `MS_MAX_CONCURRENT_REQ` are retried, then stored as `source_unavailable`. The **PL white list** allows **about 100 searches/day**. The Swiss UID service allows **20 requests per minute**.

### Coverage matrix

`coverageLevel` is the best free check this repo can do for that country, not a claim that every field is filled. Iceland and Liechtenstein have format checks only and no free existence API, so a number from those states is not queried.

| Coverage | Countries | What a row can confirm |
|---|---|---|
| `full` | CZ, FI, FR, IE, SK, NO, GB, CH | A national register exists in the company Actor, or (NO, CH) this Actor confirms the VAT flag itself |
| `id-only` | PL, RO | Number lookup only (PL white list / KRS; RO is the company Actor's ANAF register). VAT still goes to VIES |
| `vat-only` | AT, BE, BG, HR, CY, DK, EE, DE, GR, HU, IT, LV, LT, LU, MT, NL, PT, SI, ES, SE, XI | VAT format and VIES existence. No free company register in this repo |
| `lei-only` | IS, LI | No free VAT or company API here |

This Actor does not call HMRC, so a UK VAT number is not existence-checked. `GB` is `full` in the shared list because the company Actor can use Companies House when you bring a free key.

A personal number keeps its country and is still never sent out. Its `existence` is `not_possible_for_personal_ids`.

### FAQ and disclaimer

**Are personal numbers sent to a government API?** No. Personal numbers are format-checked only.

**Which services confirm that a company number exists?** Existence checks use VIES (every EU state and XI), Brønnøysund for Norway, the Swiss UID register (20 requests/minute), the PL white list (about 100 searches/day) and FR recherche-entreprises. Polish KRS numbers use the KRS open API as well.

**What if VIES is down?** That row is `source_unavailable`. Other numbers in the same run continue.

Official registers can name a sole trader. Do not use this Actor to build a file of private individuals. Results can include a business name and address that some laws treat as personal data. Use them only when you have a legitimate reason. Questions and corrections belong on the Actor's **Issues** tab. Programmatic access is on the **API** tab.

# Actor input Schema

## `numbers` (type: `array`):

Tax or company numbers, one per line. VAT for every EU country, Northern Ireland (XI), Norway (NO…MVA) and Switzerland (CHE…MWST). A line may be 'EL094259216', 'DE 136695976', or 'NL individual 123456782' (type is individual, company or auto). Greece accepts EL or GR. Personal IDs are format-checked only and are never sent to a register.

## Actor input object example

```json
{
  "numbers": [
    "PL7740001454",
    "NL individual 123456782"
  ]
}
```

# Actor output Schema

## `dataset` (type: `string`):

One row per submitted number

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

Counts by existence result

# 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 = {
    "numbers": [
        "PL7740001454",
        "NL individual 123456782"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mikee368/eu-tin-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 = { "numbers": [
        "PL7740001454",
        "NL individual 123456782",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("mikee368/eu-tin-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 '{
  "numbers": [
    "PL7740001454",
    "NL individual 123456782"
  ]
}' |
apify call mikee368/eu-tin-vat-validator --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mikee368/eu-tin-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/fvtQce2NjUZPCBYOi/builds/kMDqHxODqhubOAazW/openapi.json
