# EU Company KYB by ID: Auto-Detect FR, NO, PL, UK, FI (`datacraft-studio/eu-company-kyb-router`) Actor

Look up a SIREN, SIRET, Norwegian organisation number, Polish KRS, NIP or REGON, UK company number, or Finnish Y-tunnus. The actor detects the country, checks the checksum, and returns registry KYB data from the official open sources.

- **URL**: https://apify.com/datacraft-studio/eu-company-kyb-router.md
- **Developed by:** [Yahya Nouali](https://apify.com/datacraft-studio) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $5.00 / 1,000 company enricheds

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 KYB by ID

Paste a mixed list of company identifiers. The actor detects the country, checks the checksum, and returns one KYB row from the official registry.

Supported identifiers:

| Country | Identifiers |
| --- | --- |
| France | SIREN (9 digits), SIRET (14 digits) |
| Norway | Organisasjonsnummer (9 digits, starting with 8 or 9) |
| Poland | KRS, NIP (10 digits), REGON (9 or 14 digits) |
| United Kingdom | Companies House company number |
| Finland | Y-tunnus (`0112038-9`) |

Optional prefixes are accepted: `FR`, `NO`, `PL`, `GB`, `UK`, `FI`, plus VAT-shaped forms such as `FR96552100554`, `NO 923609016 MVA`, `PL7740001454`, and `FI01120389`. Spaces and dots are ignored.

A 9-digit number can be a French SIREN, a Norwegian organisation number, or a Polish REGON. The actor keeps the number only when exactly one checksum matches. When more than one matches, set `countryHint` or the row comes back as `ambiguous` and is not charged. The same rule covers a 10-digit NIP that also looks like a KRS, and a 14-digit value that passes both the SIRET and REGON-14 checks.

`vatNumber` is formatted from the identifier (French key plus SIREN, `NO` plus the organisation number plus `MVA`, `PL` plus NIP, `FI` plus eight digits). The United Kingdom row leaves it empty. This actor does not call an EU VAT validation service.

Registry calls run inside this actor. It does not start the country actors, so a lookup is billed once.

### Use cases

- Onboard a counterparty when the only thing you have is a registration number
- Enrich a CRM export that mixes French, Nordic, Polish, and UK identifiers
- Reject bad numbers before they reach a registry
- Watch a portfolio for name, status, address, activity, or officer changes

### Input

| Field | Default | Meaning |
| --- | --- | --- |
| `ids` | — | Up to 10,000 identifiers. An array, or one string split on commas, semicolons, or new lines. |
| `countryHint` | `auto` | `FR`, `NO`, `PL`, `UK`, or `FI` when a number matches more than one checksum. `GB` is accepted as `UK`. |
| `mode` | `enrich` | `enrich` returns every resolved company. `monitor` returns a row only after a real change. |
| `includeOfficers` | `true` | Directors where the registry publishes them. Charged only when at least one person comes back. |
| `monitorKeyValueStoreName` | `eu-company-kyb-snapshots` | Named key-value store for snapshots. Keep the same name on later runs. |
| `maxItems` | `100` | How many identifiers to process in this run. |
| `minRequestIntervalMs` | `300` | Pause between upstream calls. Keep at least 300 so the Finnish open API stays inside its shared budget. |
| `companiesHouseApiKey` | — | Secret. Overrides `COMPANIES_HOUSE_API_KEY`. Required only for UK numbers. It is not written to the dataset. |

```json
{
  "ids": ["552100554", "923609016", "0112038-9", "0000028860", "FR96552100554"],
  "countryHint": "auto",
  "mode": "enrich",
  "includeOfficers": true,
  "maxItems": 100,
  "minRequestIntervalMs": 300
}
```

Samples live in `examples/`.

### Output

Filter `recordType`.

- `company` — a registry hit. `result` is `found`.
- `unmatched` — `result` is `invalid`, `ambiguous`, `not_found`, or `error`. Ambiguous rows include `candidates`.
- `run_summary` — counts, the stop reason, and the prices that applied. The same object is stored as `RUN_SUMMARY` in the default key-value store.

Unified fields: `country`, `id`, `idType`, `normalizedId`, `name`, `legalForm`, `status`, `address`, `activity`, `activityCode`, `registrationDate`, `vatNumber`, `vatRegistered`, `officers`, `source`, `license`, `sourceUrl`, `riskFlags`, `riskSummary`, `result`.

`registry` is the parsed country dossier (France, Norway, Poland, the UK, or Finland). It is not the raw HTTP body. Polish national identification numbers are dropped before the row is stored.

`riskSummary` is a triage label (`CLEAR`, `REVIEW`, or `HIGH`) from registry flags such as cessation, bankruptcy, liquidation, or insolvency. It is not a credit score.

### Pricing

Pay per event. Set the prices in [PUBLISHING.md](PUBLISHING.md) before you sell the actor.

| Event | Price | When |
| --- | ---: | --- |
| `company-enriched` | $0.005 | Enrich mode, and the registry returned the company. This is the primary event. |
| `officers` | $0.002 | `includeOfficers` is on and at least one officer, board member, or person with significant control was returned. |
| `change-detected` | $0.015 | Monitor mode, after the baseline, when a watched field changed or a previously snapshotted company disappeared. |

Not charged: invalid identifiers, ambiguous identifiers, not found, request errors, duplicates in the same run, the monitor baseline, and unchanged monitor checks. Monitor mode does not also charge `company-enriched` or `officers`.

French BODACC announcements are folded into the company row. There is no separate announcement event. Norway annual accounts and subunits are not requested. UK charges, filings, insolvency filings, and Gazette notices are not requested. Insolvency on a UK row comes from the company profile flags.

If the primary charge is refused, the company row is not stored and the run stops. If the officer charge is refused after the company charge succeeded, the row is stored with `officers` empty and a warning, then the run stops.

### Monitor mode

Watched fields: name, legal form, status, address, activity, registration date, VAT number, and the sorted officer list.

1. The first run writes `eu-{country}-{idType}-{id}` into the named key-value store. It does not push a company row and it does not charge.
2. Later runs push a row only when a watched field changed. `monitorObservation` is `changed` and `changes` lists the fields.
3. A company that was snapshotted and later disappears is a charged change.
4. Deleting the key-value store starts a new free baseline.

### What each registry returns

- **France.** Recherche d'entreprises (SIRENE) plus the latest BODACC announcements. Dirigeants are mapped to `officers` when requested. A SIRET is reduced to its SIREN before the call.
- **Norway.** Brønnøysund Enhetsregisteret. Roles are loaded only when `includeOfficers` is on. The row includes the derived `NO…MVA` form and the registry's VAT registration flag.
- **Poland.** KRS open API. A NIP or REGON is resolved through the Ministry of Finance VAT white list, then the KRS extract when a KRS number is present. A sole trader can come back from the white list alone. KRS board names are already masked. There is no GUS BIR key on this actor.
- **United Kingdom.** Companies House company profile. Officers and persons with significant control are loaded only when `includeOfficers` is on. Without a key, that row is an `error` and the rest of the batch continues.
- **Finland.** PRH / YTJ open data. The open feed has no board members, so `officers` stays empty and the officer event is not charged.

Keep `minRequestIntervalMs` at 300 or higher. France's search API allows about 7 requests per second (this actor stays at or under 6, and 4 for BODACC). Companies House allows 600 requests per 5 minutes. The Finnish open API is a shared budget of about 300 requests per minute.

### Integrations: Make, Zapier, n8n

You can call this Actor from no-code tools through Apify's official integrations. Use its Actor ID `55DSTTe1xeZu0GC0r`, and connect each tool with OAuth or your Apify API token. Never put the token in the Actor input.

- **Make**: in the Apify app, add **Run an Actor** and set *Run synchronously* to Yes. Then add **Get Dataset Items** with the run's default dataset ID, and map the rows to Google Sheets, a CRM or Slack. For long runs, use the **Watch Actor Runs** trigger instead.
- **Zapier**: the Apify app has the **Run Actor** action. A synchronous run has a 30-second hard timeout, so for larger lists start the run and use the **Finished Actor Run** trigger with **Fetch Dataset Items**.
- **n8n**: install the official node `@apify/n8n-nodes-apify`. It is a verified community node on n8n Cloud; on self-hosted n8n, add it under Settings > Community Nodes. Use **Run an Actor and Get Dataset**, or the Apify trigger for finished runs.

Example input:

```json
{"mode": "enrich", "ids": ["552100554", "923609016", "0112038-9"], "includeOfficers": true}
```

#### Scheduled monitor to Slack or Sheets

1. Save a monitor input as an Actor task and keep the same store name on every run:

```json
{
  "mode": "monitor",
  "ids": [
    "552100554",
    "923609016",
    "0112038-9"
  ],
  "monitorKeyValueStoreName": "eu-company-kyb-snapshots"
}
```

2. In Apify Console > **Schedules**, run the task on a cron such as `0 7 * * 1` (every Monday at 07:00), with your timezone selected.
3. On the task's **Integrations** tab, add Slack or an HTTP webhook on *Run succeeded*. You can also use Make **Watch Actor Runs**, Zapier **Finished Actor Run** or the n8n Apify trigger.
4. Fetch the dataset items and keep only the change rows: `recordType` is `"company"` and `monitorObservation` is `"changed"`. The first run only stores the baseline and is free.

#### HTTP API

This call runs the Actor and returns the dataset rows in one request. Synchronous runs time out after 300 seconds, so use `/runs` for big lists.

```bash
curl -X POST "https://api.apify.com/v2/acts/55DSTTe1xeZu0GC0r/run-sync-get-dataset-items" \
  -H "Authorization: Bearer <YOUR_APIFY_TOKEN>" \
  -H "Content-Type: application/json" \
  -d '{"mode": "enrich", "ids": ["552100554", "923609016", "0112038-9"], "includeOfficers": true}'
```

### Sources and licences

- **France.** [Recherche d'entreprises](https://recherche-entreprises.api.gouv.fr) and [BODACC](https://www.bodacc.fr). Reused under the **Licence Ouverte 2.0** / Etalab.
- **Norway.** [Enhetsregisteret](https://data.brreg.no/enhetsregisteret/api/docs/index.html), Brønnøysundregistrene. Reused under **NLOD 2.0**.
- **Poland.** KRS Open API, Krajowy Rejestr Sądowy. Attribute Ministerstwo Sprawiedliwości. NIP and REGON resolution uses the VAT white list of Ministerstwo Finansów. The white list is not a CC-licensed dataset.
- **United Kingdom.** [Companies House API](https://developer.company-information.service.gov.uk/). Credit Companies House. Open Government Licence applies where the publisher states it. The public register data has no extra restriction beyond the usual credit requirement and the crown insignia.
- **Finland.** [YTJ open data](https://avoindata.prh.fi/en/ytj/swagger-ui), Finnish Patent and Registration Office (PRH). **CC BY 4.0**. Attribute PRH / Finnish Patent and Registration Office.

This actor is not an official service of those registries.

### Local development

```bash
npm install
npm test
npm run build
npm run smoke
```

`npm test` uses mocked HTTP, including checksum clashes. `npm run smoke` calls the live French, Norwegian, Finnish, and Polish registries for one real id each. UK calls stay in the mocked tests unless you set `COMPANIES_HOUSE_API_KEY` yourself. Do not commit that key.

Input the actor reads from `INPUT` or `./storage/key_value_stores/default/INPUT.json` when you run `npm start`.

### Français

Collez une liste d'identifiants. L'acteur reconnaît le pays, vérifie la clé de contrôle, puis interroge le registre officiel.

Identifiants acceptés : SIREN et SIRET (France), numéro d'organisation (Norvège), KRS, NIP et REGON (Pologne), company number (Royaume-Uni), Y-tunnus (Finlande). Les préfixes `FR`, `NO`, `PL`, `GB`, `UK`, `FI` et les formes de numéro de TVA sont acceptés. Le champ `vatNumber` est calculé à partir de l'identifiant. Aucun service européen de validation de TVA n'est appelé.

Un numéro à 9 chiffres n'est retenu que si une seule clé de contrôle passe (SIREN, numéro norvégien, REGON). S'il en passe plusieurs, indiquez `countryHint` ou la ligne revient `ambiguous`, sans facturation. Les lignes `invalid`, `not_found` et `error` ne sont pas facturées non plus.

| Événement | Prix |
| --- | ---: |
| `company-enriched` | 0,005 $ |
| `officers` | 0,002 $ |
| `change-detected` | 0,015 $ |

Le mode `monitor` enregistre une base dans le key-value store nommé. La première passe et les contrôles inchangés sont gratuits. Seul un changement réel est facturé.

Les dirigeants sont renvoyés pour la France, la Norvège, la Pologne (noms déjà masqués dans le KRS) et le Royaume-Uni lorsque `includeOfficers` est vrai. La donnée ouverte finlandaise ne contient pas de conseil d'administration. Le Royaume-Uni exige une clé Companies House (`companiesHouseApiKey` ou `COMPANIES_HOUSE_API_KEY`). Sans clé, seule la ligne britannique est en erreur.

Licences : France, **Licence Ouverte 2.0** ; Norvège, **NLOD 2.0** ; Finlande, **CC BY 4.0** ; Pologne, réutilisation du KRS avec attribution au Ministerstwo Sprawiedliwości et liste blanche de TVA du Ministerstwo Finansów ; Royaume-Uni, crédit Companies House et Open Government Licence lorsque l'éditeur l'indique.

Gardez `minRequestIntervalMs` à 300 ou plus. L'API ouverte finlandaise est partagée à environ 300 requêtes par minute.

**Intégrations** : l'acteur se branche sur Make (*Run an Actor* + *Get Dataset Items*), Zapier (*Run Actor*, déclencheur *Finished Actor Run*) et n8n (nœud officiel `@apify/n8n-nodes-apify`), ou par l'API `run-sync-get-dataset-items`. Les exemples sont dans la section *Integrations* ci-dessus.

# Changelog

This Actor's version history is a separate document: https://apify.com/datacraft-studio/eu-company-kyb-router/changelog.md

# Actor input Schema

## `ids` (type: `array`):

SIREN or SIRET, Norwegian organisation number, Polish KRS, NIP or REGON, UK company number, or Finnish Y-tunnus. Optional prefixes: FR, NO, PL, GB, UK, FI, or a VAT form such as NO 923609016 MVA, FI01120389, PL plus a NIP. Spaces and dots are ignored.

## `countryHint` (type: `string`):

Used only when a number matches more than one checksum, usually a 9-digit value that could be a French SIREN, a Norwegian organisation number, or a Polish REGON. auto leaves an unresolved clash as an ambiguous row, which is not charged.

## `mode` (type: `string`):

Enrich returns one row per id. Monitor stores a snapshot in the named key-value store and returns a row only when the company changed. The first monitor run is a free baseline. Unchanged companies are free.

## `includeOfficers` (type: `boolean`):

Directors and board members where the registry publishes them: France (dirigeants), Norway (roles), Poland (KRS board, masked), and the UK (officers and PSCs). Finland's open data has no board members. Charged only when at least one officer is returned.

## `monitorKeyValueStoreName` (type: `string`):

Named key-value store for monitor snapshots. Use the same name on later runs. Deleting it starts a new free baseline. Letters, numbers, and hyphens only.

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

Maximum number of identifiers to process in this run.

## `minRequestIntervalMs` (type: `integer`):

Pause between upstream calls. Keep at least 300. The Finnish open API is shared at about 300 requests per minute, and the French search API allows about 7 requests per second.

## `companiesHouseApiKey` (type: `string`):

Optional. Overrides the COMPANIES_HOUSE_API_KEY secret for this run. Required only for UK company numbers. The key is not written to the dataset.

## Actor input object example

```json
{
  "ids": [
    "552100554",
    "923609016",
    "0112038-9",
    "0000028860"
  ],
  "countryHint": "auto",
  "mode": "enrich",
  "includeOfficers": true,
  "monitorKeyValueStoreName": "eu-company-kyb-snapshots",
  "maxItems": 100,
  "minRequestIntervalMs": 300
}
```

# Actor output Schema

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

One company or unmatched row per identifier, then a run_summary row. Invalid, ambiguous, and not-found rows are not charged.

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

Counts for found, not found, ambiguous, baselines, and changes.

# 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 = {
    "ids": [
        "552100554",
        "923609016",
        "0112038-9",
        "0000028860"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datacraft-studio/eu-company-kyb-router").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 = { "ids": [
        "552100554",
        "923609016",
        "0112038-9",
        "0000028860",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("datacraft-studio/eu-company-kyb-router").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 '{
  "ids": [
    "552100554",
    "923609016",
    "0112038-9",
    "0000028860"
  ]
}' |
apify call datacraft-studio/eu-company-kyb-router --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,datacraft-studio/eu-company-kyb-router"
        }
    }
}
```

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/55DSTTe1xeZu0GC0r/builds/BZiuRODh0pB6QDbhs/openapi.json
