# LEI Lookup & Watch: Legal Entity Identifier Status & Renewals (`mouadapi/lei-watch`) Actor

Look up or watch Legal Entity Identifier (LEI) records from the free public GLEIF API: status, renewal date, name, address and parents. Watch mode returns only new, changed or soon-to-renew LEIs. Never charged for failed or unchanged rows. Not affiliated with or endorsed by GLEIF.

- **URL**: https://apify.com/mouadapi/lei-watch.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 lei record returneds

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

Returns Legal Entity Identifier (LEI) records from the free public GLEIF API (status, next renewal date, legal name,
addresses without street lines, BIC and optional parent companies) and, in watch mode, only the LEIs that are new to your list, changed, or due
for renewal soon. Never charged for failed or unchanged rows.

Give it LEI codes (or company names) and get one clean, flat row per company: is the LEI still `ISSUED` or has it `LAPSED`,
when must it be renewed, what is the registered legal name and address, which register and number, who is the parent.
Run it on a schedule in **watch mode** and it tells you only what changed since the last run: a lapsed LEI, a renewal date
that is 30 days away, a new name or address, a merger (successor LEI). Built for KYC / KYB, counterparty and vendor
monitoring, MiFIR / EMIR reporting teams, and AI agents that need a reliable company identifier.

*Data: LEI data from GLEIF (the Global Legal Entity Identifier Foundation) under CC0. Not affiliated with or endorsed by GLEIF.*

### Quick start

- Look up two LEIs (export: every record, every run):

```json
{ "companies": ["7LTWFZYICNSX8D621K86", "529900EXG2PM316ISO63"] }
```

- Watch the same LEIs for changes and renewals (the first run is the baseline; later runs return only changes):

```json
{ "companies": ["7LTWFZYICNSX8D621K86", "529900EXG2PM316ISO63"], "stateName": "my-counterparties" }
```

- Find a company by name (best match; use the LEI for watching):

```json
{ "companies": ["Deutsche Bank"], "country": "DE" }
```

### What it does

- **LEI codes:** looked up in batches of 100 per request; one row per LEI. An unknown LEI gives a free `no_data` row, an
  LEI with wrong check digits a free `failed` row (no request).
- **Company names:** searched on the legal name (GLEIF's search needs every word of the legal name; a trailing legal form
  such as "AG", "Inc" or "Ltd" is left out of the search). Candidates are ranked: the same name (legal forms spelled out:
  "AG" = "Aktiengesellschaft") first, then active and issued records, then main entities before branches and funds.
  `matchesPerName` sets how many are returned (default 1). `matchType` and `nameCandidates` say how sure the match is.
- **Watch mode:** remembers each LEI's legal name, entity and registration status, next renewal date, legal and
  headquarters address, legal form, jurisdiction, successor, expiration and parents under your watch list name, and
  returns only:
  - `baseline`: the first time an LEI is on the list;
  - `changed`: a tracked field changed (`changedFields` and the previous name, statuses and renewal date are in the row);
  - `renewal_due`: nothing changed, but the next renewal date is now within `renewalWarningDays` (sent once per renewal);
  - unchanged LEIs are free and left out (`includeUnchanged: true` returns them as free rows).
  - A watch run with nothing new or changed returns **exactly one free `no_data` row** that says so, so a scheduled run
    never looks empty or broken.
- **Parents** (`includeParents: true`): direct and ultimate accounting parent (LEI and legal name), from GLEIF's
  relationship records.
- **No personal data** (when in doubt, a record is left out whole): only records whose legal form (ISO 20275 ELF code) is
  on a reviewed list of forms that can't be a person are returned, so sole traders, "other" (8888) and "not listed"
  (9999) forms never are; also left out: GLEIF's sole-proprietor category, a person's name (or a sole-trader form or an
  owner) in any name field (legal, other, successor, parent or related-entity names), a parent or successor that is a
  person, and an address line that is a person's name or names an owner. An LEI left out gives a free `no_data` row with
  `dropReason: "person"` that says why, without the name.
- **Addresses carry no street line:** `legalAddress` and `headquartersAddress` hold only the postal code, city, region and
  country (a free-text address line can name a person).
