# Company Registry Search — Companies House, SIRENE +9 (KYB) (`shelvick/global-company-registry-search`) Actor

Search official company registers in 11 countries by company name or registration number — Companies House, Annuaire des Entreprises/SIRENE, Brønnøysund, PRH, Zefix, ARES, KRS, ABR, ACRA and more — plus GLEIF LEI. One normalized, source-cited KYB record per entity. No API key.

- **URL**: https://apify.com/shelvick/global-company-registry-search.md
- **Developed by:** [Scott Helvick](https://apify.com/shelvick) (community)
- **Categories:** Developer tools, AI
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $8.50 / 1,000 entity record delivereds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## Company Registry Search — Companies House, SIRENE +9 (KYB)

Verifying a counterparty in more than one country means a different official
register, identifier format and response shape for each one, most of them answering
in their own language. This Actor searches the official company registers of 11
countries by company name **or** registration number, in parallel, and returns one
normalized record per matched entity — same field names whatever country it came
from, plus the URL of the official register entry it was read from.

### What this does

- **Company registry search by name or by registration number.** Pass a company
  name or any covered register's own primary identifier (see the Input table). Each
  country decides independently whether the query looks like one of *its* numbers;
  if not, it name-searches there — a nine-digit query is a French SIREN, a Norwegian
  orgnr and an Australian ACN at once, so per-register detection is the only form of
  the question with an answer.
- **Pass a list, not just one name.** `queries` takes up to 100 names and/or
  registration numbers, each fanned out over the same countries — one run for a whole
  supplier, vendor or portfolio list, with every row carrying the `query` it came from
  so the results split back apart.
- **One normalized record per entity**, whatever the source: legal name and
  registration number, status verbatim *and* mapped to `active` / `inactive` /
  `unknown`, legal form, incorporation date, registered address as one line plus
  components, activity codes carrying the source's own scheme name (NAF, NACE,
  CZ-NACE, PKD, TOL2008), officers where published, GLEIF LEI, and `source_url` —
  a citation on every record.
- **Officers / directors** for the three registers that publish them under an open
  licence: Companies House (officers page), Annuaire des Entreprises
  (*dirigeants*) and Brønnøysundregistrene (*roller*). Everywhere else `officers`
  is `[]` with an `officers_note` saying why, so a record never looks truncated.
- **GLEIF LEI enrichment** on every matched entity, joined on registration number
  and falling back to legal name plus country. A LEI is attached only on a
  confident single match, and enrichment never delays the registry record.
- **Explained misses.** Every country that produced no entity still returns a row
  with a machine-routable `outcome` and a plain-language `message`. None of them is
  charged, and the run never ends FAILED.
- **No API key, no account, no signup.** Every source is a keyless public official
  register, read over plain HTTP requests — no browser, no data broker in the path.
- **KYB and due-diligence use cases:** verify a cross-border supplier before
  onboarding; resolve a legal name and number to a LEI for sanctions-adjacent
  screening prep; check that a subsidiary named in a contract exists in its stated
  jurisdiction; enrich a vendor-master record with the registered entity and its
  official address.

#### Coverage

| Country | Official register | Name search | Number lookup | Officers |
|---|---|---|---|---|
| GB | Companies House | yes | company number | yes |
| FR | Annuaire des Entreprises (SIRENE / INPI data) | yes | SIREN / SIRET | yes |
| NO | Brønnøysundregistrene — Enhetsregisteret | yes | organisasjonsnummer | yes |
| FI | Finnish Patent and Registration Office (PRH / YTJ) | yes | business ID | — |
| CH | Zefix — Swiss Central Business Name Index | yes | UID (CHE…) | — |
| EE | e-Business Register | yes | registry code | — |
| LV | Uzņēmumu reģistrs (Latvian Register of Enterprises) | yes | regcode | — |
| CZ | ARES — Czech Business Register | yes | IČO | — |
| PL | KRS — National Court Register | **no — number only** | KRS number | — |
| AU | Australian Business Register (ABN Lookup) | yes | ABN / ACN | — |
| SG | ACRA | yes | UEN | — |
| LT | Registrų centras | **deferred** | deferred | — |

