# Companies House Bulk Data: UK Companies + Monthly Changes (`wrtcom/uk-company-registry`) Actor

UK companies from the official Companies House bulk file, no API key: number, name, status, SIC codes, registered office. Monthly feed of new, dissolved and changed companies. From $1.50 per 1,000. No personal data.

- **URL**: https://apify.com/wrtcom/uk-company-registry.md
- **Developed by:** [WRTLabs](https://apify.com/wrtcom) (community)
- **Categories:** Business, Lead generation
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $1.50 / 1,000 company records

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 Bulk Data: UK Companies + Monthly Changes

Every month, find out which UK companies are **new**, which have **gone** from the register
(dissolved or struck off), and which have **changed**. Tracked changes: status, name, SIC codes,
registered office, and overdue accounts or confirmation statements. Each change comes with its old and new
values. You can also take a **snapshot** of all live companies that match your filters.

All data comes from the official **Companies House Free Company Data Product**, the monthly bulk
file of every live company on the UK register. No logins, no scraping of the Companies House
website, and **organisation data only**: no officers, no persons with significant control, no
"care of" names.

### Two modes

| Mode | What you get | Use it for |
|---|---|---|
| `changes` | One record per **new**, **gone** or **changed** company since your previous run with the same filters | Monthly monitoring: new competitors in your SIC code, companies going into liquidation in your area, moved registered offices, overdue filings |
| `snapshot` (default) | One record per live company that matches the filters | A full list to start from, e.g. all active IT companies in Manchester |

#### How the change feed works

- The Actor keeps a small state per filter set in your account (a named key-value store).
  Each run compares the current monthly file with that state.
- **The first run with a new filter set only builds the baseline.** It cannot know what changed
  before it existed, so as "new" it reports only the companies incorporated between
  *Incorporated from* and *Incorporated to* (default: the last 31 days). Real change records
  (gone / changed) start from the next monthly file.
- Companies House publishes the file once a month (within 5 working days after the month ends).
  A run with no new file since the last one returns 0 records and says so in the bulk file report.
  A monthly schedule (e.g. on the 8th) is enough.
- A company that stops matching your filters (e.g. it leaves status *Active*) is reported once as
  `changed`, then it is no longer tracked. A company that newly matches (e.g. it adds your SIC
  code) is reported as `new`.
- Use **Only new since last run** with the change feed. If a run stops early (*Max results* or
  your maximum cost per run), it does not move the state forward. The next run compares the same
  months again and delivers only what you have not received yet. The first (baseline) run is the
  exception: it always reads the whole file and saves the baseline, even when it stops delivering early.
- Limit: changes mode tracks up to **~380,000 companies per filter set at the default 1 GB
  memory** (~160,000 at 512 MB, ~1.6 million at 4 GB). That is enough for all IT companies (SIC 62,
  \~340,000). The whole register (~5.7 million) is too big. Narrow it by SIC code, postcode, status
  or type, or give the run more memory.

### Filters (both modes)

| Input | Example | Meaning |
|---|---|---|
| `sicPrefixes` | `["62", "6311"]` | at least one SIC code starts with one of these |
| `postcodePrefixes` | `["M1", "SW1A", "E1 "]` | registered office postcode starts with it, spaces ignored. A trailing space means an exact outward code (`"E1 "` = E1, not E14) |
| `companyStatus` | `["Active", "Liquidation"]` | exact status, case-insensitive |
| `companyCategory` | `["Private Limited Company"]` | exact company type, case-insensitive |
| `dateFrom`, `dateTo` | `"2026-10-01"` | incorporation date range (only when you set them) |
| `keywords` | `["software"]` | company name contains one of these words |
| `maxItems` | `50` | stop after this many results (form default 50, without a value 1000) |
| `onlyNew` | `true` | skip records you already received in earlier runs |

### Output

#### Change record (mode `changes`)

```json
{
  "id": "01895499:2026-11-01:changed",
  "record_type": "change",
  "change_type": "changed",
  "company_number": "01895499",
  "company_name": "AIRTEL LIMITED",
  "company_status": "Active",
  "company_category": "Private Limited Company",
  "incorporation_date": "1985-03-14",
  "postcode": "SS7 1AR",
  "sic_codes": ["61100 - Wired telecommunications activities", "62020 - Information technology consultancy activities"],
  "changed_fields": ["company_status"],
  "old": {"company_status": "Active - Proposal to Strike off"},
  "new": {"company_status": "Active"},
  "company_url": "https://find-and-update.company-information.service.gov.uk/company/01895499",
  "file_date": "2026-11-01",
  "previous_file_date": "2026-10-01",
  "licence": "Companies House public register information (no reuse conditions)",
  "attribution": "Source: Companies House, Free Company Data Product (https://download.companieshouse.gov.uk/en_output.html). Companies House imposes no rules on the use of public register information."
}
```

(Example from a local test; the old status was edited in the test state to show a change.)

| Field | Meaning |
|---|---|
| `change_type` | `new`, `gone` or `changed` |
| `changed_fields` | for `changed`: any of `company_name`, `company_status`, `sic_codes`, `registered_office`, `accounts_overdue`, `confirmation_statement_overdue` |
| `old`, `new` | old and new values of the changed fields. `new`: `old` is null and `new` holds the tracked fields. `gone`: `old` holds the last known values and `new` is null |
| `file_date`, `previous_file_date` | the two monthly files compared |
| `id` | company number + file date + change type (stable, used by *Only new*) |

#### Company record (mode `snapshot`)

`company_number`, `company_name`, `company_status`, `company_category`, `country_of_origin`,
`incorporation_date`, `dissolution_date`, `registered_office` (PO box, address lines, town,
county, country, postcode), `sic_codes` (code + description) and `sic` (codes only),
`accounts` (reference day/month, next due, last made up, category), `accounts_overdue`,
`confirmation_statement` (next due, last made up), `confirmation_statement_overdue`, `returns`,
`mortgages` (counts of charges), `limited_partnership` (partner counts only), `previous_names`
(date + name), `uri`, `company_url`, `file_date`, plus `licence`, `licence_url`, `attribution`.
`*_overdue` = the next due date is before the file date.

Each run also writes two reports to the run's key-value store. `RUN_REPORT` holds the requests,
records and success rate. `CH_BULK_REPORT` holds the file date, rows, seconds and memory per part
file, whether the run was a baseline or a compare, and any error.

### Coverage and limits

- Live companies only (the bulk file does not list dissolved companies). That is why a dissolved
  company shows up as `gone`. The exact reason (dissolved, struck off, converted) is not in the file.
- Monthly data, as of the end of the previous month. For real-time status, check the
  `company_url`.
- "Care of" / "for the attention of" text (C/O, FAO, ATTN) and names after a personal title
  (MR, MRS, MS, MISS, MESSRS, DR) are cut from the registered office address lines, because they
  can name a person. This affects ~3% of companies. When the whole line was such text, the line is
  empty.
- No officers, PSC, filing history or accounts figures. They are not in the bulk file, and
  officers/PSC are personal data that we don't collect.
- Each run downloads the monthly file (7 parts of ~70 MB) from Companies House, one part at a
  time, and reads it row by row. A full run takes several minutes, mostly reading the file.

### Cost

Pay per result:

- **$0.003 per change record** (`change`)
- **$0.0015 per company record** (`company`)

Examples: 50 companies = $0.075; 10,000 companies = $15; 1,000 changes = $3.
Platform usage for reading the file is on us. Set *Max results* or a maximum cost per run to
cap what you pay.

### Monthly change alert in 3 steps

1. Pick mode `changes` and set your filters (e.g. `sicPrefixes: ["62"]`, `postcodePrefixes: ["M1"]`).
2. Turn on **Only new since last run** and run it once: this builds the baseline.
3. Add a monthly **Schedule** in Apify Console (e.g. on the 8th). Each run then delivers that
   month's new, gone and changed companies. Send them on with an Apify integration (e.g. email,
   Slack, Google Sheets, Make, Zapier) or a webhook.

