# UK Disqualified Directors Delta (`titan_coder/uk-disqualified-directors-delta`) Actor

Monitors saved searches (name or company) against the UK Companies House Disqualified Officers Register and reports only officers newly matching your search since last check. A delta monitor, not a one-off lookup. Free when nothing new appears. Requires your own free chApiKey.

- **URL**: https://apify.com/titan\_coder/uk-disqualified-directors-delta.md
- **Developed by:** [Radu Furtuna](https://apify.com/titan_coder) (community)
- **Categories:** Business, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$10.00 / 1,000 new disqualification detecteds

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

## UK Disqualified Directors Delta

Monitors saved searches against the official **UK Companies House Disqualified Officers Register** and
reports only officers that are **newly matching** your search since the last check. This is a delta
monitor, not a one-off name lookup — other tools on the marketplace let you search the register once and
return everything that matches; this one remembers what it already found and only bills you for genuinely
new results on each scheduled run.

Free when nothing new appears. Requires your own free API key from Companies House.

### How it works

1. You give each watch a `query` — the same free-text search you'd type into the Companies House
   disqualified-officers search box: a person's full name, a surname, or a company name.
2. On every scheduled run the actor re-runs that exact search and compares the officer IDs it finds
   against a durable record of what it found last time.
3. Officers that are **new** since the last run are delivered as a row (full disqualification detail:
   type, dates, case identifier, company names) and billed once.
4. The very first run for a new watch is a **baseline** — it records what's currently in your search
   results but does not bill anything, since there's nothing to compare against yet.

### Getting a Companies House API key

Register a free account and application at
[developer.company-information.service.gov.uk](https://developer.company-information.service.gov.uk/),
create a **REST API key**, and paste it into the `chApiKey` field. Without a key the run exits
immediately — no requests made, nothing charged.

### Choosing a good query

Use a **narrow** query: a person's full name or a full company name. The actor scans up to 300 results
per search per run (the top of Companies House's relevance ranking for your query), which is normally far
more than enough for a specific name or company. A very broad query (a single letter, a very common
surname) can have more matches than that window — in that case the coverage record for that watch will
show `windowFull: true`, meaning the actor cannot guarantee every new match was seen (it may be ranked
outside the scanned window). This is a real, disclosed limitation of the search API, not a bug: Companies
House ranks search results by relevance to your query text, not by disqualification date, and there is no
official "recently added" endpoint available with a standard API key (only a separate real-time Streaming
API that this actor does not use).

**A watch that never sees its whole result set never starts monitoring — on purpose.** The very first run
for a new watch (the baseline) is a special case: if `windowFull` is true on that first run, the actor
*refuses to initialize the watch at all*. `coverage.watches[].reason` will read
`baseline_incomplete_window_full` and the digest will say the baseline was not built. This is deliberate,
not a bug: a baseline that only saw part of the matching set would later treat the records it missed as
"new" on some future run and bill you for officers who were disqualified before you ever started watching.
Every scheduled run honestly retries the baseline from scratch until one run's window is wide enough to
see the whole result set — **if your watch appears to be doing nothing run after run, this is why**: check
`coverage`/`digest` for `baseline_incomplete_window_full` and narrow the `query` (a full name instead of a
surname, a full company name instead of a fragment) so the very first run can complete.

Once a watch's baseline *has* been accepted, a later run whose scan is still `windowFull` behaves
differently: the watch keeps working normally (it is not re-baselined, and previously-seen officers are
never re-billed), but `coverage.watches[].reason` for that run will read `window_full` and the digest will
carry an explicit warning that the scan was incomplete — a genuinely new disqualification could exist in
the unscanned tail of results and simply not be visible to that run. Narrow the query if you see this
repeatedly.

### Input

- **monitorId** — a stable name for this monitor's history. Reuse it on every scheduled run.
- **watches** — 1-15 objects: `{"watchId": "acme-industries", "query": "Acme Industries Ltd"}`.
- **chApiKey** — your Companies House REST API key.
- **notifyOn** / **webhookUrl** — optional webhook with a digest of newly delivered disqualifications.

### Output

- **Dataset** — one row per newly matched disqualification: `watchId`, `officerId`, `kind`
  (natural|corporate), `name`, `disqualificationTypes`, `disqualifiedFrom`, `disqualifiedUntil`,
  `caseIdentifier`, `companyNames`, `reasonAct`/`reasonSection`, `entries` (the full disqualification
  history for that officer), `findAndUpdateUrl` (link to the public record). **The dataset is not purely
  one-row-per-disqualification**: every run also appends exactly one extra row shaped
  `{"_type": "run_summary", "status", "monitorId", "runId", "eventsBilled", "reason", "coverage", ...}` as
  an honest receipt of what that run did — present even on a run that delivered zero disqualification
  rows, so the dataset is never silently empty and you always have something to check a scheduled run
  against. If you consume this dataset programmatically, filter out `_type == "run_summary"` to get only
  disqualification rows.
- **Key-value store** — `coverage` (per-watch status, including `windowFull`, `partialReason`) and
  `digest` (a short human-readable summary).

### Known limitations (read before relying on this for compliance/KYB)

- **Not a legal finding or identity confirmation.** A name match in the register does not confirm the
  identity of a specific individual — common names can and do collide, and the register itself does not
  disambiguate beyond what Companies House publishes.
- **First appearance only.** The actor catches a disqualified officer the first time it appears in your
  search results. If that same officer later receives an *additional* disqualification entry, this is not
  re-detected or re-billed — this is a discovery monitor for new officers matching your query, not a
  lifecycle tracker of one already-known officer's ongoing case history.
- **Window completeness depends on query breadth** — see "Choosing a good query" above. `windowFull: true`
  in the coverage record is an honest signal, not an error.
- **At-most-once delivery, not exactly-once.** In the rare case of a crash between claiming a new result
  and confirming its delivery, that result may be permanently skipped rather than delivered or billed
  twice. You will never be charged twice for the same discovery.
- **No removal tracking.** If an officer's disqualification lapses or is later removed from the search
  index, this actor does not report that — it only reports new arrivals.

This actor is an informational monitor of the public Companies House Disqualified Officers Register. It is
not legal, credit, or compliance advice, and does not replace checking
[find-and-update.company-information.service.gov.uk](https://find-and-update.company-information.service.gov.uk/)
directly before any hiring, lending, or governance decision.

# Actor input Schema

## `monitorId` (type: `string`):

Name of this monitor's durable history (a-z, 0-9, dash; up to 40 chars). Reuse the same value on every scheduled run so the actor remembers which officers it already found for each saved search.

## `watches` (type: `array`):

1-15 objects: {"watchId": "acme-industries", "query": "Acme Industries Ltd"}. query is the same free-text search you would type into the Companies House disqualified-officers search (a person's full name, surname, or a company name) - the actor re-runs this exact search every scheduled run and reports only officers that are NEW in the results since the last run. Use a NARROW query (a full name or full company name, not a single letter or a very common surname): the actor scans up to 300 ranked results per search per run, and a query with more total matches than that cannot be guaranteed complete (see windowFull in the coverage record).

## `chApiKey` (type: `string`):

Required. Free key from developer.company-information.service.gov.uk (register an application, REST API key). Without it the run exits immediately with no requests made and nothing charged.

## `notifyOn` (type: `string`):

new\_alerts — post the webhook only when newly-matched disqualifications were delivered; always — post it every run; never — do not call webhookUrl at all.

## `webhookUrl` (type: `string`):

Optional. Receives a digest of delivered (paid) new disqualifications as JSON. HTTPS only.

## Actor input object example

```json
{
  "monitorId": "my-disqualification-watch",
  "watches": [
    {
      "watchId": "example-search",
      "query": "Jones Engineering"
    }
  ],
  "notifyOn": "new_alerts"
}
```

# Actor output Schema

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

Every newly matched disqualification this run delivered. Key fields: watchId, officerId, kind (natural|corporate), name, disqualificationTypes, disqualifiedFrom, disqualifiedUntil, caseIdentifier, companyNames, reasonAct, reasonSection, entries (full disqualifications\[] history for this officer), findAndUpdateUrl. Every run also appends exactly one extra row with \_type: "run\_summary" (status, monitorId, runId, eventsBilled, reason, coverage) as an honest receipt of what that run did - present even when zero disqualification rows were delivered, so the dataset is never silently empty. Filter out \_type == "run\_summary" if you only want disqualification rows. Informational only - not a legal finding or identity confirmation.

## `coverage` (type: `string`):

What this run actually covered and what it charged for: per-watch status/reason/matched/windowFull, records delivered and billed, requested/attempted/succeeded/failed watch counts. Enough to reconcile every charge against every row.

## `digest` (type: `string`):

A short human-readable summary of what this run found, written every run.

# 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 = {
    "monitorId": "my-disqualification-watch",
    "watches": [
        {
            "watchId": "example-search",
            "query": "Jones Engineering"
        }
    ],
    "chApiKey": "",
    "notifyOn": "new_alerts"
};

// Run the Actor and wait for it to finish
const run = await client.actor("titan_coder/uk-disqualified-directors-delta").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 = {
    "monitorId": "my-disqualification-watch",
    "watches": [{
            "watchId": "example-search",
            "query": "Jones Engineering",
        }],
    "chApiKey": "",
    "notifyOn": "new_alerts",
}

# Run the Actor and wait for it to finish
run = client.actor("titan_coder/uk-disqualified-directors-delta").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 '{
  "monitorId": "my-disqualification-watch",
  "watches": [
    {
      "watchId": "example-search",
      "query": "Jones Engineering"
    }
  ],
  "chApiKey": "",
  "notifyOn": "new_alerts"
}' |
apify call titan_coder/uk-disqualified-directors-delta --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,titan_coder/uk-disqualified-directors-delta"
        }
    }
}
```

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/bglCMMtXz8MQPkU9j/builds/Nl9nPdkH1aH65PQiN/openapi.json
