# Companies House PSC Changes - UK Beneficial Owner Monitor (`thatmike1/companies-house-psc-changes`) Actor

Watch UK companies for changes in persons with significant control: added, ceased and changed PSCs with old values, from the public Companies House register, no API key.

- **URL**: https://apify.com/thatmike1/companies-house-psc-changes.md
- **Developed by:** [Michal Pšenčík](https://apify.com/thatmike1) (community)
- **Categories:** Business, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.005 / actor start

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

## Companies House PSC Changes

Check a watchlist of UK companies for changes to their persons with significant control (PSCs).
The actor reads the public Companies House register pages. No API key or proxy is needed.

Each delivered row contains one company's current PSC list and its changes since the last successful
check in the same named watch. It is intended for small KYB and supplier-risk teams checking roughly
100 to 1,000 companies daily.

### Input

```json
{
    "companyNumbers": ["08892100", "00519500", "00445790"],
    "watchName": "default",
    "onlyChanged": false,
    "includeCeased": true,
    "includeCorrespondenceAddress": false,
    "maxCompanies": 1000
}
```

- `companyNumbers`: required string list. Numeric values are zero-padded to eight characters;
  prefixes such as SC, NI and OC are uppercased and retained. Duplicate numbers are checked once.
  Invalid entries appear only in the free summary.
- `watchName`: defaults to `default`. Reuse the exact name on subsequent runs to compare changes.
  A different name creates an independent baseline. Named stores persist across runs; do not run
  the same watch concurrently. Watch names are hashed into storage-safe names.
- `onlyChanged`: defaults to false. Set true to deliver only first checks and changes. Unchanged
  checks still use network requests but generate no item charge.
- `includeCeased`: defaults to true. False filters ceased PSCs from `pscs`, while complete history
  remains in watch state and relevant changes still appear in `changes`.
- `includeCorrespondenceAddress`: defaults to false. Opt in only if you need these personal data.
- `maxCompanies`: defaults to 1,000 distinct valid companies per run, in input order. Zero removes
  this limit. The summary reports valid companies omitted by it.

### Output and change detection

The default dataset contains billable company checks only:

- `companyNumber`, `companyName`, `sourceUrl` and `checkedAt`.
- `pscStatus`: `has_psc` if the register lists active or historical PSCs, `exempt` if it lists
  an exemption, or `no_psc` when it lists none and no exemption. `exemptionIds` preserves the
  register's exemption element ids. `activePscCount` and `activeStatementCount` come from the header.
- `pscs`: kind (`individual`, `corporate` or `other`), name, status (`active` or `ceased`),
  notified and ceased dates, nature-of-control slugs and published individual or corporate fields.
  Birth dates are `YYYY-MM`; other dates are `YYYY-MM-DD`. Missing optional fields are omitted.
  On dissolved-company pages the header omits "active" and non-ceased PSCs have no status tag.
  These entries use `status: active` to mean **not recorded as ceased**, which may be historical
  at dissolution; it does not mean the company is operating. Explicit ceased tags and dates are
  preserved. `activePscCount` and `activeStatementCount` report the corresponding non-ceased or
  non-withdrawn header counts on these pages.
- `statements`: the register's statement text, whether each statement is active, and published
  `notifiedOn` and `withdrawnOn` dates when present. Withdrawn statements are retained with
  `active: false`.
- `changeType`: `new` for the first check in this watch, then `changed` or `unchanged`.
  `previousCheckedAt` is null on the first check. First checks establish a baseline with empty `changes`.
- `changes`: `added`, `ceased`, `removed`, `control_changed` or `details_changed`, with PSC
  `identity` (`kind`, `name`, `notifiedOn`), `changedFields`, `oldValues`, `newValues` and
  `possibleCorrection`. Added and removed entries include the full available new or old PSC;
  other changes include only changed fields. A missing side or removed field is null.

The HTML does not publish PSC ids. Identity is kind plus exact published name plus notification date.
An added entry paired with a ceased or removed entry sharing kind and notification date gets
`possibleCorrection: true` on both changes. This can indicate a renamed or corrected register entry;
these changes remain visible and are not silently merged. The flag is a heuristic, not a conclusion.

Company name, exemption and statement changes also set `changeType` to `changed`; `changes` describes
PSC changes only. Repeated identities on the same page are retained, including an active and a
ceased entry with the same kind, name and notification date. Change detection matches unchanged
occurrences first, then records of the same status, before comparing remaining occurrences.
Switching address inclusion alone does not create a detail change. Address changes
are compared only when both checks opted in. Added and removed records retain available addresses
when the current check opts in, even if the previous check excluded addresses. No identity-verification
dates are collected.

The free `SUMMARY` record in the default key-value store contains requested count, invalid entries,
duplicate count, companies omitted by `maxCompanies`, successful checks, separate `notFound` and
`errored` entries, counts by change type, rows stored, rows cut by a spending cap and HTTP requests
including retries and pagination. HTTP 404 results are `notFound` only, never dataset rows. Exhausted
request or parsing failures are `errored` only. Neither replaces the last successful baseline.

Watch state advances only after a row is stored, or after an unchanged `onlyChanged` check. A failed
push or spending-cap omission leaves the previous baseline, so the change can be reported again.
When delivery is blocked by a spending cap or the SDK signals the cap boundary, checking stops.
`rowsCutBySpendingCap` counts checked rows that were not stored; `companiesSkippedBySpendingCap`
counts the remaining selected companies that were not attempted. Their state stays unchanged.
A failure between delivery and state persistence can repeat a row on the next run. Exported results
are snapshots, not an authoritative real-time feed.

### Price

$0.002 per company check delivered to the default dataset, plus $0.005 per actor start.
The platform bills the built-in `apify-default-dataset-item` and `apify-actor-start` events.
Quiet companies still produce useful current snapshots and are billed unless `onlyChanged` is true.
With the default three companies, the expected total is $0.011. The summary is free. Invalid numbers,
404s, errors and unchanged checks suppressed by `onlyChanged` have no item charge.

### Source and limits

Requests use the [Companies House public register](https://find-and-update.company-information.service.gov.uk/).
They run sequentially at a maximum of one request per second, including retries and pagination.
HTTP 429 and server failures retry up to four times with exponential backoff and `Retry-After`.
Every request has a 30-second timeout. Pagination follows same-company `?page=` links; missing or
inconsistent pages fail that company instead of replacing its watch with incomplete data.

Companies House publishes no rate limit for these HTML pages. The rate is deliberately conservative.
HTML can change, and upstream outages may cause errors. Public-page access has been verified from a
desktop connection; Apify datacenter access must be confirmed before publishing.

### Personal data

PSC entries contain personal data. The actor outputs only published register information, with
less collected by default: correspondence addresses are omitted unless opted in, birth dates remain
month and year only, and identity-verification dates are excluded. Addresses are also omitted from
saved watch snapshots by default. Opting out on a later run removes addresses from that company's
next saved snapshot, but does not delete older datasets, backups or other watches.

As the user, you are the data controller under UK GDPR. You need a lawful basis to process PSC data,
must keep it accurate by rerunning the watchlist, and must not use it for purposes the register does
not support. Set appropriate access and retention for your datasets and named watch stores, including
deleting them when no longer needed. This is factual product information, not legal advice.

See [Companies House data-products guidance](https://www.gov.uk/guidance/companies-house-data-products)
for the source's conditions of use. Public availability does not remove your data-protection obligations.

# Actor input Schema

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

UK company numbers, normalized to eight characters. Duplicates are checked once; invalid entries appear in SUMMARY only.

## `watchName` (type: `string`):

Exact name of the persistent watch. Reuse it to compare runs. Use a new name to start a separate baseline. Run the same watch sequentially.

## `onlyChanged` (type: `boolean`):

Unchanged companies are checked and counted in SUMMARY but not delivered or billed as items.

## `includeCeased` (type: `boolean`):

Include ceased PSCs in the output PSC list. Complete history remains in watch state and changes regardless of this option.

## `includeCorrespondenceAddress` (type: `boolean`):

Opt in to correspondence addresses, which are personal data. Addresses are omitted from output and saved snapshots by default.

## `maxCompanies` (type: `integer`):

Maximum distinct valid company numbers checked per run, in input order. Zero means no limit.

## Actor input object example

```json
{
  "companyNumbers": [
    "08892100",
    "00519500",
    "00445790"
  ],
  "watchName": "default",
  "onlyChanged": false,
  "includeCeased": true,
  "includeCorrespondenceAddress": false,
  "maxCompanies": 1000
}
```

# Actor output Schema

## `rows` (type: `string`):

No description

## `summary` (type: `string`):

No description

# 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 = {
    "companyNumbers": [
        "08892100",
        "00519500",
        "00445790"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("thatmike1/companies-house-psc-changes").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 = { "companyNumbers": [
        "08892100",
        "00519500",
        "00445790",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("thatmike1/companies-house-psc-changes").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 '{
  "companyNumbers": [
    "08892100",
    "00519500",
    "00445790"
  ]
}' |
apify call thatmike1/companies-house-psc-changes --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,thatmike1/companies-house-psc-changes"
        }
    }
}
```

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/3C6RvTY5kxfCHrmzY/builds/vHm2LNdvBWSjNYEWX/openapi.json