- **You are never charged for failed results:** failed, no\_data and unchanged rows are free.

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `companies` | list of strings | — (prefilled with 2 LEIs) | LEI codes and/or company legal names, one per line |
| `mode` | `watch` or `export` | — | Empty: watch when `stateName` is given, otherwise export. An explicit mode wins |
| `stateName` | string | — (prefilled `example-watchlist`) | Watch list name. Watch mode without a name uses the list `default` |
| `renewalWarningDays` | integer 0–365 | `30` | Watch: return a record once as `renewal_due` when its renewal date is this close |
| `includeParents` | boolean | `false` | Add direct and ultimate parent (2 more requests per record) |
| `matchesPerName` | integer 1–10 | `1` | Company names: how many best matches to return |
| `country` | string | — | Two-letter country code of the legal address, to narrow name searches |
| `includeUnchanged` | boolean | `false` | Watch: also return unchanged records (free) |
| `maxItems` | integer 1–10,000 | `100` | Most charged rows per run; in watch mode the rest comes next run |

**Field names from other tools:** `leis`, `names` (→ `companies`). They work in the input but are not listed in the input
schema (charter 5.4); `companies` wins when both are given.

### Output

One row per LEI record (flat; exports cleanly to CSV, JSON and Excel):

```json
{
    "status": "ok",
    "attempts": 1,
    "error": null,
    "input": "7LTWFZYICNSX8D621K86",
    "mode": "watch",
    "watchList": "my-counterparties",
    "lei": "7LTWFZYICNSX8D621K86",
    "legalName": "DEUTSCHE BANK AKTIENGESELLSCHAFT",
    "entityStatus": "ACTIVE",
    "registrationStatus": "ISSUED",
    "category": "GENERAL",
    "legalFormId": "6QQB",
    "jurisdiction": "DE",
    "legalAddress": "60325, Frankfurt am Main, DE-HE, DE",
    "legalCountry": "DE",
    "registeredAs": "HRB 30000",
    "nextRenewalDate": "2027-06-05",
    "daysToRenewal": 248,
    "renewalDueSoon": false,
    "bic": "DEUTDEFFXXX",
    "directParentLei": null,
    "matchType": "lei",
    "changeType": "changed",
    "changedFields": "registrationStatus",
    "previousRegistrationStatus": "LAPSED",
    "url": "https://search.gleif.org/#/record/7LTWFZYICNSX8D621K86",
    "source": "GLEIF LEI data (Global Legal Entity Identifier Foundation), via the public GLEIF API",
    "license": "CC0 1.0 (GLEIF LEI Data Terms of Use); not affiliated with or endorsed by GLEIF",
    "dataUpdatedAt": "2026-09-30",
    "scrapedAt": "2026-09-30T10:00:00.000Z"
}
```

Every row also has the headquarters address, register code, creation and registration dates, last update, expiration,
successor, managing LOU, corroboration level, conformity flag and parent names (see the dataset schema).

| Status | Meaning | Charged? |
|---|---|---|
| `ok` | LEI record returned | Export: yes. Watch: `baseline`, `changed` and `renewal_due` yes; `unchanged` no |
| `no_data` | Unknown LEI, no name match, a record left out because it is or names a person, or no changes since the last run | No |
| `failed` | Invalid input, or no answer after retries (`error` says why) | No |

The key-value store holds `RUN_REPORT` (counts, charged and free rows, stop reason) and, when something fails, the raw
response (`SNAPSHOT_*`).

### Use it from AI agents

- One clear main input: `{"companies": ["<LEI or company name>"]}`. The same call returns data every time (export).
- Add `"stateName": "<list>"` to get only changes and renewal alerts on later calls; a call with nothing new returns one
  `no_data` row that says "No new or changed LEI records since the last run".
