# LEI Renewal Monitor API (`thesjostrandproject/lei-renewal-monitor`) Actor

Check counterparty LEIs on the open LEI index API and get one row per LEI: status, next renewal date, successor, and flags for lapsed, renewal due, no longer valid, needs attention, changed since. Input: leis, renewalWithinDays, changedSince. No name search, no ownership data. Pay per LEI checked.

- **URL**: https://apify.com/thesjostrandproject/lei-renewal-monitor.md
- **Developed by:** [The Sjöstrand Project](https://apify.com/thesjostrandproject) (community)
- **Categories:** Business
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$3.00 / 1,000 lei checkeds

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

## LEI Renewal Monitor API

### What this Actor returns

Give it a list of counterparty LEIs (Legal Entity Identifiers) and it returns one row per LEI it finds in the open LEI index: where each registration stands, when it is next due for renewal, and which ones have lapsed, are due for renewal, are no longer valid, need your attention, or changed since a date you choose. Every row has the same 15 fields, and a value the record does not have is `null`, never a missing key.

- `lei`, `legal_entity_label`, `entity_category`: the identifier, the registered legal name and the kind of entity
- `registration_status`, `entity_status`, `successor_lei`: the state of the LEI registration and of the entity, and the LEI that replaces it where the index names one
- `next_renewal_date`, `last_update_date`: when the registration is next due and when its record was last updated
- `lapsed`, `renewal_due`, `no_longer_valid`, `needs_attention`, `updated_since`: five flags that are computed by this tool from the index's own fields; they are not GLEIF data
- `changes_since`: with a change date, the index's own change entries since then (field, old value, new value and date), each entry written out as JSON text; entries that contain an email address or phone number are left out
- `changes_status`: `ok` when the change history was read, `unavailable` when the index could not be reached for that LEI, `not requested` when you gave no change date

The Actor reads the open LEI index API and nothing else: no other source and no enrichment. It does not scrape any website, and there is no login. It keeps nothing between runs, so to see what changed, give the date of your last check.

### Who it helps

- **Compliance, onboarding and finance teams**, who hold a list of counterparty LEIs and want to know which registrations have lapsed, are close to their renewal date, or are no longer valid because they were retired, merged or annulled.
- **Data and operations teams** who keep LEIs in a spreadsheet or database and want a scheduled check of what changed since the last run, through the Apify API.

### Quick start

1. Open the Input tab and leave the defaults as they are. They check three long-lived LEIs, so a first run returns exactly three rows.
2. Press Start. The default run costs about $0.00905: 3 rows at $0.003 is $0.009, plus Apify's tiny run-start event (see What it costs). Try it on these three first.
3. Open the Output tab, check the rows, then replace the LEIs with your own list, set how far ahead you want renewals flagged, and add a change date if you want to see what changed.

### Input

| Field | What it does | Default |
|---|---|---|
| LEIs to check (`leis`) | The counterparty LEIs, one 20-character code per entry, up to 100. Case and spaces around a code do not matter, and a code given twice is checked once. | three long-lived LEIs |
| Renewal due within (`renewalWithinDays`) | An issued LEI is flagged `renewal_due` when its next renewal date is today, within this many days, or already past. From 0 to 730. | 60 |
| Changed since (`changedSince`) | Optional, written YYYY-MM-DD. Fills `updated_since` and `changes_since`. Leave empty to skip it. | empty |
| Maximum rows (`maxItems`) | The most rows to return and pay for, from 1 to 100. It must be at least the number of valid LEIs you give; if it is lower the run stops with a clear message before checking anything. | 10 |

Each code is checked against the ISO 17442 checksum first. A code that fails is named in the run log and is neither looked up nor charged. A code that passes but is not in the index is named in the run log too, and is not charged either. The run's final status message counts both.

### Output

One row per LEI found, in the order you gave them. The rows below are what the default input returned on 13 October 2026, unchanged.

```json
[
  {
    "lei": "5493001KJTIIGC8Y1R12",
    "legal_entity_label": "Bloomberg Finance L.P.",
    "entity_category": "GENERAL",
    "registration_status": "ISSUED",
    "entity_status": "ACTIVE",
    "next_renewal_date": "2027-01-25",
    "last_update_date": "2026-01-05",
    "successor_lei": null,
    "lapsed": false,
    "renewal_due": false,
    "no_longer_valid": false,
    "needs_attention": false,
    "updated_since": null,
    "changes_since": null,
    "changes_status": "not requested"
  },
  {
    "lei": "HWUPKR0MPOU8FGXBT394",
    "legal_entity_label": "Apple Inc.",
    "entity_category": "GENERAL",
    "registration_status": "ISSUED",
    "entity_status": "ACTIVE",
    "next_renewal_date": "2027-03-08",
    "last_update_date": "2026-03-03",
    "successor_lei": null,
    "lapsed": false,
    "renewal_due": false,
    "no_longer_valid": false,
    "needs_attention": false,
    "updated_since": null,
    "changes_since": null,
    "changes_status": "not requested"
  },
  {
    "lei": "INR2EJN1ERAN0W5ZP974",
    "legal_entity_label": "MICROSOFT CORPORATION",
    "entity_category": "GENERAL",
    "registration_status": "ISSUED",
    "entity_status": "ACTIVE",
    "next_renewal_date": "2027-05-04",
    "last_update_date": "2026-04-07",
    "successor_lei": null,
    "lapsed": false,
    "renewal_due": false,
    "no_longer_valid": false,
    "needs_attention": false,
    "updated_since": null,
    "changes_since": null,
    "changes_status": "not requested"
  }
]
```

How the five flags are computed, from the index's own fields:

- `lapsed` is true when `registration_status` is `LAPSED`.
- `renewal_due` is true when `registration_status` is `ISSUED` and `next_renewal_date` is today, within `renewalWithinDays` days, or already past. It is false for any other status, and `null` when the renewal date is missing.
- `no_longer_valid` is true when `registration_status` is `RETIRED`, `MERGED`, `ANNULLED`, `DUPLICATE`, `CANCELLED` or `PENDING_ARCHIVAL`, or `entity_status` is `INACTIVE`. `TRANSFERRED` and `PENDING_TRANSFER` only move the LEI to another issuer, so they do not count.
- `needs_attention` is false only when `registration_status` is `ISSUED`, `entity_status` is `ACTIVE` and `renewal_due` is false. It is true for anything else, including a status this tool does not recognise and a missing renewal date, so a record is never shown as clear by mistake. Filter on it for a quick list of what to look at.
- `updated_since` is true when `last_update_date` is on or after `changedSince`. It is `null` when you gave no date.

With `changedSince`, each row also lists the index's own change entries dated on or after that date, each one written out as JSON text with the index's names: `recordType`, `modificationType`, `field`, `date`, `valueOld` (the old value), `valueNew` (the new value) and `context`. Entries that contain an email address or phone number are left out. An empty list means nothing changed. If the history of one LEI cannot be read, that row still comes back with all its flags, `changes_since` is `null` and `changes_status` is `unavailable`; run it again for that LEI. For example, this input:

```json
{
  "leis": [
    "INR2EJN1ERAN0W5ZP974",
    "EXAMPLE0000000009981",
    "5493001KJTIIGC8Y1R13"
  ],
  "renewalWithinDays": 365,
  "changedSince": "2026-04-01"
}
```

returned this row. It is what that input returned on 13 October 2026, unchanged. The other two LEIs gave no rows: one fails the checksum and one is not in the index. They are named in the run log and were not charged.

```json
{
  "lei": "INR2EJN1ERAN0W5ZP974",
  "legal_entity_label": "MICROSOFT CORPORATION",
  "entity_category": "GENERAL",
  "registration_status": "ISSUED",
  "entity_status": "ACTIVE",
  "next_renewal_date": "2027-05-04",
  "last_update_date": "2026-04-07",
  "successor_lei": null,
  "lapsed": false,
  "renewal_due": true,
  "no_longer_valid": false,
  "needs_attention": true,
  "updated_since": true,
  "changes_since": [
    "{\"recordType\": \"LEI\", \"modificationType\": \"UPDATE\", \"field\": \"/lei:LEIData/lei:LEIRecords/lei:LEIRecord/lei:Registration/lei:NextRenewalDate\", \"date\": \"2026-04-08T00:00:00Z\", \"valueOld\": \"2026-05-04T00:00:00Z\", \"valueNew\": \"2027-05-04T00:00:00Z\", \"context\": null}",
    "{\"recordType\": \"LEI\", \"modificationType\": \"UPDATE\", \"field\": \"/lei:LEIData/lei:LEIRecords/lei:LEIRecord/lei:Registration/lei:LastUpdateDate\", \"date\": \"2026-04-08T00:00:00Z\", \"valueOld\": \"2025-03-28T08:58:24.047Z\", \"valueNew\": \"2026-04-07T09:26:31.880Z\", \"context\": null}"
  ],
  "changes_status": "ok"
}
```

### What it costs

You pay for one event, **LEI checked**, at $0.003 per row returned. Apify also adds its standard run-start event, $0.00005 per start for an Actor run with up to 1 GB of memory; it is the platform's own event, not ours. The default run of 3 rows costs about $0.00905. Checking the history for a change date does not cost extra. A row whose history could not be read (`changes_status` `unavailable`) is still charged, because its status check worked. Invalid LEIs, LEIs not in the index and rows the Actor leaves out (see below) are never charged.

For example, a list of 100 valid LEIs that are all found costs $0.30 plus the start event. If your Apify plan includes monthly credit, a small run draws on that credit.

If the source cannot be reached or keeps answering with errors, the run stops with a clear status message, and only the rows already returned are charged.

### Limits and honest notes

- **Up to 100 LEIs per run.** The source's documented limit is 60 requests per minute. The Actor sends at most one request every two seconds, half of that, and looks LEIs up 50 to a request, so a list of 100 takes 2 requests. With a change date it also asks for each LEI's history, one request per LEI and up to 3 per LEI for a very long history, so a full run of 100 is at most 302 requests. At two seconds each that is 604 seconds, about 10 minutes, plus the source's own answer time. The platform's default run timeout is one hour, so a full run has several times that margin when the source is healthy. Longer lists: split them over several runs.
- **Pace and busy source.** When the source is busy it waits as asked, up to about a minute per request, and then stops with a clear message.
- **The flags are this tool's reading of the index's fields.** They are not GLEIF data. `no_longer_valid` and `needs_attention` read the registration status, the entity status and `renewal_due`; `successor_lei` is filled only where the index names a successor.
- **A change is dated by the index.** It can be dated a day after the `last_update_date` of the same update, so a row can show `updated_since` true on a date where `changes_since` is empty, or the other way round, at the edge of the date you chose.
- **Long histories are cut.** For one LEI the Actor reads at most the first 600 history entries; if the history is longer, the cut is written to the run log. Entries that hold an email address or a phone number are left out of `changes_since`.
- **A history that fails.** If the index keeps refusing one LEI's history after the Actor's retries, that row is still returned and charged with `changes_since` null and `changes_status` `unavailable`, and the run log names the LEI. If the main lookup fails, the run stops with a clear message as before.
- **The history covers every record of the LEI**, not only its registration: relationship and reporting-exception records are included, each marked by its `recordType`.
- **No sole proprietors.** A sole proprietor is a natural person, so the Actor leaves those records out. It also leaves out any record whose name holds an email address or a phone number, and any record with no name or registration status.
- **What it does not do.** No search by name, no ownership or parent links, no addresses, no other source, and nothing stored between runs.
- **Freshness** follows the source. `last_update_date` is the date the LEI record was last updated at the source.

### Questions

**Why did my run return fewer rows than I gave LEIs?**
Some LEIs failed the checksum, were not in the index, or were left out because they are sole proprietors or lacked a name or a registration status. The final status message counts each reason and the run log names the LEIs. You were not charged for those.

**What does the run say when something is wrong?**
It says what it found and what to try. "Give at least one LEI in leis" means the list was empty. "You gave 25 LEIs but maxItems is 10" means raise `maxItems` so no LEI is left unchecked. A message about `changedSince` or `renewalWithinDays` shows the right shape. "Could not be reached" or "kept answering" means the source is busy; wait a few minutes and run again.

**What does `needs_attention` mean?**
It is false only for an issued registration of an active entity that is not due for renewal inside your window. Anything else is true, so a lapsed, retired, merged, annulled or inactive record, a renewal that is due, and a status the tool does not recognise all show up.

**What does a lapsed registration mean?**
`LAPSED` means the entity did not renew its LEI in time. The record is still in the index, so you can see its dates, and `lapsed` is true.

**How do I see what changed since my last check?**
Set `changedSince` to the date of your last check. Rows then say whether the record was updated since, and list the changes the index recorded. Run it on a schedule with the Apify scheduler or the API; each run is independent and charged per row.

**Why is `renewal_due` true for an LEI that is still issued?**
Its next renewal date is today, inside your window, or already past while the index still shows it as `ISSUED`. Raise or lower `renewalWithinDays` to move the window.

### Data source and AI note

Data source: the GLEIF LEI Index (the Global LEI Index), released under the CC0 1.0 public-domain licence. The index's fields are output as the source gives them. The five flags are computed by this tool from those fields and are not GLEIF data. There is no other source and no enrichment. `last_update_date` is the date the record was last updated at the source, taken from its `lastUpdateDate` field; the rows have no source_updated_at field.

This tool is not affiliated with, endorsed by or sponsored by GLEIF or any Local Operating Unit.

This tool was built with the help of AI coding assistants.

### Help

Questions, a row that looks wrong, or an idea for a field: please open an issue on the Issues tab. Thank you for the report; every one is read.

# Actor input Schema

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

The counterparty LEIs to check, one 20-character code per entry, up to 100. A code whose checksum fails is reported in the run log and not charged, and so is a valid code the LEI index does not know. The default is three long-lived entities, so a first run always returns rows.

## `renewalWithinDays` (type: `integer`):

An issued LEI counts as due for renewal (renewal_due) when its next renewal date is today or within this many days, or already past. From 0 to 730.

## `changedSince` (type: `string`):

Optional. A date written YYYY-MM-DD. Each row then says whether its last update is on or after that date (updated_since) and lists the field changes the source recorded on or after it (changes_since). Leave empty to skip this; it adds one or more requests per LEI.

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

The most rows to return and pay for, from 1 to 100. It must be at least the number of valid LEIs you give; if it is lower the run stops with a message before checking anything.

## Actor input object example

```json
{
  "leis": [
    "5493001KJTIIGC8Y1R12",
    "HWUPKR0MPOU8FGXBT394",
    "INR2EJN1ERAN0W5ZP974"
  ],
  "renewalWithinDays": 60,
  "changedSince": "",
  "maxItems": 10
}
```

# Actor output Schema

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

The rows of this run, one per LEI found, with the 15 fields described in the dataset schema (LEI, name, category, registration and entity status, renewal and update dates, successor LEI, five computed flags, the changes since the given date and whether they could be read).

# 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 = {
    "leis": [
        "5493001KJTIIGC8Y1R12",
        "HWUPKR0MPOU8FGXBT394",
        "INR2EJN1ERAN0W5ZP974"
    ],
    "renewalWithinDays": 60,
    "changedSince": "",
    "maxItems": 10
};

// Run the Actor and wait for it to finish
const run = await client.actor("thesjostrandproject/lei-renewal-monitor").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 = {
    "leis": [
        "5493001KJTIIGC8Y1R12",
        "HWUPKR0MPOU8FGXBT394",
        "INR2EJN1ERAN0W5ZP974",
    ],
    "renewalWithinDays": 60,
    "changedSince": "",
    "maxItems": 10,
}

# Run the Actor and wait for it to finish
run = client.actor("thesjostrandproject/lei-renewal-monitor").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 '{
  "leis": [
    "5493001KJTIIGC8Y1R12",
    "HWUPKR0MPOU8FGXBT394",
    "INR2EJN1ERAN0W5ZP974"
  ],
  "renewalWithinDays": 60,
  "changedSince": "",
  "maxItems": 10
}' |
apify call thesjostrandproject/lei-renewal-monitor --silent --output-dataset

```

## MCP server setup

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

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/bKB9E3yRduM1L90vw/builds/nurfoRhrnvJl9ViWy/openapi.json
