# UK Regulatory Compliance Status Watchdog (`conceivable_extension/uk-compliance-status-watchdog`) Actor

Monitors a fixed watch list of known UK businesses for regulatory status changes — Companies House filing status/dissolution, CQC inspection ratings, and FCA firm registration status. A compliance monitor for suppliers and partners you already know, not a lead-generation tool.

- **URL**: https://apify.com/conceivable\_extension/uk-compliance-status-watchdog.md
- **Developed by:** [joseph fadero](https://apify.com/conceivable_extension) (community)
- **Categories:** Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $3.00 / 1,000 entity checked no changes

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?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
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.
Actors are written with capital "A".

## 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.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## UK Regulatory Compliance Status Watchdog

**Two modes, two different jobs, two different buyers — on the same Companies House data. Pick one per run.**

- **`watchlist_monitor`** (default, original): a compliance monitor for businesses you already know about. You give it a **fixed watch list** of suppliers, partners, or portfolio companies you already have a relationship with, and it tells you when their regulatory status changes — a dissolution, a CQC rating drop, an FCA firm losing authorisation.
- **`prospect_discovery`** (addendum): a lead-gen feed. You give it a region and/or SIC-code scope, and it *finds* new signal-worthy companies — fresh incorporations, new director appointments, new charges, SIC-code pivots, unusually high filing activity, or dissolution risk — rather than monitoring entities you already track.

Do not let discovery-mode framing bleed into the compliance-monitoring output existing `watchlist_monitor` buyers depend on — the two output schemas are entirely separate (`ComplianceReport` vs. `DiscoverySignal`), and this README keeps them in their own sections below.

If you're looking to *find* new UK business prospects but don't need the signal-classification/director-background layer this actor's `prospect_discovery` mode adds, [UK Business Leads](../UK%20Local%20Business%20Leads/uk-local-business-leads) (Actor 3) is a simpler Google-Maps-plus-Companies-House sourcing tool.

### Who it's for

- **`watchlist_monitor`**: compliance/risk teams, insurers, franchisors, or any B2B business that needs to know when a supplier's or partner's regulatory standing changes — before it becomes their problem too.
- **`prospect_discovery`**: sales/lead-gen teams (agencies, B2B vendors, investors) looking for a live feed of newly signal-worthy UK companies in a target sector/region — not a compliance concern at all.

### What it checks

| Source | What it monitors | Watch-list field needed |
|---|---|---|
| Companies House | Filing status, dissolution | `companyNumber` |
| CQC (care sector) | Inspection rating | `cqcLocationId` |
| FCA | Firm registration/authorisation status | `fcaFirmReferenceNumber` |

Each is a separate, independently-failing module — a missing FCA credential doesn't stop the Companies House or CQC checks from running.

### Credentials — read this before running

All three of these are free public APIs, but "free" doesn't mean keyless:

- **Companies House** — requires a free registered API key (developer.company-information.service.gov.uk), sent as HTTP Basic Auth. Any watch-list entry with `checkCompaniesHouse: true` is skipped with a clear message if `companiesHouseApiKey` isn't set — it doesn't waste a call on a request that would just 401.
- **CQC** — some public endpoints work without a key; others are gated behind a free subscription key (api-portal.service.cqc.org.uk). This actor always attempts the call and only asks for `cqcSubscriptionKey` if the API itself returns a 401/403, rather than assuming one way or the other.
- **FCA** — always requires a free email+key credential pair (register.fca.org.uk/Developer), sent as `X-Auth-Email`/`X-Auth-Key`. Skipped upfront with a clear message if `fcaAuthEmail`/`fcaAuthKey` aren't set.

### A gap worth knowing about: CCJs

The output schema's `changeType` enum includes `ccj` (County Court Judgment), but **this actor never populates it**. CCJ data against UK companies is not part of Companies House's free public API — it sits with Registry Trust and commercial credit bureaus behind a paid feed. What Companies House *does* expose for free is dissolution and general filing-status changes, which is what the `companies_house` source actually monitors. If CCJ monitoring is a hard requirement, it needs a paid third-party data source layered on top — flagging this now rather than silently shipping a `ccj` field that never fires.

### `watchlist_monitor` — input schema

```json
{
  "mode": "watchlist_monitor",
  "watchList": [
    { "companyNumber": "12345678", "checkCompaniesHouse": true, "checkCQC": false, "checkFCA": false },
    { "cqcLocationId": "1-XXXXXXXXX", "checkCQC": true }
  ],
  "alertOnAnyChange": false,
  "companiesHouseApiKey": "your-key",
  "cqcSubscriptionKey": "your-key (optional)",
  "fcaAuthEmail": "your-email",
  "fcaAuthKey": "your-key"
}
```

`alertOnAnyChange` — off (default) pushes a dataset row only when a status change is actually detected against the last run. On, it pushes a full status row for every watched entity every run, useful for building a complete audit trail rather than just a change log.

### `watchlist_monitor` — output schema

```json
{
  "entityId": "string",
  "entityName": "string",
  "source": "companies_house | cqc | fca",
  "previousStatus": "string | null",
  "currentStatus": "string | null",
  "changeDetected": "boolean",
  "changeType": "filing_status | dissolution | cqc_rating_change | fca_registration_change | null",
  "checkedAt": "ISO timestamp",
  "detailsUrl": "string",
  "status": "success | failed | skipped",
  "errorMessage": "string | null"
}
```

`previousStatus` is `null` on an entity's first-ever check (nothing to diff against yet) — that's expected, not a failure. Status is persisted between runs in the actor's Key-Value Store (named `uk-compliance-status-store`), same pattern as Actor 12's BSR history tracking. **A real, previously-flagged bug was fixed during this addendum's build**: the store was being opened with no name (`Actor.openKeyValueStore()`), which scopes it to the current run only and silently never persists across runs — meaning `previousStatus` would have always come back `null` in production, defeating this actor's entire premise. Confirmed live: two real runs against the real Companies House API, with the same real company, correctly showed `previousStatus: null` on the first run and `previousStatus: "active"` on the second.

### `watchlist_monitor` — pricing

| Event | Price |
|---|---|
| Run started | £0.05 |
| Entity checked (no change) | £0.005 |
| Change detected | £0.02 |
| Check failed / skipped | free |

### Prospect Discovery Mode (addendum)

Reuses the same Companies House REST API and diffing engine as `watchlist_monitor` — the change is what you're diffing against (a broad region/SIC-code sweep via the real Advanced Search API, `advanced-search/companies`) instead of a fixed watch list, and what you do with a detected change (surface it as a lead signal, not a compliance alert).

#### Input schema

```json
{
  "mode": "prospect_discovery",
  "discoveryFilters": {
    "region": "Cambridge",
    "sicCodes": ["62012"],
    "signalTypes": ["new_incorporation", "director_change", "new_charge", "sic_change", "rapid_filing_activity", "dissolution_risk"],
    "minCompanyAge": 0,
    "maxCompanyAge": 7,
    "maxCompanies": 50
  },
  "companiesHouseApiKey": "your-key"
}
```

At least one of `region`/`sicCodes` is required — an unscoped sweep genuinely matches hundreds of thousands of companies (confirmed live: `sic_codes=62012` alone returns 215,343 hits with no date/region narrowing). `signalTypes` defaults to `["new_incorporation"]` if omitted, since it's the cheapest signal to compute (no extra API calls beyond the sweep itself) — the other five each cost one or more additional real Companies House calls per company, only made when actually requested. `maxCompanyAge`/`minCompanyAge` are in days since incorporation and default to a 7-day window, matching this addendum's own worked example. `maxCompanies` is a safety cap not in the addendum's own field list — added because nothing else bounds how many companies a broad sweep could return; defaults to 50, hard-capped at 200 regardless of what's set.

**`region` is a real-but-imprecise filter — not an exact match.** Companies House's own `location` search parameter is a relevance-ranked text search (confirmed live: `location=Cambridge` returned ~77% genuine Cambridge results mixed with some from other towns), not a hard filter. This actor uses it as a real pre-filter for efficiency, then applies an exact client-side check against each result's registered-office locality on top — so output is precise, but the region parameter alone, if you were calling Companies House directly, would not be.

#### Output schema

```json
{
  "companyNumber": "string",
  "companyName": "string",
  "signalType": "new_incorporation | director_change | new_charge | sic_change | rapid_filing_activity | dissolution_risk",
  "signalDetectedDate": "ISO timestamp",
  "signalStrength": "high | medium | low",
  "directorBackground": "string | null",
  "plainEnglishSummary": "string",
  "detailsUrl": "string"
}
```

`directorBackground` and the per-row `plainEnglishSummary` are populated for `new_incorporation` and `director_change` signals where a director's most recent other directorship was found (capped to one prior appointment per officer, to bound API calls on a real sweep) — `null` when the director has no other appointments (a real, common, correct case for a brand-new company's first-ever director, not a bug). "Relevant experience" means the prior company shares a SIC code with the newly-discovered one.

