# ESMA MiFID Firm Permissions & Passporting Monitor (`automa-flow/esma-mifid-permissions-monitor`) Actor

Monitor EU/EEA MiFID investment firms for authorisation, investment-service permissions, passporting and ESMA sanction changes from the official registers. First run stores a baseline; later runs emit typed alerts.

- **URL**: https://apify.com/automa-flow/esma-mifid-permissions-monitor.md
- **Developed by:** [Vadim Bezrukov](https://apify.com/automa-flow) (community)
- **Categories:** Business, Automation, Developer tools
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 firm 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

## ESMA MiFID Firm Permissions & Passporting Monitor

Monitor EU/EEA MiFID investment firms for authorisation status, current
investment-service permissions, passporting/branch footprint and ESMA sanctions.
The first run stores a last-successful baseline; later runs emit typed changes
from ESMA's official machine-to-machine registers.

This Actor normalises public ESMA Registers information. It is not affiliated
with ESMA, does not use ESMA branding, and is not legal or compliance advice.
Verify legally consequential decisions against ESMA and the relevant national
competent authority.

### Check one firm

```json
{
  "mode": "watchlist",
  "monitorKey": "esma-demo",
  "firms": [
    {
      "entityId": "ae42",
      "lei": "529900RUQ6E2Z710XY62"
    }
  ],
  "includePermissions": true,
  "includePassporting": true,
  "includeSanctions": true,
  "changesOnly": false,
  "baselineMode": "emitSnapshot"
}
```

Open the Actor, keep this example and click **Start**. Expect a `BASELINE` row
for Catam Asset Management AG with current MiFID services; every later run of
this example writes the current record again because `changesOnly` is off here
(turn it on for a schedule that should stay quiet). The configured price
is **$0.01005** for that first verified firm (`apify-actor-start` +
`firm-checked`). No ESMA login or proxy is needed.

Keep the same `monitorKey` on a daily or weekly schedule. Unchanged successful
checks are still paid; a quiet run can have an empty Dataset - inspect `CHECKS`
and `RUN_SUMMARY`.

### Two modes

| Mode | Use it for | Result |
| --- | --- | --- |
| `watchlist` | Scheduled counterparty monitoring | First run `BASELINE`; later runs typed `CHANGE` rows |
| `lookup` | Agent/API current snapshot | One `SNAPSHOT` per firm; no monitor state |

Resolve each target in this order: `entityId` (`ae42` or `42`), exact LEI, then
exact name plus `homeMemberState`. If a name or LEI matches more than one head
office, the Actor returns `AMBIGUOUS_TARGET` with candidates instead of guessing.
Name matching requires exact normalised name and home state; multiple exact
name matches remain ambiguous even if one is a head office. Non-MiFID records
return `INVALID_INPUT`. Invalid items do not stop valid siblings: inspect the
per-input statuses in `CHECKS`. The same firm resolved through different
identifiers produces one observation and one paid firm check.

MCP / agent: ask “Check the current ESMA regulatory permissions of this
investment firm” via
<https://mcp.apify.com?tools=automa-flow/esma-mifid-permissions-monitor>.

### Example Tasks

Choose a saved example for the information you want to check:

- **Monitor MiFID permissions for regulated firms** - watch known entity ids for
  service grants and withdrawals.
- **Watch MiFID passporting and branch changes** - follow a head-office LEI for
  related branch records.
- **Look up an EU investment firm on ESMA registers** - one-shot snapshot, no
  monitor state.

### Sample output

First watchlist run (abridged):

```json
{
  "record_type": "BASELINE",
  "status": "FOUND",
  "entity_id": "ae42",
  "entity_name": "Catam Asset Management AG",
  "lei": "529900RUQ6E2Z710XY62",
  "authorisation_status": "Active",
  "change_types": ["BASELINE"],
  "source": "esma_registers_upreg",
  "scraped_at": "2026-09-13T12:00:00Z"
}
```

### Change types

`STATUS_CHANGED`, `AUTHORISATION_WITHDRAWN`, `AUTHORISATION_RESTORED`,
`SERVICE_GRANTED`, `SERVICE_WITHDRAWN`, `SERVICE_STATUS_CHANGED`,
`PASSPORT_HOST_ADDED`, `PASSPORT_HOST_REMOVED`, `BRANCH_ADDED`,
`BRANCH_REMOVED`, `BRANCH_STATUS_CHANGED`, `NEW_SANCTION`,
`SANCTION_UPDATED`, `ENTITY_METADATA_CHANGED`.

`ENTITY_NOT_FOUND` and `ENTITY_REAPPEARED` describe disappearance from, and
reappearance in, a verified register lookup. Neither proves that authorisation
was withdrawn or restored. Authorisation events require source status/date
evidence. `event_id` identifies an observation and is preserved during delivery
recovery; a later observation has a different ID.

Each change row includes `previous`, `current`, source evidence and
`observed_at`. A failed or partial source check never overwrites last-good
state and never invents a permission or passport withdrawal.

### Source

Public ESMA Registers A2A:

- `https://registers.esma.europa.eu/solr/esma_registers_upreg/select`
- `https://registers.esma.europa.eu/solr/esma_registers_sanctions/select`

Postal addresses and sanction decision text are omitted. Output is transformed
/ normalised from the register and must be labelled as such if republished.
Reproduction of register information is authorised by ESMA provided the source
is acknowledged (legal notice observed 2026-09-13).

### Schedule example

```json
{
  "mode": "watchlist",
  "monitorKey": "counterparties",
  "firms": [
    { "lei": "529900RUQ6E2Z710XY62" },
    { "entityId": "ae2978" }
  ],
  "changesOnly": true,
  "baselineMode": "emitSnapshot"
}
```

Configure an `ACTOR.RUN.SUCCEEDED` webhook. Its receiver can fetch that run's
Dataset, select rows with `record_type=CHANGE`, and deduplicate alerts by
`event_id`.

### Pricing

Pay-per-event; platform usage is included:

- `apify-actor-start` $0.00005 once the run starts (512 MB = one start event)
- `firm-checked` $0.01 per unique firm that is `FOUND` or authoritatively
  `NOT_FOUND`

A first verified firm therefore costs **$0.01005**. Set `maxTotalChargeUsd` to
at least **$0.011**. The default run cap is **$1**, enough for about 99
verified firms plus start. Raise it before larger watchlists; 500 firms need
about **$5.01**. Later checks of the same firms are charged even when nothing changed.

Not charged: retries, ambiguous names, invalid input, source failures, partial
child retrieval, duplicate targets, individual change events, recovery-only
delivery.

### Limitations

- MiFID passporting in this register is branch presence associated by exact LEI
  or head-office LEI, including branches that carry their own LEI while pointing
  at the watched head office. Freedom-of-services child documents currently
  belong to crowdfunding (CSP) entities, not MIF.
- Current `aeActivity` rows are Active; withdrawn services appear in activity
  history. Missing children after a transport failure are `PARTIAL`, not
  withdrawals. An incomplete dimension is omitted (`null`), not published as an
  empty list.
- HTTP 400 and other non-retryable Solr errors set `error.retryable=false`.
  A mixed batch with some `NOT_FOUND` and some `SOURCE_FAILED` items finishes
  as `PARTIAL`; the run is `SOURCE_FAILED` only when every unique firm failed.
- HTTP 408/425/429/5xx, transport errors, transient HTML/invalid JSON and
  retryable Solr pagination inconsistencies use bounded backoff. A pagination
  retry restarts at page zero so documents from different source snapshots are
  never combined. Persistent failures remain explicit and never establish that
  a firm, service or branch is absent.
- Parent lookups, exact-name lookups and independent permission/passporting/
  sanction dimensions run concurrently behind a four-request source limit.
  This keeps large mixed watchlists inside the run timeout without increasing
  peak request concurrency.
- Overlapping runs for the same `monitorKey` wait up to 30 seconds for the
  active run. An unexpected interruption keeps the fail-closed marker because
  a stale writer may resume; stop all runs for that key before deleting only
  its lock request. Never delete the KVS baseline.
- If Dataset delivery fails after ESMA was observed, the run stays `FAILED`
  with `DATASET_DELIVERY_FAILED`, no custom charge, and the monitor lock is
  released so the next run can recover `pendingDelivery`.
- `lookup` does not open shared monitor storage. Delivery recovery reports zero
  new charges and points to the recovery run's Dataset; original charge details
  remain under `original_billing`.
- `registers.esma.europa.eu/robots.txt` currently returns HTML (HTTP 200)
  rather than a crawl policy; this Actor only calls the published A2A Solr
  endpoints.
- The Console example runs with `changesOnly: false`, so successful checks appear in the Dataset: the first run emits `BASELINE`, later runs a `SNAPSHOT` per firm. A
  `changesOnly` run with no diffs can be empty; `RUN_SUMMARY` still reports
  verified counts.

# Actor input Schema

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

watchlist compares each firm with last successful state. lookup returns the current snapshot only and does not write monitor state.

## `monitorKey` (type: `string`):

Use one key per watchlist. The same key compares against its last verified state. lookup ignores stored state.

## `firms` (type: `array`):

Up to 500 firms. Resolve in this order: entityId (ae42 or 42), exact LEI, then exact name plus homeMemberState. Duplicate identifiers are fetched once. Ambiguous names are returned, not guessed.

## `includePermissions` (type: `boolean`):

Current MiFID services and activity history from ESMA. Keep this stable for a monitor key so missing dimensions are not treated as withdrawals.

## `includePassporting` (type: `boolean`):

Related MiFID branch records associated by exact LEI or head-office LEI. Freedom-of-services child documents are included only when ESMA attaches them to the watched entity.

## `includeSanctions` (type: `boolean`):

Sanctions register rows bound by sn\_entityEsmaID to the watched firm's ae\_dbId. Decision text is omitted.

## `changesOnly` (type: `boolean`):

When true, later watchlist runs emit baselines, changes and diagnostics. Unchanged firms stay in CHECKS. lookup always emits snapshots. The example keeps it off so every run shows the current record.

## `baselineMode` (type: `string`):

emitSnapshot returns each firm's first verified observation, including the Console prefill. storeOnly saves that baseline without Dataset rows.

## Actor input object example

```json
{
  "mode": "watchlist",
  "monitorKey": "esma-demo",
  "firms": [
    {
      "entityId": "ae42"
    },
    {
      "lei": "529900RUQ6E2Z710XY62"
    },
    {
      "name": "Catam Asset Management AG",
      "homeMemberState": "LIECHTENSTEIN"
    }
  ],
  "includePermissions": true,
  "includePassporting": true,
  "includeSanctions": true,
  "changesOnly": true,
  "baselineMode": "emitSnapshot"
}
```

# Actor output Schema

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

No description

## `changes` (type: `string`):

One row per change type. Filter record\_type=CHANGE for alerts.

## `checks` (type: `string`):

Includes duplicates, invalid entries, unchanged firms and inputs skipped before source access.

## `runSummary` (type: `string`):

Verification counts, accepted event charges, budget refusals and recovery status.

# 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 = {
    "mode": "watchlist",
    "monitorKey": "esma-demo",
    "firms": [
        {
            "entityId": "ae42",
            "lei": "529900RUQ6E2Z710XY62"
        }
    ],
    "includePermissions": true,
    "includePassporting": true,
    "includeSanctions": true,
    "changesOnly": false,
    "baselineMode": "emitSnapshot"
};

// Run the Actor and wait for it to finish
const run = await client.actor("automa-flow/esma-mifid-permissions-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 = {
    "mode": "watchlist",
    "monitorKey": "esma-demo",
    "firms": [{
            "entityId": "ae42",
            "lei": "529900RUQ6E2Z710XY62",
        }],
    "includePermissions": True,
    "includePassporting": True,
    "includeSanctions": True,
    "changesOnly": False,
    "baselineMode": "emitSnapshot",
}

# Run the Actor and wait for it to finish
run = client.actor("automa-flow/esma-mifid-permissions-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 '{
  "mode": "watchlist",
  "monitorKey": "esma-demo",
  "firms": [
    {
      "entityId": "ae42",
      "lei": "529900RUQ6E2Z710XY62"
    }
  ],
  "includePermissions": true,
  "includePassporting": true,
  "includeSanctions": true,
  "changesOnly": false,
  "baselineMode": "emitSnapshot"
}' |
apify call automa-flow/esma-mifid-permissions-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,automa-flow/esma-mifid-permissions-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/dP8Oxg5xtPX2pgtQw/builds/NQpOe9CZOztG1Tonb/openapi.json
