# UK Companies House Streaming Change Monitor (`plym-actor-factory/uk-companies-house-realtime-monitor`) Actor

Watch UK companies for filings, officers, PSC, charges, insolvency and profile changes via the official Companies House Streaming API. Near-real-time scheduled catch-up with resumable timepoints, typed events, pay only for delivered unique changes ($0.015/event).

- **URL**: https://apify.com/plym-actor-factory/uk-companies-house-realtime-monitor.md
- **Developed by:** [Daniel Witney](https://apify.com/plym-actor-factory) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

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

## UK Companies House Streaming Change Monitor

Watch a portfolio of UK companies and receive **typed change events** when Companies House publishes updates — filings, officers, persons with significant control (PSC), charges, insolvency cases, and company profile fields — via the **official Companies House Streaming API**.

### Value proposition

- Catch up on official stream queues with **resumable timepoints** (not HTML scraping, not “diff two REST snapshots and hope”).
- Get **structured events** (`NEW_FILING`, `OFFICER_*`, `PSC_*`, `STATUS_CHANGED`, …) ready for Slack/webhooks/CRM — not opaque blobs.
- **Pay only when we deliver a unique watchlist change.** Quiet schedules stay cheap.
- One short scheduled run can drain **multiple streams** with durable state.

### How it works (Approach A)

1. Schedule the Actor (e.g. every 5–15 minutes) with a watchlist of `companyNumbers`.
2. Each run connects briefly to the selected Streaming API endpoints, resumes from stored `timepoint`s, and processes NDJSON until time/event/idle budgets.
3. Events for companies **on your watchlist** are normalized, deduplicated (`event_uid`), written to the dataset, and charged once under pay-per-event.
4. Timepoints are saved **only after successful processing** so a crash does not skip unpaid work incorrectly.
5. Every **successful** run also writes one non-billable **`RUN_STATUS`** health row so quiet / Store auto-test runs still leave a non-empty dataset.

This is **near-real-time catch-up**, not a guaranteed instant push consumer. Detection latency ≈ your schedule interval + stream lag. Companies House allows a maximum of **two concurrent streaming connections** per account; this design avoids forever-open connections.

### 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** a Streaming key. That fixture path only writes a non-billable `RUN_STATUS` / `FIXTURE_HEALTH` row. It is **not** Companies House monitoring.

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

1. Set **`sourceMode` = `live`**
2. Provide secret input **`companiesHouseStreamingApiKey`** — a Companies House **Streaming API** key (**not** the free REST API key; they are different products)
3. Schedule short runs (e.g. every 5–15 minutes) with your `companyNumbers` watchlist

Official docs: [Companies House Streaming API overview](https://developer-specs.company-information.service.gov.uk/streaming-api/guides/overview) · [Developer hub](https://developer.company-information.service.gov.uk/)

Detection latency ≈ **your schedule interval + stream lag**. This Actor does **not** claim instant delivery or that it never misses an event (416 stale timepoints require resync; overlapping live runs can hit the 2-connection CH limit).

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

Apify Store runs Actors daily with **schema default input**. Defaults are chosen so that path:

| Field | Default |
|-------|---------|
| `companyNumbers` | `["00000006","SC123456"]` |
| `streams` | all 7 supported streams |
| `maxRunSeconds` | `55` |
| `maxEvents` | `500` |
| `resumeFromStoredTimepoints` | `true` |
| `sourceMode` | `fixture` |
| `emitFixtureDemoEvents` | `false` |
| `maxIdleHeartbeats` | `3` |

On that path the Actor:

- **SUCCEEDS** quickly (no CH network call)
- Pushes exactly one dataset row: `event_type: "RUN_STATUS"`, `event_subtype: "FIXTURE_HEALTH"`, `billable: false`
- Does **not** call `Actor.charge` / does **not** emit fabricated filing/officer/PSC/company changes
- Leaves a **non-empty** default dataset within well under 5 minutes

`emitFixtureDemoEvents: true` is only for unit/local demos of typed change shapes; leave it **false** on Store and in production.

### Features

- Streams: `companies`, `filings`, `officers`, `persons-with-significant-control`, `persons-with-significant-control-statements`, `charges`, `insolvency-cases`
- Watchlist filter with company-number normalization (trim, upper case, zero-pad)
- Durable timepoints + seen `event_uid`s in the default Key-Value Store
- `sourceMode: fixture | live` — fixture default for Store auto-test / local CI without a streaming key
- Always-on non-billable `RUN_STATUS` health row (`HEALTH_OK` | `MONITOR_IDLE` | `CATCHUP_COMPLETE` | `FIXTURE_HEALTH`)
- Handles heartbeats (blank lines), 429 backoff guidance, 416 resync guidance
- Secret input for Streaming API key (never logged)

### Input (summary)

| Field | Notes |
|-------|--------|
| `companyNumbers` | Watchlist (required) |
| `streams` | Subset of streams (default: all supported) |
| `maxRunSeconds` | Wall-clock budget (default 55) |
| `maxEvents` | Max delivered **change** events per run |
| `resumeFromStoredTimepoints` | Default true |
| `sourceMode` | `fixture` (default, Store-safe) or `live` (production) |
| `emitFixtureDemoEvents` | Default **false**; gate for fabricated fixture changes |
| `companiesHouseStreamingApiKey` | Secret; Streaming key only — **not** the REST key |
| `maxTotalChargeUsd` | Optional budget hint |
| `maxIdleHeartbeats` | Leave a quiet stream after N blank heartbeats |

#### Production example

```json
{
  "companyNumbers": ["00000006", "SC123456"],
  "streams": ["filings", "officers", "charges"],
  "maxRunSeconds": 55,
  "sourceMode": "live",
  "companiesHouseStreamingApiKey": "YOUR_STREAMING_KEY",
  "resumeFromStoredTimepoints": true
}
```

### Output

#### Change events (billable under PPE)

Dataset rows for real CH changes include: `company_number`, `company_name`, `event_type`, `event_subtype`, `changed_fields`, `published_at`, `effective_date`, `resource_kind`, `resource_id`, `summary`, `source_url`, `raw_source_reference`, `detected_at`, `event_uid`, `billable: true`.

#### RUN\_STATUS health row (never billed)

Every successful run also pushes one row:

| Field | Value |
|-------|--------|
| `event_type` | `RUN_STATUS` |
| `event_subtype` | `HEALTH_OK` | `MONITOR_IDLE` | `CATCHUP_COMPLETE` | `FIXTURE_HEALTH` |
| `billable` | `false` |
| `summary` | Honest counts: streams processed, events read/matched/delivered/charged, timepoints ok |
| `resource_kind` | `actor-run-status` |

Quiet live runs (no watchlist matches) still **SUCCEED** with a `MONITOR_IDLE` (or similar) `RUN_STATUS` row — the dataset is never empty on success.

### Pricing

Pay-per-event: **`change-event-delivered`** at **$0.015** per delivered unique watchlist change.

**Never charged:** `RUN_STATUS` health rows, no-change runs, invalid company numbers, duplicates (`event_uid`), heartbeats, retries, filtered non-watchlist traffic, or fixture Store-health runs.

### Limitations (honest)

- Near-real-time **catch-up**, not guaranteed sub-second delivery.
- Live mode needs a **Companies House Streaming API** key (separate from the free REST key).
- If a stored `timepoint` is too old, CH returns **416** — clear state and resync from an official snapshot if you need historical continuity.
- Max **two** concurrent CH streaming connections per account; do not run many overlapping live actors with the same key.
- Fixture mode does not contact Companies House; with default `emitFixtureDemoEvents=false` it only writes `RUN_STATUS`.

### Data source

[Companies House Streaming API](https://developer-specs.company-information.service.gov.uk/streaming-api/guides/overview) — Crown copyright public data. Respect rate limits and attribution.

### FAQ

**Do I need an API key for local / Store default tests?**\
No — use `sourceMode: "fixture"` (the schema default). You get a `RUN_STATUS` / `FIXTURE_HEALTH` row only.

**How do I run production monitoring?**\
Set `sourceMode: "live"` and your Streaming API key as a secret input.

**Can I use my REST API key?**\
No. Streaming uses a distinct streaming key. Do not put either key in git.

**Will I be charged on quiet days?**\
No — only delivered unique watchlist changes. `RUN_STATUS` is free.

**Is this an always-on stream?**\
No. Short scheduled runs by design.

### MCP / API notes

Run via Apify API, Scheduler, or MCP (`https://mcp.apify.com`) like any Actor. Pass the streaming key as a **secret input** field; never embed it in prompts or repos.

# Actor input Schema

## `companyNumbers` (type: `array`):

UK Companies House numbers to watch (spaces ignored; numeric padded to 8). Invalid entries are skipped with no charge.

## `streams` (type: `array`):

Streaming API paths to catch up in this run. Allowed: companies, filings, officers, persons-with-significant-control, persons-with-significant-control-statements, charges, insolvency-cases. Default: all supported streams. Unknown names are rejected at runtime.

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

Wall-clock budget for this scheduled catch-up (Approach A). Prefer short runs (e.g. 45–90s) on a schedule.

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

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

## `resumeFromStoredTimepoints` (type: `boolean`):

If true, resume each stream from the last successfully processed timepoint in the default Key-Value Store.

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

fixture = local/CI / Apify Store daily auto-test default (no Streaming key; emits RUN\_STATUS only unless emitFixtureDemoEvents=true). live = official Streaming API — production users must set this and provide companiesHouseStreamingApiKey.

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

When sourceMode=fixture, if true push fabricated NDJSON change events (unit/local demos only). Default false so Store daily auto-tests never emit fake CH changes or charge change-event-delivered. Production: leave false and use sourceMode=live.

## `companiesHouseStreamingApiKey` (type: `string`):

Secret Streaming API key (Basic auth). NOT the REST API key. Never logged. Required only when sourceMode=live.

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

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

## `maxIdleHeartbeats` (type: `integer`):

Leave a stream after this many consecutive blank heartbeat lines with no data (within the run).

## Actor input object example

```json
{
  "companyNumbers": [
    "00000006",
    "SC123456"
  ],
  "streams": [
    "companies",
    "filings",
    "officers",
    "persons-with-significant-control",
    "persons-with-significant-control-statements",
    "charges",
    "insolvency-cases"
  ],
  "maxRunSeconds": 55,
  "maxEvents": 500,
  "resumeFromStoredTimepoints": true,
  "sourceMode": "fixture",
  "emitFixtureDemoEvents": false,
  "maxIdleHeartbeats": 3
}
```

# Actor output Schema

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

JSON summary: delivered, charged, runStatus, timepoints, stats

# 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/uk-companies-house-realtime-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 = {}

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

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,plym-actor-factory/uk-companies-house-realtime-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/7G5IHQ8n1XZ5lYK2E/builds/W7M31GlBDAJRpFJmi/openapi.json