In addition to the per-row schema above, each run logs one **aggregate run-level summary** in the addendum's own worked-example style ("34 new technology companies incorporated... 11 have directors with prior technology-company experience") — deterministically built from the run's real signal counts (not an LLM call; this actor has no AI dependency and none was added for this), with sentence structure that genuinely varies by what was actually found, not one fixed template with numbers substituted in.

#### `director_change` / `sic_change` / `new_charge` are diff-based, same as `watchlist_monitor`

These three need a real previous snapshot to compare against, stored in a separate named KV store (`uk-compliance-discovery-snapshots`) keyed per company and per field. **A real bug was caught and fixed during this addendum's own live testing, before it shipped**: reading and writing that snapshot separately for each requested signal type meant an earlier signal type's write (e.g. `sic_change` saving an untouched `activeDirectorNames: []` default alongside its own real SIC-code update) leaked into a later signal type's "previous state" read within the *same* run — making a company's very first-ever look register every current director as newly appointed. Fixed by loading the snapshot once per company, diffing all requested fields against that single load, and writing once at the end — with per-field `undefined` (never captured) kept distinct from an empty array (captured, genuinely empty). Verified live with three real runs: baseline (no false positives), unchanged rerun (still no false positives), and a rerun against a deliberately altered snapshot (correctly detected both a real `director_change` and `sic_change`).

