# LEI Lookup & Company Name Matcher — with Match Score (`mouadapi/company-id-matcher`) Actor

Matches messy company names to their LEI record at GLEIF and returns the legal name, country, status and a 0-100 match score with its reason; names with no clear match, and failed rows, are never charged. Not affiliated with or endorsed by GLEIF.

- **URL**: https://apify.com/mouadapi/company-id-matcher.md
- **Developed by:** [COMPASS DEV](https://apify.com/mouadapi) (community)
- **Categories:** Business, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.00 / 1,000 company matcheds

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

Matches messy company names to their LEI record at GLEIF and returns the legal name, country, status and a 0-100 match score with its reason; names with no clear match, and failed rows, are never charged.

Give company names the way they come out of a CRM, an invoice file or a supplier list ("Siemens Aktiengesellschaft",
"icu medical inc", "Deutsche Bank AG | DE") or LEIs, and get, for each one, its Legal Entity Identifier, the legal name and
country on the record, the entity and registration status, a match score and the reason for it, plus the closest
alternatives. Built for supplier onboarding, KYB, data cleaning and AI agents that need the official identifier of a
company. *Not affiliated with or endorsed by GLEIF.*

### What it does

- **Matching that tells you how sure it is:** every candidate gets a score from 0 to 100:
  - **100**: the same name (case, accents, punctuation and how the legal form is spelled don't matter: "ICU Medical, Inc." = "icu medical inc").
  - **92**: the same name without its legal form ("ICU Medical" for "ICU Medical, Inc.").
  - **85 or less**: the share of telling words both names have, so an extra word ("Branch", a city) lowers it.
  - A country you gave that differs from the record's costs 30 points; an inactive entity costs 5.
- **No false matches:** a name matches only when its best candidate reaches `minScore` (80 by default) and no other
  candidate scores within 3 points. Otherwise the row is a **free `no_data` row** that lists the candidates and their
  scores (`below_min_score` or `ambiguous`). A branch or a foundation of a company is not the company.
- **Finds the company however its form is written:** GLEIF's search needs every word as written on the record, so each name
  gets up to three searches (without its legal form, as typed, with the form written out: "AG" → "Aktiengesellschaft"),
  and a two-word search when all three find nothing.
- **LEIs too:** an entry that is an LEI (check digits tested) is looked up directly (score 100, "LEI given").
- **Watch mode** (give a watch list name in `stateName`): returns only names whose match changed since the last run (another
  LEI, a new legal name, a status change such as ISSUED → LAPSED). A watch run with nothing new returns **exactly one free
  `no_data` row** that says so.
- **No people:** organisation records only. A record that is, may be, or names a person (sole proprietors, legal forms off
  GLEIF's organisation list, a person's name in a name or address field) is never matched or listed, only counted
  (`peopleLeftOut`). A person's name given as an entry is refused before any search.
- **You are never charged for failed results:** failed, no_data and unchanged rows are free.

### Quick start

```json
{ "companies": ["Siemens Aktiengesellschaft", "icu medical inc", "7LTWFZYICNSX8D621K86", "Deutsche Bank AG | DE"] }
```

Only in one country, with a stricter threshold:

```json
{ "companies": ["Siemens", "BASF", "Bayer"], "country": "DE", "minScore": 90 }
```

Watch your supplier list for changes every week (with an Apify schedule):

```json
{ "companies": ["ICU Medical, Inc. | US", "Baxter International Inc."], "stateName": "suppliers" }
```

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `companies` | list of names or LEIs | — (prefilled with 4 examples) | One per line; a name may end with " | CC" (a country code). Never a URL; a person's name is refused |
| `country` | two-letter code | — | Look only in this country for every name without its own code |
| `minScore` | integer 50–100 | `80` | The lowest score that counts as a match |
| `maxAlternatives` | integer 0–10 | `3` | Other candidates listed on each row, with their scores |
| `mode` | `watch` or `export` | — | Empty: watch when `stateName` is given, otherwise export. An explicit mode wins |
| `stateName` | string | — | Watch list name. Watch mode without a name uses the list `default` |
| `maxItems` | integer 1–10,000 | `1000` | Most charged matches per run; in watch mode the rest come in the next run |

Changing `minScore` starts a new watch baseline.

### Output

```json
{
    "status": "ok",
    "attempts": 1,
    "error": null,
    "input": "icu medical inc",
    "mode": "export",
    "watchList": null,
    "noDataReason": null,
    "query": "icu medical",
    "countryHint": null,
    "lei": "5493003NRW7LS2QZRH60",
    "legalName": "ICU MEDICAL, INC.",
    "otherNames": [],
    "legalCountry": "US",
    "legalForm": "XTIQ",
    "entityStatus": "ACTIVE",
    "registrationStatus": "ISSUED",
    "nextRenewalDate": "2026-12-02",
    "matchScore": 100,
    "matchReason": "same legal name (case, punctuation and legal-form spelling ignored)",
    "ambiguous": false,
    "alternatives": ["254900891YA8DKEHTV75 ICU Medical B.V. (NL): 92", "254900DVR7A372V5E361 ICU MEDICAL INDIA LLP (IN): 68"],
    "candidatesFound": 3,
    "peopleLeftOut": 2,
    "changeType": null,
    "changedFields": null,
    "previousValues": null,
    "url": "https://search.gleif.org/#/record/5493003NRW7LS2QZRH60",
    "source": "GLEIF API (Global Legal Entity Identifier Foundation). Not affiliated with or endorsed by GLEIF.",
    "license": "CC0 1.0 (GLEIF LEI Data Terms of Use)",
    "licenseUrl": "https://www.gleif.org/en/meta/lei-data-terms-of-use",
    "scrapedAt": "2026-10-06T22:00:00.000Z"
}
```

| Status | Meaning | Charged? |
|---|---|---|
| `ok` | The name matched an LEI record (or the LEI was found) | Export: yes. Watch: `baseline`, `new` and `changed` yes; unchanged names no |
| `no_data` | No candidate, none at `minScore` (`below_min_score`), several alike (`ambiguous`), no such LEI, or nothing new since the last run | No |
| `failed` | An invalid entry (a URL, a person's name, an LEI with wrong check digits) or no answer from GLEIF (`error` says why) | No |

The key-value store holds `RUN_REPORT` (counts, charged and free rows, the GLEIF data set's date, stop reason).

### Output fields

| Field | Type | Description |
|---|---|---|
| `status` | string (or null) | ok = a name matched to an LEI record (charged in export mode, and in watch mode when it is a baseline, new or changed match); no_data = no candidate, none at minScore, several alike, or nothing new since the last run (free); failed = an invalid entry (a URL, a person's name, a wrong LEI) or no answer (free) |
| `attempts` | integer (or null) | Requests made for this row (retries included) |
| `error` | string (or null) | Why a row is no_data or failed; null on ok rows |
| `input` | string (or null) | The entry as you gave it |
| `mode` | string (or null) | watch or export |
| `watchList` | string (or null) | Watch list name (watch mode); null in export mode |
| `noDataReason` | string (or null) | no_candidate (no record has these words), below_min_score, ambiguous (several score alike), not_found (no LEI record, or one left out), nothing_new (watch); null otherwise |
| `query` | string (or null) | The words searched at GLEIF (the name without its legal form, or its two longest words on a second search), or the LEI |
| `countryHint` | string (or null) | The country you gave (" / CC" or the country input) |
| `lei` | string (or null) | The matched Legal Entity Identifier (ISO 17442) |
| `legalName` | string (or null) | The legal name on the LEI record |
| `otherNames` | array (or null) | Other names on the LEI record (trading or previous names) |
| `legalCountry` | string (or null) | The country of the legal address (ISO 3166-1 alpha-2) |
| `legalForm` | string (or null) | The legal form code on the record (ISO 20275 ELF code, or the text given) |
| `entityStatus` | string (or null) | ACTIVE or INACTIVE (GLEIF) |
| `registrationStatus` | string (or null) | ISSUED, LAPSED, RETIRED … (GLEIF) |
| `nextRenewalDate` | string (or null) | When the LEI registration is due for renewal (ISO 8601 date) |
| `matchScore` | integer (or null) | How well the record matches the name, 0-100 (100 = the same name; 92 = the same name without its legal form; lower = words in common) |
| `matchReason` | string (or null) | Why the record scored as it did, in plain words |
| `ambiguous` | boolean (or null) | True when several records scored alike (the row is then no_data) |
| `alternatives` | array (or null) | Other candidates with their scores, best first ("LEI legal name (country): score") |
| `candidatesFound` | integer (or null) | GLEIF records the search returned (after the people filter) |
| `peopleLeftOut` | integer (or null) | GLEIF records the search returned that are, or may be, or name a person: never matched or listed, only counted |
| `changeType` | string (or null) | Watch mode: baseline (first run of the list), new (a name added) or changed (another LEI, legal name or status); null in export mode |
| `changedFields` | string (or null) | Watch mode: the tracked fields that changed, comma-separated |
| `previousValues` | string (or null) | Watch mode: the changed fields' previous values, as JSON |
| `url` | string (or null) | The record on GLEIF's public LEI search |
| `source` | string (or null) | Where the data comes from |
| `license` | string (or null) | The terms of the data |
| `licenseUrl` | string (or null) | Where the terms are published |
| `scrapedAt` | string (or null) | When the row was written (ISO 8601, UTC) |

### Pricing

Pay per event: one `company-match` event per matched name (export: every match; watch: `baseline`, `new` and `changed`).

| Plan | Price per match | Per 1,000 matches |
|---|---|---|
| Free (no discount) | $0.0015 | $1.50 |
| Bronze | $0.0013 | $1.30 |
| Silver | $0.00115 | $1.15 |
| Gold (and Platinum, Diamond) | $0.0010 | $1.00 |

- Never charged: failed rows, no_data rows (no clear match, ambiguous, nothing new) and unchanged names. You are never charged
  for failed results.
- Apify also charges its small per-run start event. There are no usage fees on top, so the actor is eligible for x402
  agent payments.

### Use it from AI agents

One clear main input, `companies` (names or LEIs); every row has `status`, `lei`, `legalName`, `matchScore`, `matchReason`
and `alternatives`. Call it through the Apify API, the Apify MCP server (`mouadapi/company-id-matcher`) or x402 agentic
payments. Copy-paste call (your Apify token in place of `YOUR_APIFY_TOKEN`):

```bash
curl -X POST "https://api.apify.com/v2/acts/mouadapi~company-id-matcher/run-sync-get-dataset-items?token=YOUR_APIFY_TOKEN" -H "Content-Type: application/json" -d '{"companies": ["Siemens Aktiengesellschaft", "icu medical inc"]}'
```

```js
import { ApifyClient } from 'apify-client';

const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });
const run = await client.actor('mouadapi/company-id-matcher').call({ companies: ['Deutsche Bank AG | DE', 'BASF SE'], minScore: 90 });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
```

### Limits

- LEI records only: companies that never registered for an LEI (many small businesses) are not found. National registers
  (France's SIRENE, US state registers) are not in this version.
- Each name search reads one page of up to 50 candidates; a very common name ("Holding") may need its legal form or a
  country code to find the right one.
- GLEIF publishes no rate limit: this actor sends one request a second at most, and at most four searches a name. On HTTP 429
  it pauses 60, 120 and 240 seconds on the same connection, then stops with free `failed` rows. No other IP or proxy is ever
  tried.

### Known issues

- The score is our own measure of how alike two names are; it never claims two companies are the same legal entity beyond
  what the names and the country show. Check `alternatives` when the score is under 100.

### FAQ

**Why is a well-known company `no_data`?** Either several records score alike (`ambiguous`: add the country with " | DE" or
the legal form), or the best one is under `minScore`. The row lists the candidates with their scores.

**Why did a watch run return one `no_data` row?** Nothing changed since the last run of the watch list; the row says how many
names were unchanged. It is free.

### Data and licence

- Source: the GLEIF API (api.gleif.org), documented at https://www.gleif.org/en/lei-data/gleif-api.
- Licence: the LEI data are provided under CC0 1.0 (GLEIF LEI Data Terms of Use,
  https://www.gleif.org/en/meta/lei-data-terms-of-use).
- This actor is not affiliated with or endorsed by GLEIF.

# Actor input Schema

## `companies` (type: `array`):

One company per line: a name ("Siemens Aktiengesellschaft", "icu medical inc"), a name with a country code after a bar ("Deutsche Bank AG | DE"), or an LEI. Never a URL. A person's name is refused (no people search).

## `country` (type: `string`):

Look only in this country (two-letter code, e.g. DE) for every name without its own " | CC".

## `minScore` (type: `integer`):

The lowest score (0-100) that counts as a match. Below it, or when two records score alike, the row is a free no_data row with the closest candidates.

## `maxAlternatives` (type: `integer`):

How many other candidates to list on each row, with their scores.

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

Empty: watch when a watch list name is given, otherwise export. "watch" returns only names whose match changed (another LEI, legal name or status) since the last run of the list; "export" returns every name. An explicit mode wins.

## `stateName` (type: `string`):

Name of your watch list (kept in your own storage between runs). Giving a name turns on watch mode; watch mode without a name uses the list "default".

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

Most matched names returned and charged per run. In watch mode the rest come in the next run.

## Actor input object example

```json
{
  "companies": [
    "Siemens Aktiengesellschaft",
    "icu medical inc",
    "7LTWFZYICNSX8D621K86",
    "Deutsche Bank AG | DE"
  ],
  "minScore": 80,
  "maxAlternatives": 3,
  "maxItems": 1000
}
```

# Actor output Schema

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

Dataset with one row per entry (status ok / no_data / failed)

## `runReport` (type: `string`):

Summary of the run (counts, charged and free rows, stop reason)

# 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 = {
    "companies": [
        "Siemens Aktiengesellschaft",
        "icu medical inc",
        "7LTWFZYICNSX8D621K86",
        "Deutsche Bank AG | DE"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("mouadapi/company-id-matcher").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 = { "companies": [
        "Siemens Aktiengesellschaft",
        "icu medical inc",
        "7LTWFZYICNSX8D621K86",
        "Deutsche Bank AG | DE",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("mouadapi/company-id-matcher").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 '{
  "companies": [
    "Siemens Aktiengesellschaft",
    "icu medical inc",
    "7LTWFZYICNSX8D621K86",
    "Deutsche Bank AG | DE"
  ]
}' |
apify call mouadapi/company-id-matcher --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,mouadapi/company-id-matcher"
        }
    }
}
```

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/hBdhyRUhP2gb0vIzo/builds/Qwxt5orjRqVhvHRRb/openapi.json