- Every row has `status`, `error`, `url` (the record on GLEIF's public LEI search) and `scrapedAt`.
- Via the Apify MCP server or API: `mouadapi/lei-watch`. Pay per returned record; x402 agentic payments supported.

Copy-paste call (your Apify token in `APIFY_TOKEN`):

```bash
curl -s -X POST "https://api.apify.com/v2/acts/mouadapi~lei-watch/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"companies": ["7LTWFZYICNSX8D621K86"]}'
```

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

const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('mouadapi/lei-watch').call({ companies: ['7LTWFZYICNSX8D621K86'] });
const { items } = await client.dataset(run.defaultDatasetId).listItems();
console.log(items);
```

### Pricing

Pay per event, one `lei-record` event per returned record (export: every record; watch: `baseline`, `changed` and
`renewal_due` rows). `no_data`, `failed` and unchanged rows are free.

| Plan | Price per 1,000 records |
|---|---|
| Free | $1.50 |
| Bronze | $1.30 |
| Silver | $1.15 |
| Gold (and higher) | $1.00 |

Apify also charges its small per-run start event. There are no usage fees on top.

### Limits

- **One request at a time, at most one a second** (GLEIF publishes no per-minute limit; we stay well under the usual
  60 a minute). 100 LEIs take about 1 request; with parents, 2 more requests per LEI (about 3 minutes for 100).
- If GLEIF answers HTTP 429 (rate limit), the run 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

- **Name search is best effort:** GLEIF's legal-name search needs every word ("Deutsche Bank AG" does not find
  "DEUTSCHE BANK AKTIENGESELLSCHAFT", so the legal form is left out of the search). Check `matchType`; watch by LEI code.
- Parents are the accounting-consolidation parents GLEIF has; many entities report none (then the fields are empty).
- GLEIF refreshes its data several times a day (`dataUpdatedAt` is the data set's publish date).

### Data and licence

- Source: the public GLEIF API (api.gleif.org), documented at gleif.org. GLEIF's LEI Data Terms of Use: the data "are
  provided under the CC0 licence" and the Access Service "is provided for free".
- This Actor is not provided, supported, authorized or endorsed by GLEIF or any LEI issuer (LOU), and is not a GLEIF
  service. It returns the LEI data as published.

### FAQ

**How do I get alerted before an LEI lapses?** Schedule a daily or weekly watch run with your LEIs and
`renewalWarningDays` (default 30). Each LEI comes back once as `renewal_due` when its renewal date gets close, and again as
`changed` if it lapses or is renewed.

**Why did a company name give the wrong entity?** GLEIF's name search is word based. Check `matchType` (`name (same
name)` is an exact match after spelling out legal forms), raise `matchesPerName`, narrow with `country`, or use the LEI.

**Are people's names returned?** No. Only legal forms that can't be a person pass, and records with a person's name in any
name field, a parent or successor that is a person, or an address line that names a person or an owner are left out whole.
No free-text address line is ever returned. The check is automatic and leans towards leaving data out: a company named
like a person (a bare two-word name such as "Fresh Express", with no legal-form or organisation word) is left out too.

# Actor input Schema

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

LEI codes (20 characters, e.g. 7LTWFZYICNSX8D621K86) and/or company legal names, one per line. An LEI gives exactly its record; a name gives its best match(es) from GLEIF's legal-name search (every word must be in the legal name). Use LEI codes for watching.

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

watch: only records that are new to your watch list, changed since the last run, or due for renewal soon (unchanged records are free and left out). export: every record, every run. Empty: watch when a watch list name is given, otherwise export.

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

Name of your watch list (letters, digits, dashes). The run remembers each LEI's status, renewal date, names, addresses and parents under this name and compares next time. Watch mode without a name uses the list "default".

## `renewalWarningDays` (type: `integer`):

In watch mode, a record whose next renewal date is this many days away or less is returned once as renewal\_due (every row also has daysToRenewal and renewalDueSoon). Default 30.

## `includeParents` (type: `boolean`):

Add each record's direct and ultimate parent (LEI and legal name) from GLEIF's relationship data. Two more requests per record. Default false.

## `matchesPerName` (type: `integer`):

For company names: how many best matches to return (same normalized name first, then active and issued records, then main entities before branches and funds). Default 1.

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

Two-letter country code of the legal address (e.g. DE, US, GB) to narrow name searches. LEI codes are not affected.

## `includeUnchanged` (type: `boolean`):

Watch mode: also return records that did not change, as free rows (changeType unchanged). Default false.

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

Most charged rows per run. In watch mode, changes over the limit are returned in the next run. Default 100.

## Actor input object example

```json
{
  "companies": [
    "7LTWFZYICNSX8D621K86",
    "529900EXG2PM316ISO63"
  ],
  "mode": "watch",
  "stateName": "example-watchlist",
  "renewalWarningDays": 30,
  "includeParents": false,
  "matchesPerName": 1,
  "includeUnchanged": false,
  "maxItems": 100
}
```

# Actor output Schema

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

Dataset with one row per returned LEI record (plus free no\_data / failed rows)

## `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": [
        "7LTWFZYICNSX8D621K86",
        "529900EXG2PM316ISO63"
    ],
    "mode": "watch",
    "stateName": "example-watchlist"
};

// Run the Actor and wait for it to finish
const run = await client.actor("mouadapi/lei-watch").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": [
        "7LTWFZYICNSX8D621K86",
        "529900EXG2PM316ISO63",
    ],
    "mode": "watch",
    "stateName": "example-watchlist",
}

# Run the Actor and wait for it to finish
run = client.actor("mouadapi/lei-watch").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": [
    "7LTWFZYICNSX8D621K86",
    "529900EXG2PM316ISO63"
  ],
  "mode": "watch",
  "stateName": "example-watchlist"
}' |
apify call mouadapi/lei-watch --silent --output-dataset

```

## MCP server setup

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

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/clTofk6taykoH9Sd0/builds/kZaJlJi8nBtiLKJ7A/openapi.json
