# GLEIF LEI Monitor: Lapsed LEI & Entity Change Watch (`plym-actor-factory/gleif-lei-portfolio-watch`) Actor

LEI monitoring for regulatory-reporting, treasury and onboarding teams: watch counterparty LEIs and get events when an LEI lapses, is retired or annulled, the entity goes inactive, the legal name or address changes, or parent relationships change. Free official GLEIF API, no key.

- **URL**: https://apify.com/plym-actor-factory/gleif-lei-portfolio-watch.md
- **Developed by:** [Daniel Witney](https://apify.com/plym-actor-factory) (community)
- **Categories:** AI, Automation, Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $20.00 / 1,000 lei change event delivereds

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

## GLEIF LEI Monitor: Lapsed LEI & Entity Change Watch

**LEI monitoring for regulatory-reporting, treasury and counterparty-onboarding teams.** Watch your counterparties' **LEIs** and get a typed event when an LEI **lapses**, is retired or annulled, the entity becomes **inactive**, the legal name or address changes, or its Level-2 **parent / child** relationships change — from the free official **GLEIF API / Golden Copy**, **no key**. Useful for keeping counterparty LEIs current for transaction reporting (e.g. EMIR / MiFIR) and KYC refresh. **Not** a bulk LEI dump, **not** an OFAC mashup.

**Quick start (no key):** replace the example LEIs with your counterparties' → set **Source mode = live** → save as a Task and schedule it daily or weekly. The first live run records a baseline (no charge); later runs deliver an event only when a watched LEI changes. Clicking *Start* with the defaults runs a free health check only.

### Who this is for

- **Regulatory-reporting teams** (EMIR / MiFIR / SFTR) that need counterparty LEIs to stay valid.
- **Treasury and trading operations** at lenders, funds and corporates holding counterparty LEI books.
- **KYC / onboarding teams** that want to know when a counterparty's legal name, address or parent changes.

#### Typical workflow

1. Export your counterparties' 20-character LEIs.
2. Run once with `sourceMode: "live"` (no key) — this **records a baseline** of each LEI's registration status, entity status, name, address and parents (no charge, no events).
3. Save as a Task and **schedule daily or weekly** (Golden Copy refreshes a few times a day; LEI changes are sparse).
4. Each later run **compares against the stored baseline** and delivers `LEI_LAPSED`, `LEI_RETIRED`, `ENTITY_INACTIVE`, `LEGAL_NAME_CHANGED`, `PARENT_CHANGED`, … only for LEIs on your list. Route them to your reporting or KYC queue via Apify integrations.

#### First run vs later runs

**Run 1 (live) is a baseline:** it stores each LEI's current state and emits no events. **Run 2 onwards** compares against that baseline, so keep the same Task (its key-value store holds the checkpoint).

#### Run it live now (no API key)

```json
{
  "leis": [
    "5493001KJTIIGC8Y1R12",
    "549300RMUDWPHCUQNE66"
  ],
  "sourceMode": "live",
  "signalGroups": [
    "registration",
    "entity",
    "identity",
    "relationships"
  ],
  "requestPaceMs": 1100,
  "maxRunSeconds": 300
}
```

#### Example output (SAMPLE)

> SAMPLE — illustrative values in the real output shape; LEI `00000000000000000000` / `EXAMPLE CAPITAL S.A.` are placeholders, not a real LEI record.

```json
{
  "lei": "00000000000000000000",
  "legal_name": "EXAMPLE CAPITAL S.A.",
  "event_type": "LEI_LAPSED",
  "event_subtype": "ISSUED->LAPSED",
  "changed_fields": [
    "registration.status"
  ],
  "previous_value": "ISSUED",
  "new_value": "LAPSED",
  "as_of": "2026-09-17T08:00:00Z",
  "summary": "LEI 00000000000000000000 registration status ISSUED→LAPSED on GLEIF Golden Copy.",
  "source_url": "https://api.gleif.org/api/v1/lei-records/00000000000000000000",
  "event_uid": "<md5>",
  "registration_status": "LAPSED",
  "entity_status": "ACTIVE",
  "billable": true
}
```

**What you get per $0.02 event:** one deduplicated status, identity or relationship change for one LEI on your watchlist (one dataset row like the sample above). Baselines, quiet runs, duplicates and invalid LEIs are never charged.

### Honest coverage (read before buying)

| Included (free GLEIF API / Golden Copy) | **Not** included |
|-----------------------------------------|------------------|
| Registration status, entity status, legal name, legal + HQ address | Paid LEI issuer portal extracts |
| Ultimate + direct parent LEIs; first page of direct children | Full deep ownership graphs beyond first children page |
| Official `as_of` from Golden Copy publish timestamp | Sub-hour streaming (Golden Copy refreshes a few times/day) |
| Watchlist of your LEIs only | Dump of all ~3.4M LEIs |

**Rate limits (official):** ~**60 requests / minute / user**. Default `requestPaceMs=1100`. Relationship mode uses **up to ~4 HTTP calls per LEI** (record + ultimate-parent + direct-parent + optional children). Large books need longer `maxRunSeconds` or chunked schedules.

**Cadence:** LEI changes are **sparser** than daily gazettes — schedule **daily or weekly**, not sub-hour streaming.

### Value proposition

- **LEI watchlist × Golden Copy / API snapshot diff** → `LEI_LAPSED`, `LEI_ISSUED`, `LEI_RETIRED`, `LEI_ANNULLED`, `ENTITY_INACTIVE`, `ENTITY_REACTIVATED`, `LEGAL_NAME_CHANGED`, `ADDRESS_CHANGED`, `PARENT_CHANGED`, `CHILD_ADDED`.
- Official free public HTTP only. **No paid LEI issuer API.** Not OFAC screening.
- **Pay only for unique delivered change events.** First observation stores a baseline (no charge). Quiet days ≈ $0.
- Durable KVS checkpoint + `event_uid` dedupe.

### PRODUCTION USE (required for real monitoring)

> **Store default ≠ product.** Schema defaults use `sourceMode=fixture` so Apify Store daily auto-tests leave a non-empty health dataset **without** calling GLEIF. That fixture path only writes a non-billable `RUN_STATUS` / `FIXTURE_HEALTH` row. It is **not** LEI portfolio monitoring.

For **production monitoring** you **must**:

1. Set **`sourceMode` = `live`**
2. Provide your `leis` watchlist
3. Schedule runs with fair-use pacing (`requestPaceMs` ≥ 1100 recommended)
4. Optionally set `includeChildren=false` to save requests if you only need status/name/parent

Official references:

- [GLEIF API](https://www.gleif.org/en/lei-data/gleif-api/)
- [GLEIF Golden Copy](https://www.gleif.org/en/lei-data/gleif-golden-copy)
- Endpoint: `https://api.gleif.org/api/v1/lei-records/{LEI}`

### Apify Store daily auto-test / default input

| Field | Default |
|-------|---------|
| `leis` | Bloomberg sample LEIs |
| `signalGroups` | `registration`, `entity`, `identity`, `relationships` |
| `sourceMode` | `fixture` |
| `emitFixtureDemoEvents` | `false` |
| `maxRunSeconds` | `55` |
| `requestPaceMs` | `1100` |

On that path the Actor **SUCCEEDS** quickly, pushes exactly one `RUN_STATUS` / `FIXTURE_HEALTH` row (`billable: false`), and does **not** charge `lei-change-event-delivered`.

### Input (summary)

| Field | Notes |
|-------|--------|
| `leis` | Watchlist (required, 20-char LEI) |
| `signalGroups` | `registration`, `entity`, `identity`, `relationships` |
| `sourceMode` | `fixture` (default) or `live` |
| `emitFixtureDemoEvents` | Default **false** |
| `includeChildren` | Default true (extra HTTP when relationships on) |
| `maxChildrenPageSize` | Default 50 (max 200; first page only) |
| `maxRunSeconds` / `maxEvents` | Budgets |
| `resumeFromCheckpoint` | Default true |
| `requestPaceMs` | Default **1100** (~60/min) |

#### Production example

```json
{
  "leis": ["5493001KJTIIGC8Y1R12", "549300RMUDWPHCUQNE66"],
  "signalGroups": ["registration", "entity", "identity", "relationships"],
  "sourceMode": "live",
  "includeChildren": true,
  "maxRunSeconds": 600,
  "requestPaceMs": 1100,
  "resumeFromCheckpoint": true
}
```

### Output

#### Change events (billable under PPE)

Typed rows include: `lei`, `legal_name`, `event_type`, `event_subtype`, `changed_fields`, `previous_value`, `new_value`, `as_of`, `evidence`, `summary`, `source_url`, `event_uid`, `registration_status`, `entity_status`, `billable: true`.

#### RUN\_STATUS (never billed)

Every successful run pushes one health row (`FIXTURE_HEALTH` | `MONITOR_IDLE` | `CATCHUP_COMPLETE` | `HEALTH_OK`).

### Pricing

Pay-per-event: **`lei-change-event-delivered`** at **$0.02** per delivered unique change event ($20 / 1,000). Single tier (see `docs/monetization.md` and ADR 0010). Health / baseline / duplicate / fixture rows are never charged.

### Local development

```bash
cd actors/gleif-lei-portfolio-watch
npm install
npm test
npm run build
apify run -p
```

### Attribution

Data originates from the **Global Legal Entity Identifier Foundation (GLEIF)** free public API / Golden Copy. Not an official GLEIF product. Not a paid LEI issuer service. Not OFAC.

# Actor input Schema

## `leis` (type: `array`):

20-character Legal Entity Identifiers to monitor. Invalid entries are skipped with no charge. Not a full Golden Copy dump — only your book is watched.

## `signalGroups` (type: `array`):

Which change groups to evaluate: registration (ISSUED/LAPSED/RETIRED/…), entity (ACTIVE/INACTIVE), identity (legal name / legal+HQ address), relationships (ultimate/direct parent + optional direct children). Default: all.

## `maxRunSeconds` (type: `integer`):

Wall-clock budget. Official GLEIF API fair-use is ~60 requests/minute — relationship fetches add requests per LEI.

## `maxEvents` (type: `integer`):

Stop after this many unique change events are delivered (does not include RUN\_STATUS).

## `resumeFromCheckpoint` (type: `boolean`):

If true, resume LEI snapshots and seen event\_uids from the default Key-Value Store.

## `sourceMode` (type: `string`):

fixture = local/CI / Apify Store daily auto-test default (no network; emits RUN\_STATUS only unless emitFixtureDemoEvents=true). live = official free GLEIF API (Golden Copy). No paid LEI issuer APIs. Not an OFAC mashup.

## `emitFixtureDemoEvents` (type: `boolean`):

When sourceMode=fixture, if true push fabricated change events from local fixtures (unit/local demos only). Default false so Store daily auto-tests never emit fake change events or charge lei-change-event-delivered. Production: leave false and use sourceMode=live. SAMPLE rows only: demo events use placeholder entities (never real companies/IDs), are flagged sample=true / isSample=true, and are NEVER charged. To see them, set Source mode = fixture and put the placeholder values SAMPLE00000000000118, SAMPLE00000000000215 in "LEIs to watch (example LEIs prefilled — replace with yours)".

## `includeChildren` (type: `boolean`):

When relationships signals are enabled, also fetch the first page of direct-child LEIs to detect CHILD\_ADDED. Extra HTTP request per watched LEI. Default true.

## `maxChildrenPageSize` (type: `integer`):

GLEIF page\[size] for direct-children (max 200). Only first page is stored for CHILD\_ADDED diffs — honest limit for large trees.

## `maxTotalChargeUsd` (type: `number`):

Optional soft budget hint for delivered change events. Platform ACTOR\_MAX\_TOTAL\_CHARGE\_USD also applies when set.

## `requestPaceMs` (type: `integer`):

Minimum delay between outbound GLEIF HTTP requests. Official rate limit ~60 requests/minute/user (default 1100).

## Actor input object example

```json
{
  "leis": [
    "5493001KJTIIGC8Y1R12",
    "549300RMUDWPHCUQNE66"
  ],
  "signalGroups": [
    "registration",
    "entity",
    "identity",
    "relationships"
  ],
  "maxRunSeconds": 55,
  "maxEvents": 500,
  "resumeFromCheckpoint": true,
  "sourceMode": "fixture",
  "emitFixtureDemoEvents": false,
  "includeChildren": true,
  "maxChildrenPageSize": 50,
  "requestPaceMs": 1100
}
```

# Actor output Schema

## `OUTPUT` (type: `string`):

JSON summary: delivered, charged, runStatus, checkpoint, stats, GLEIF coverage limits

# 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 = {};

// Run the Actor and wait for it to finish
const run = await client.actor("plym-actor-factory/gleif-lei-portfolio-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 = {}

# Run the Actor and wait for it to finish
run = client.actor("plym-actor-factory/gleif-lei-portfolio-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 '{}' |
apify call plym-actor-factory/gleif-lei-portfolio-watch --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,plym-actor-factory/gleif-lei-portfolio-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/FIvoQsJb5HDAJGMOi/builds/ghUXpmMJCrJ3FW82F/openapi.json