Plus the **GLEIF Global LEI Index** for LEI enrichment on every matched record.

Three notes the rows carry themselves: Zefix's open dataset lists
currently-registered entities only, so a dissolved Swiss company will not appear;
the ACRA dataset is a monthly snapshot; the Australian Business Register publishes
ABN-level details, narrower than a full company record, and no incorporation date.
Lithuania is recognised but not searchable in this version — its public search now
sits behind an interactive browser challenge — and returns a free row saying so
rather than being dropped from the coverage list.

### Why one normalized schema over primary sources matters

The single-country lookup is not the hard part. The hard part is that a
cross-border KYB question — *is this company real, in this jurisdiction, under this
name, with this number?* — means integrating a different official source per
country, each with its own identifier grammar, its own status vocabulary in its own
language, and its own idea of what an address is: a separate thing to build, monitor
and re-fix every time one of them moves.

The alternative most tooling reaches for is a commercial aggregator: one API, one
schema, no per-country work. The cost is that you are no longer reading the
register — you are reading somebody's copy of it, on their refresh cycle, with no
way to show an auditor where a field came from. A diligence file wants a citation,
not a vendor's assertion.

So this Actor reads the primary sources directly and pays the normalization cost
once, so the caller never does. `status` is kept verbatim *and* mapped, because
comparing entities across countries needs the mapping and defending a conclusion
needs the original. And where a register genuinely doesn't publish something — a
Swiss status field, an Australian incorporation date, officers outside three
countries — the record says so in a note instead of returning a bare null and
leaving you to guess whether the company or the pipeline was empty.

### How it compares to other ways of getting this data

| Approach | Countries per call | Reads the official register | Normalized across countries | Citation per record | Key / account needed |
|---|---|---|---|---|---|
| Single-country registry scraper | 1 | yes | n/a | sometimes | sometimes |
| Thin wrapper over 2–4 registry APIs | 2–4 | yes | partly | rarely | sometimes |
| Commercial company-data aggregator | many | no — a licensed copy | yes | no | yes, paid |
| **This Actor** | **up to 12 + LEI** | **yes** | **yes, one schema** | **yes, every record** | **no** |

The bet: breadth *under one schema, over primary sources, with citations* is the
thing that is actually missing. Any one of those three alone already exists.

### Input

| Field | Type | Required | Default | Description |
|---|---|---|---|---|
| `query` | string | one of `query` / `queries` | — | A company name (e.g. `Carrefour`) **or** an official registration number (e.g. `00445790`, `652014051`, `923609016`, `0112038-9`, `CHE-105.909.036`, `45274649`, `0000028860`, `88000014675`, `196800306E`). Numbers are matched exactly; names are matched as the register matches them. |
| `queries` | array of strings | one of `query` / `queries` | — | A list of names and/or registration numbers to look up in one run — a whole supplier or portfolio list in a single call. Up to 100 entries, 200 characters each. Give this, `query`, or both: they are merged, `query` first, and near-duplicates (case, accent, punctuation) are searched once. Each entry fans out over the same `countries`, and every row carries its own `query`. |
| `countries` | array of strings | | all covered | ISO-3166 alpha-2 codes, e.g. `["GB","FR","NO"]`. Empty searches every covered country. An unrecognised code returns one free `not_covered` row naming it and never fails the run. |
| `maxRecordsPerCountry` | integer | | `10` | Upper bound on matched entities returned *per country*, 1–50. Each delivered entity record is billed once; rows reporting no match, no coverage, a failure or a rate limit are always free. |
| `includeOfficers` | boolean | | `true` | Include officers for the three registers that publish them openly (GB, FR, NO). Only the name, role and appointment date the register itself publishes are returned — never dates of birth, phone numbers or email addresses. |
| `includeLei` | boolean | | `true` | Enrich each matched entity with its GLEIF LEI when the registration number, or the legal name plus country, gives a confident match. No confident match leaves `lei` null. |

### Output

One dataset record per matched entity. Real record, from a name search scoped to
Norway:

```json
{
  "outcome": "match",
  "query": "Equinor",
  "country": "NO",
  "registry": "Brønnøysundregistrene — Enhetsregisteret (Norway)",
  "legal_name": "EQUINOR ASA",
  "registration_number": "923609016",
  "status": "Registrert",
  "status_normalized": "active",
  "legal_form": "Allmennaksjeselskap",
  "incorporation_date": "1972-09-18",
  "registered_address": "Forusbeen 50, 4035, STAVANGER, Norge",
  "address_components": {
    "address": "Forusbeen 50",
    "postal_code": "4035",
    "city": "STAVANGER",
    "municipality": "STAVANGER",
    "country": "Norge"
  },
  "activity_codes": [
    { "scheme": "NACE", "code": "06.100", "label": "Utvinning av råolje" },
    { "scheme": "NACE", "code": "06.200", "label": "Utvinning av naturgass" }
  ],
  "officers": [
    { "name": "Anders Opedal", "role": "Daglig leder" },
    { "name": "Jarle Kjell Roth", "role": "Styrets leder" }
  ],
  "officers_note": null,
  "lei": "OW6OFBNCKXC4US5C7523",
  "source_url": "https://virksomhet.brreg.no/nb/oppslag/enheter/923609016",
  "retrieved_at": "2026-09-18T09:41:02Z",
  "match_type": "name",
  "message": null
}
```

A country that produced nothing returns a row in the same schema, with
`outcome` != `match`, `message` filled and every entity field null:

```json
{
  "outcome": "not_covered",
  "query": "Equinor",
  "country": "PL",
  "registry": "KRS — Polish National Court Register",
  "legal_name": null,
  "message": "Poland's National Court Register (KRS) is searchable here by KRS number only: its open API exposes entity lookups by number, and its name-search front end is not reachable without an interactive browser session. Supply the 10-digit KRS number (for example 0000028860) to get the full Polish record. Nothing was charged for this row."
}
```