`new_incorporation`, `rapid_filing_activity`, and `dissolution_risk` are computed directly each run (not diff-based) — they'll fire consistently for a matching company on every run within their respective windows, which is correct, not a bug.

#### Pricing

Rides the existing `run-started` event. One new PPE event: `signal-detected` — needs pricing configured in the Apify Console alongside the existing three.

### Setup note

No browser needed — plain Node actor (`apify/actor-node:20`), all sources (Companies House, CQC, FCA, and the addendum's Advanced Search/Officers/Charges/Filing-History endpoints) are REST APIs called via axios.

### n8n integration

- **Workflow A (trigger):** scheduled run against your watch list — weekly for Companies House/FCA, monthly for CQC (inspection ratings change far less often). For `prospect_discovery`, a daily or weekly sweep of your target region/SIC scope.
- **Workflow B (processing):** for `watchlist_monitor`, alert on any `changeDetected: true`, routed by `changeType` — dissolution goes urgent, rating/registration changes go informational. For `prospect_discovery`, route by `signalStrength` into a lead queue.

# Actor input Schema

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

watchlist\_monitor (default): compliance monitoring for a fixed list of known entities — insurance/risk buyers. prospect\_discovery (addendum): sweep a region/SIC-code slice of Companies House for new signal-worthy companies — lead-gen buyers. These are two different jobs on the same data; pick one per run.

## `watchList` (type: `array`):

Required when mode is watchlist\_monitor. One entry per entity to monitor. Fields: companyNumber (Companies House), cqcLocationId (CQC), fcaFirmReferenceNumber (FCA), entityName (optional, for readable output), checkCompaniesHouse/checkCQC/checkFCA (booleans — only run the checks you have the matching ID for). Example: \[{ "companyNumber": "12345678", "checkCompaniesHouse": true }, { "cqcLocationId": "1-XXXXXXXXX", "checkCQC": true }]

## `discoveryFilters` (type: `object`):

Required when mode is prospect\_discovery. Needs at least one of region or sicCodes. { region?: string, sicCodes?: string\[], signalTypes?: ("new\_incorporation"|"director\_change"|"new\_charge"|"sic\_change"|"rapid\_filing\_activity"|"dissolution\_risk")\[] (default: \["new\_incorporation"]), minCompanyAge?: number (days, default 0), maxCompanyAge?: number (days, default 7), maxCompanies?: number (default 50, hard-capped at 200 — an unscoped sweep can match hundreds of thousands of companies) }. `region` is a real-but-imprecise text match against Companies House's own search (confirmed live) plus an exact client-side locality check on top — see README.

## `alertOnAnyChange` (type: `boolean`):

Off (default): only push a dataset row when a status change is actually detected. On: push a full status row for every watched entity on every run, not just changes.

## `companiesHouseApiKey` (type: `string`):

Free key from developer.company-information.service.gov.uk. Required for any watchList entry with checkCompaniesHouse: true — the API rejects unauthenticated requests.

## `cqcSubscriptionKey` (type: `string`):

Optional key from api-portal.service.cqc.org.uk. Some CQC public endpoints work without one; if checks fail with an auth error, register for a free key and set this.

## `fcaAuthEmail` (type: `string`):

Free credential pair from register.fca.org.uk/Developer. Required for any watchList entry with checkFCA: true.

## `fcaAuthKey` (type: `string`):

Paired with fcaAuthEmail. Required for any watchList entry with checkFCA: true.

## Actor input object example

```json
{
  "mode": "watchlist_monitor",
  "watchList": [
    {
      "companyNumber": "12345678",
      "checkCompaniesHouse": true,
      "checkCQC": false,
      "checkFCA": false
    },
    {
      "cqcLocationId": "1-XXXXXXXXX",
      "checkCQC": true
    }
  ],
  "discoveryFilters": {
    "region": "Cambridge",
    "sicCodes": [
      "62012"
    ],
    "signalTypes": [
      "new_incorporation"
    ],
    "maxCompanyAge": 7,
    "maxCompanies": 25
  },
  "alertOnAnyChange": false
}
```

# Actor output Schema

## `resultsDatasetUrl` (type: `string`):

Companies House-derived compliance status changes for watchlisted entities, or newly discovered signal-worthy companies in prospect-discovery mode, produced by this 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 = {
    "watchList": [
        {
            "companyNumber": "12345678",
            "checkCompaniesHouse": true,
            "checkCQC": false,
            "checkFCA": false
        },
        {
            "cqcLocationId": "1-XXXXXXXXX",
            "checkCQC": true
        }
    ],
    "discoveryFilters": {
        "region": "Cambridge",
        "sicCodes": [
            "62012"
        ],
        "signalTypes": [
            "new_incorporation"
        ],
        "maxCompanyAge": 7,
        "maxCompanies": 25
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("conceivable_extension/uk-compliance-status-watchdog").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 = {
    "watchList": [
        {
            "companyNumber": "12345678",
            "checkCompaniesHouse": True,
            "checkCQC": False,
            "checkFCA": False,
        },
        {
            "cqcLocationId": "1-XXXXXXXXX",
            "checkCQC": True,
        },
    ],
    "discoveryFilters": {
        "region": "Cambridge",
        "sicCodes": ["62012"],
        "signalTypes": ["new_incorporation"],
        "maxCompanyAge": 7,
        "maxCompanies": 25,
    },
}

# Run the Actor and wait for it to finish
run = client.actor("conceivable_extension/uk-compliance-status-watchdog").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 '{
  "watchList": [
    {
      "companyNumber": "12345678",
      "checkCompaniesHouse": true,
      "checkCQC": false,
      "checkFCA": false
    },
    {
      "cqcLocationId": "1-XXXXXXXXX",
      "checkCQC": true
    }
  ],
  "discoveryFilters": {
    "region": "Cambridge",
    "sicCodes": [
      "62012"
    ],
    "signalTypes": [
      "new_incorporation"
    ],
    "maxCompanyAge": 7,
    "maxCompanies": 25
  }
}' |
apify call conceivable_extension/uk-compliance-status-watchdog --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,conceivable_extension/uk-compliance-status-watchdog"
        }
    }
}

```

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/46439AUCnTday3Nbd/builds/YkDrY7PkPlGsnmfPU/openapi.json