### FAQ

**Do I need a Companies House API key?** No. The Actor reads the public monthly bulk file.

**How fresh is the data?** Monthly, as of the end of the previous month (the Companies House
file date is in every record). It is not a real-time feed.

**How long does a run take?** A quick test (form defaults, 50 results) takes about 20 seconds.
A full pass over the file takes several minutes.

**Are directors or owners included?** No. Officers and persons with significant control are
personal data and are not collected.

**Why is my first `changes` run small?** It builds the baseline. Real gone / changed records
start with the next monthly file (see *How the change feed works*).

**Can I use it from code or an AI agent?** Yes: through the Apify API and client libraries
(Python, JavaScript), or as a tool for AI agents through the Apify MCP server.

### Attribution and licence

Companies House register information is public. Companies House states that it "imposes no rules
or requirements on how the information on the public register is used". Every record carries:
*Source: Companies House, Free Company Data Product
(https://download.companieshouse.gov.uk/en_output.html). Companies House imposes no rules on the
use of public register information.*

Not affiliated with or endorsed by Companies House or the UK Government.

# Actor input Schema

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

'changes' = companies that are new, gone (dissolved / struck off) or changed since your previous run with the same filters. The FIRST run only builds the baseline and reports companies incorporated between 'Incorporated from' and 'Incorporated to' (default: the last 31 days) as new. 'snapshot' = one record per live company that matches the filters.

## `sicPrefixes` (type: `array`):

Keep companies with at least one SIC code starting with any of these (e.g. '62' = IT, '6201' = programming, '56101' = restaurants). Empty = all. Changes mode needs filters: it tracks up to ~380,000 companies at the default 1 GB memory (about 1.6 million at 4 GB); the whole register is ~5.7 million.

## `postcodePrefixes` (type: `array`):

Registered office postcode starts with any of these, spaces ignored (e.g. 'SW1', 'EC1A', 'M'). End a value with a space for an exact outward code: 'E1 ' matches E1 but not E14. Empty = all.

## `companyStatus` (type: `array`):

Exact status, case-insensitive: e.g. 'Active', 'Active - Proposal to Strike off', 'Liquidation', 'In Administration', 'Voluntary Arrangement'. Empty = all (the file holds live companies only).

## `companyCategory` (type: `array`):

Exact type, case-insensitive: e.g. 'Private Limited Company', 'Public Limited Company', 'Limited Liability Partnership', 'Community Interest Company', 'Charitable Incorporated Organisation', 'Overseas Entity'. Empty = all.

## `dateFrom` (type: `string`):

Only companies incorporated on or after this date (YYYY-MM-DD). Changes mode, first run: also the start of the window of companies reported as new (default 31 days before 'Incorporated to').

## `dateTo` (type: `string`):

Only companies incorporated on or before this date (YYYY-MM-DD). Default: no limit (first-run window ends today).

## `keywords` (type: `array`):

Keep only companies whose name contains any of these words (case-insensitive). Empty = all.

## `maxItems` (type: `integer`):

Stop after this many results (you pay per result). The form starts at 50 for a quick test; default without a value is 1000. Changes mode: a run that stops early saves no new state, so the next run (with 'Only new') continues where it stopped.

## `onlyNew` (type: `boolean`):

Skip records already returned by earlier runs on your account. Recommended for the change feed with a monthly schedule.

## Actor input object example

```json
{
  "mode": "snapshot",
  "sicPrefixes": [
    "62"
  ],
  "postcodePrefixes": [
    "M"
  ],
  "maxItems": 50,
  "onlyNew": false
}
```

# Actor output Schema

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

Mode 'changes': change type, company number and name, changed fields with old and new values, status, postcode, file date.

## `companies` (type: `string`):

Mode 'snapshot': company number, name, status, type, incorporation date, SIC codes, registered office, overdue flags.

## `allFields` (type: `string`):

Same records with every field.

## `runReport` (type: `string`):

JSON report: requests, records scanned and delivered, success/block rate, whether the run collected everything.

## `fileReport` (type: `string`):

JSON report: file date, part files read, rows, seconds and memory per part, baseline or compare run, state size, errors.

# 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 = {
    "sicPrefixes": [
        "62"
    ],
    "postcodePrefixes": [
        "M"
    ],
    "maxItems": 50
};

// Run the Actor and wait for it to finish
const run = await client.actor("wrtcom/uk-company-registry").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 = {
    "sicPrefixes": ["62"],
    "postcodePrefixes": ["M"],
    "maxItems": 50,
}

# Run the Actor and wait for it to finish
run = client.actor("wrtcom/uk-company-registry").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 '{
  "sicPrefixes": [
    "62"
  ],
  "postcodePrefixes": [
    "M"
  ],
  "maxItems": 50
}' |
apify call wrtcom/uk-company-registry --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,wrtcom/uk-company-registry"
        }
    }
}
```

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/TDBKPkeYESg0aC5Xv/builds/chokzD3WleOCerhrr/openapi.json
