# EU Company Register Lookup - VAT Validation & LEI Data (`eu_open_data/eu-company-register-lookup`) Actor

Resolve a company name or registration number against official European business registers, validate its VAT number through VIES, and attach its LEI. One schema for every country, no personal data.

- **URL**: https://apify.com/eu\_open\_data/eu-company-register-lookup.md
- **Developed by:** [joeri munsterman](https://apify.com/eu_open_data) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 company resolveds

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 Company Register Lookup — VAT Validation, LEI & Official Registry Data

Two things in one Actor:

1. **Bulk VAT check for all EU countries.** Paste a list of VAT numbers and get *valid*, *invalid* or *unknown* for each, checked against the European Commission's **VIES** service, with the name VIES has on file.
2. **Company register lookup.** Resolve a company name or registration number against **official European business registers**, with VIES validation and **LEI**, in one schema and with **no personal data**.

### Bulk VAT check: without false "invalid" results

If you sell to businesses in other EU countries, you need your customer's VAT number to be valid in VIES to invoice at 0%. Checking a list sounds easy. It is not, because of how VIES behaves:

- **VIES says `isValid: false` when a member state is merely busy.** It answers HTTP 200 with a `userError` such as `MS_MAX_CONCURRENT_REQ` or `MS_UNAVAILABLE`. A script that only reads `isValid` tells you a healthy customer's number is invalid. When we tested on 21 September 2026, France's VIES server refused 7 of 8 requests this way, even at one request per second, while the VIES status page showed "Available".
- **Every member state runs its own server**, and some refuse as soon as you ask more than one thing at a time.
- **Greece is `EL` in VIES, not `GR`**, and numbers from Norway, the UK or Switzerland are not in VIES at all.

What this Actor does about it:

| | |
|---|---|
| **Four outcomes, never guessed** | `geldig` (valid), `ongeldig` (invalid), `onbekend` (VIES gave no answer, with the reason), `onjuist-formaat` or `buiten-vies` |
| **Retries that do not stall your list** | Every number is asked once first; only the unknown ones are retried at the end, in rounds with pauses, so an overloaded country can recover |
| **Pay only for answers** | Only valid or invalid results are billed. Unknown, malformed and non-EU numbers are free |
| **Your order kept** | Every row has `positie`, its position in your input; duplicates are checked once |
| **Messy input accepted** | `nl 8104.33941.b01` is read as `NL810433941B01`; `GR` becomes `EL` |

Example row:

```json
{
  "type": "btw-controle",
  "positie": 1,
  "invoer": "nl 8104.33941.b01",
  "btwNummer": "NL810433941B01",
  "land": "NL",
  "resultaat": "geldig",
  "naamVolgensVies": "COOLBLUE B.V.",
  "gecontroleerdOp": "2026-09-21T21:30:48.859Z",
  "bron": "VIES (Europese Commissie)"
}
```

An unknown one tells you why, so you can simply run those numbers again later:

```json
{
  "positie": 3,
  "btwNummer": "FR14652014051",
  "resultaat": "onbekend",
  "reden": "VIES gaf geen uitsluitsel (MS_MAX_CONCURRENT_REQ). Probeer het later opnieuw."
}
```

Up to 1,000 numbers per run. Expect roughly 1 to 3 numbers per second depending on the countries involved; raise the run timeout for long lists. Results are written in blocks, so if a run does hit its timeout, everything already checked is kept.

### Company register lookup

| | |
|---|---|
| **Countries** | United Kingdom (Companies House), France (RNE/SIRENE), Poland (KRS), Norway (Brreg), Finland (PRH/YTJ) |
| **No keys needed** | UK data comes through the official Companies House API; you do not need your own API key |
| **Search by name or number** | Company name, or a SIREN, UK company number, Polish KRS number, Norwegian org. number or Finnish business ID. Poland's KRS can only be searched by number |
| **One schema** | Same field names and ISO dates for every country |
| **VAT** | Derived where the register omits it, then verified against the European Commission's VIES service |
| **LEI** | Matched through GLEIF, with the match method included so you can judge confidence |
| **Ranked** | Every row carries a `relevantie` score; near-miss names are filtered out, best match first |
| **No personal data** | Directors, owners, sole traders, phone numbers and e-mail addresses are removed at the source |

### Example output

```json
{
  "land": "NO",
  "naam": "EQUINOR ASA",
  "registerId": "923609016",
  "registerNaam": "Enhetsregisteret (Brreg)",
  "rechtsvormOmschrijving": "Allmennaksjeselskap",
  "status": "actief",
  "opgerichtOp": "1972-09-18",
  "sbiAchtigeCode": "06.100",
  "adres": { "straat": "Forusbeen 50", "postcode": "4035", "plaats": "STAVANGER", "land": "NO" },
  "aantalMedewerkers": 21272,
  "btwNummer": "NO923609016MVA",
  "btwGeldig": null,
  "lei": "OW6OFBNCKXC4US5C7523",
  "leiMatchWijze": "registratienummer",
  "relevantie": 1,
  "bronUrl": "https://data.brreg.no/enhetsregisteret/api/enheter/923609016"
}
```

### Three details that matter

**`btwGeldig` is `true`, `false` or `null` — and `null` means unknown.** VIES answers with HTTP 200 and `isValid: false` even when the member state is merely busy or offline. Treating that as "invalid" would tell you a healthy VAT number is bad. This Actor only reports `false` when VIES actually says `INVALID`, and returns `null` otherwise. When a number is valid, you also get the name registered with the tax authority in `btwNaam`.

**`leiMatchWijze` tells you how the LEI was found.** `registratienummer` means GLEIF's own record carries the same national ID — a firm match. `naam` means the legal name matched exactly, there was exactly one candidate in that jurisdiction, and no conflicting registration number. Two companies sharing a trading name will not be given the same LEI.

**Sole traders are excluded by design.** A one-person business is a natural person, so its registry entry is personal data under the GDPR. French `personne physique` entries, Norwegian ENK and Finnish *yksityinen elinkeinonharjoittaja* are filtered out, along with director lists, e-mail addresses and phone numbers. That makes this output usable for company screening, not for contacting individuals.

### Pricing

Pay per event, so you pay for results rather than runtime:

| Event | Price |
|---|---|
| `vat-validated`: one VAT number with a definitive valid or invalid answer from VIES, in the bulk check or the register lookup | $0.005 |
| `company-resolved`: one company found in an official register | $0.02 |
| `lei-matched`: only when a LEI is actually attached | $0.01 |

So checking 1,000 VAT numbers costs at most $5, and less if some come back unknown.

### Sources and attribution

All data comes from official, openly reusable sources:

- United Kingdom — [Companies House API](https://developer.company-information.service.gov.uk/). Contains public sector information licensed under the [Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/). Officers are not retrieved.
- Poland — [KRS API, Ministry of Justice](https://prs.ms.gov.pl/krs/openApi). Only the company section of the extract is used; the section listing board members and proxies is never read.
- France — [recherche-entreprises.api.gouv.fr](https://recherche-entreprises.api.gouv.fr) (INSEE/RNE)
- Norway — [Brønnøysundregistrene](https://data.brreg.no/enhetsregisteret/api/dokumentasjon) (NLOD)
- Finland — [PRH open data](https://avoindata.prh.fi/ytj_en.html)
- VAT — [European Commission VIES](https://ec.europa.eu/taxation_customs/vies/)
- LEI — [GLEIF](https://www.gleif.org/en/lei-data/gleif-api) (CC0)

Registry data reflects what the source publishes; this Actor does not correct or supplement it.

### Limits

- Name search follows each register's own matching, then re-ranks. Very short or very generic terms return broad results; raise `minimaleRelevantie` to tighten them.
- Norway and the UK are outside the EU, so VIES cannot validate their VAT numbers. UK VAT numbers are not in Companies House either, so that field stays empty for UK companies.
- Polish VAT numbers are taken from the NIP in the KRS extract and validated through VIES.
- A register can be briefly unavailable. The run continues on the remaining countries and reports which one failed, rather than failing entirely.

Found a wrong field or want another country? Open an issue on the Actor page.

# Actor input Schema

## `btwNummers` (type: `array`):

One VAT number per line, from any EU member state or Northern Ireland, e.g. NL810433941B01 or DE811907980. Spaces, dots and lowercase are fine; GR is converted to EL. When you fill this in, the register search below is skipped. Up to 1,000 numbers per run; allow roughly 1 to 3 numbers per second, and raise the run timeout for long lists. Only definitive answers (valid or invalid) are billed.

## `zoekterm` (type: `string`):

Name, or a national registration number (SIREN, Norwegian org. number, Finnish business ID, UK company number, Polish KRS number), to look up across the selected registers. Used only when the VAT list above is empty.

## `landen` (type: `array`):

Which official registers to query. Poland's KRS can only be searched by KRS number, not by name.

## `maxPerLand` (type: `integer`):

Upper bound per register. Also caps the cost, since billing is per resolved company.

## `btwControle` (type: `boolean`):

Check each EU VAT number against the European Commission's VIES service.

## `leiKoppeling` (type: `boolean`):

Attach the global LEI identifier where one exists.

## Actor input object example

```json
{
  "zoekterm": "equinor",
  "landen": [
    "FR",
    "NO",
    "FI",
    "GB",
    "PL"
  ],
  "maxPerLand": 10,
  "btwControle": true,
  "leiKoppeling": true
}
```

# Actor output Schema

## `companies` (type: `string`):

All resolved companies, best match first. Each row has country, registry ID, name, legal form, status, address, VAT number and validity, LEI and a relevance score.

## `overview` (type: `string`):

The same companies with only the key columns: country, name, registry ID, legal form, status, VAT and LEI.

# 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 = {
    "zoekterm": "equinor"
};

// Run the Actor and wait for it to finish
const run = await client.actor("eu_open_data/eu-company-register-lookup").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 = { "zoekterm": "equinor" }

# Run the Actor and wait for it to finish
run = client.actor("eu_open_data/eu-company-register-lookup").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 '{
  "zoekterm": "equinor"
}' |
apify call eu_open_data/eu-company-register-lookup --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,eu_open_data/eu-company-register-lookup"
        }
    }
}
```

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/cnA69vcmUubtGvmSV/builds/Dhjha1J3lztTBuEIf/openapi.json