`outcome` is the field to branch on: `match` (the only billable kind), `no_match`,
`not_covered`, `failed`, `rate_limited`, `invalid_input`, `internal_error`. Beside it,
`retry` says what to do about a non-delivery in one value — `later`, `with_changes`, or
`never`, and `null` on a `match`. The run
also writes two key-value-store records — `OUTPUT` with the per-outcome counts, and
`COVERAGE` with the live coverage manifest (which countries this build searches,
which publish officers, each register's caveat) — so a caller can read current
coverage instead of trusting a document.

### Example

```json
{
  "query": "Equinor",
  "countries": ["NO", "GB"],
  "maxRecordsPerCountry": 5
}
```

```bash
curl -X POST "https://api.apify.com/v2/acts/shelvick~global-company-registry-search/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"query":"Equinor","countries":["NO","GB"],"maxRecordsPerCountry":5}'
```

```python
from apify_client import ApifyClient

client = ApifyClient("<APIFY_TOKEN>")
run = client.actor("shelvick/global-company-registry-search").call(
    run_input={"query": "Equinor", "countries": ["NO", "GB"], "maxRecordsPerCountry": 5}
)
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    if row["outcome"] == "match":
        print(row["country"], row["legal_name"], row["registration_number"], row["lei"], row["source_url"])
    else:
        print(row["country"], row["outcome"], row["message"])
```

### Calling from an AI agent

Agents are the primary caller: five input fields, one required, every schema
description written to be constructed against without reading this page.

- **Apify MCP server (`mcp.apify.com`)** — expose
  `shelvick/global-company-registry-search` as a tool; the input schema and its
  per-field descriptions are advertised to the model, and the `outcome` + `message`
  contract lets the agent tell "this company does not exist in Finland" from
  "Finland could not be reached" without parsing prose. Pay per call via x402 USDC
  on Base or Skyfire managed tokens.
- **Apify Python SDK** — the snippet above, then iterate the dataset.
- **REST API** — `POST /v2/acts/shelvick~global-company-registry-search/run-sync-get-dataset-items`
  for a synchronous call (a full all-countries query finishes well inside the sync
  window), or the async `/runs` endpoint plus polling.

### Pricing

Pay-per-event, and the event is a delivered record: one charge per matched entity
record, pushed to the dataset *before* it is charged. Rows reporting no match, no
coverage, a source failure, a rate limit or an unusable request are never charged —
a query that finds a company in two countries out of eleven costs two records, not
eleven lookups. A platform Actor-start event is amortized across the whole query,
and `maxRecordsPerCountry` caps what one broad name search can bill.

See the **Pricing** tab on this Store page for the current per-event rates and any
active subscriber discounts.

### Behavior

**Run-level failures.** Input validation is the only run-level check, and an
unusable input becomes one free `invalid_input` row on a SUCCEEDED run, naming the
field and the fix. An unhandled fault likewise becomes one free `internal_error`
row. The run never ends FAILED — an agent caller gets an answer, not an exception.

**Per-country outcomes**, all free and all carrying `message`: `no_match` (searched,
nothing matched, with that register's caveat appended where it has one),
`not_covered` (an unrecognised code, Lithuania, or a name query sent to Poland),
`failed` (the register could not be reached or read — retryable) and `rate_limited`
(the register's own published request limit was reached — a 429 fails that country
fast and is not retried in-run, while every other country still delivers).

**Latency.** Countries are searched concurrently, each bounded at about 45 seconds
independent of the run timeout, so one slow register cannot hold a finished query
hostage. A full all-countries query typically completes in 35–40 seconds. Records are
delivered as each country finishes, not in the order you listed them — sort on
`country` if you need a fixed order.

**Telemetry:** to improve coverage and reliability, this Actor reports anonymous
usage metrics and diagnostic events to the developer — the shape of the request
(how many countries, which flags were set), the per-country outcome funnel
(matched / no-match / failed / rate-limited counts), which normalized fields the
delivered records actually carried, whether each match came from a number or a
name, and how long the run took — and, only when something goes wrong, the query
that triggered it. No account identifiers are collected, and telemetry never
affects a run.

### FAQ

**Am I charged when a country finds nothing?**
No. Only `outcome: "match"` rows bill, and they are pushed to the dataset before
they are charged. A run where every country comes back empty costs nothing beyond
the Actor-start event.

**Why is Poland number-only, and Lithuania not searched at all?**
Poland's KRS serves entity lookups by KRS number, but its name-search front end is
not reachable without an interactive browser session, so a Polish name query returns
a free row telling you to supply the number. Lithuania's public search moved behind
an interactive browser challenge this version does not use; it stays in the coverage
list with a free row explaining itself, because "we cannot search this yet" and
"your country code was wrong" are different answers.

**Why do most countries return an empty officers list?**
Because their register does not publish officer names under its open-data licence.
Officers come back only from Companies House, Annuaire des Entreprises and
Brønnøysundregistrene, and even there only the name, role and appointment date the
register publishes. Every empty list carries an `officers_note` saying which case
it is.

**Is the data live?**
Every lookup hits the register at request time, so records are as current as that
register publishes — except for the two documented cases above: the Singapore
dataset is a monthly snapshot, and the Swiss dataset lists currently-registered
entities only.

### What this doesn't do

- **No financial statements, filings or documents.** The entity record — identity,
  status, form, address, activity, officers — not accounts, annual returns or
  filing images.
- **No beneficial-ownership or shareholder data.** Officers as the register
  publishes them, in three countries; nothing about ownership chains.
- **No personal contact or identity data, ever.** No dates of birth, phone numbers
  or email addresses, even where a source exposes them.
- **No monitoring.** A run answers a point-in-time question; it does not watch an
  entity for status changes.
- **Not every country.** 11 searchable today (Lithuania listed but deferred); anything outside it returns
  an explicit, free `not_covered` row rather than an empty result.

Use a filings-and-financials source for accounts and annual returns, an
ownership-data provider for shareholder and beneficial-ownership chains, and a
sanctions- or watchlist-screening service for the screening decision itself — this
Actor produces the verified entity identity and LEI those steps take as input. For
US companies use the sibling Actor below; no US register is in this coverage list.

Records come from official public registers as published, for lawful business,
compliance and diligence use. Registers lag recent filings and occasionally carry
clerk-side errors, so verify against `source_url` before a regulatory decision
rests on a field.

### Related Actors

| Actor | Use it when |
|---|---|
| [Secretary of State Business Entity Search + UCC Liens](https://apify.com/shelvick/business-entity-lien-search) | the company is a US entity — you need state Secretary of State registration plus the UCC liens filed against it |

# Actor input Schema

## `query` (type: `string`):

A company name ("Carrefour") OR an official registration number (UK 00445790, French SIREN 652014051, Norwegian orgnr 923609016, Finnish 0112038-9, Swiss CHE-105.909.036, Czech IČO 45274649, Polish KRS 0000028860, Australian ABN 88000014675, Singapore UEN 196800306E). Each country decides independently whether the query is one of ITS numbers; if not it name-searches there. At least one of `query` / `queries` is required — use `queries` for a whole list in one run.

## `queries` (type: `array`):

A list of company names and/or registration numbers to look up in one run — one call for a whole supplier or portfolio list. Each entry is searched across the same `countries` as a single query would be, and every row carries the `query` it came from so results split back apart. Up to 100 entries, 200 characters each. Give this, `query`, or both: they are merged and near-duplicates searched once. Each delivered record is billed once.

## `countries` (type: `array`):

ISO-3166 alpha-2 country codes to search, e.g. \["GB","FR","NO"]. Leave empty to search every covered country. Covered: GB (Companies House), FR (Annuaire des Entreprises), NO (Brønnøysund), FI (PRH), CH (Zefix, currently-registered only), EE (e-Business Register), LV (Uzņēmumu reģistrs), CZ (ARES), PL (KRS, registration-number lookup only), AU (ABN Lookup), SG (ACRA, monthly snapshot). LT is recognised but not searchable yet. Other codes are reported per-row as not\_covered; the run never fails.

## `maxRecordsPerCountry` (type: `integer`):

Upper bound on matched entities returned per country (1-50). Each delivered entity record is billed once; rows that report no match, no coverage, a source failure or a rate limit are always free.

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

When true (default), officers are included for the three registers that publish them under an open licence: GB (Companies House officers page), FR (dirigeants) and NO (roller). Every other country returns an empty officers list plus an officers\_note saying why. Only the name, role and appointment date the register itself publishes are returned — never dates of birth, phone numbers or email addresses.

## `includeLei` (type: `boolean`):

When true (default), each matched entity is enriched with its Legal Entity Identifier from the GLEIF public LEI index when the registration number, or the legal name plus country, gives a confident match. No confident match leaves lei null; it never blocks or delays the registry record.

## Actor input object example

```json
{
  "query": "Carrefour",
  "queries": [],
  "countries": [
    "GB",
    "FR",
    "NO"
  ],
  "maxRecordsPerCountry": 10,
  "includeOfficers": true,
  "includeLei": true
}
```

# Actor output Schema

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

Dataset items for this run: one normalized record per matched entity, plus one explained row per country that returned nothing.

## `coverage` (type: `string`):

Which countries this build searches, and what each register does and does not publish.

# 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 = {
    "query": "Carrefour",
    "queries": [],
    "countries": [
        "GB",
        "FR",
        "NO"
    ],
    "maxRecordsPerCountry": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("shelvick/global-company-registry-search").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 = {
    "query": "Carrefour",
    "queries": [],
    "countries": [
        "GB",
        "FR",
        "NO",
    ],
    "maxRecordsPerCountry": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("shelvick/global-company-registry-search").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 '{
  "query": "Carrefour",
  "queries": [],
  "countries": [
    "GB",
    "FR",
    "NO"
  ],
  "maxRecordsPerCountry": 10
}' |
apify call shelvick/global-company-registry-search --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,shelvick/global-company-registry-search"
        }
    }
}
```

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/MroRdd0EVZkrdhGzc/builds/eX6sAPIMgoOVa5yvt/openapi.json
