# UK New Company Monitor - incremental lead feed (`minimal_ricegrass/uk-newco-monitor`) Actor

Watches Companies House for newly incorporated UK companies matching your SIC codes and location; each scheduled run emits only what is new since the last run - deduplicated, with officers and addresses. Optional webhook POST to Slack, Zapier, Make or n8n.

- **URL**: https://apify.com/minimal\_ricegrass/uk-newco-monitor.md
- **Developed by:** [FJ Banks](https://apify.com/minimal_ricegrass) (community)
- **Categories:** Lead generation, Business
- **Stats:** 1 total users, 0 monthly users, 0.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $40.00 / 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?

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 New Company Monitor — incremental lead feed

Watches **UK Companies House** for newly incorporated companies matching your
filter (SIC industry codes, registered-office location, name exclusions) and, on
every scheduled run, emits **only the companies that are new since the last
run** — deduplicated, flattened, and enriched with directors and addresses.
Optionally POSTs each batch to a webhook (Slack, Zapier, Make, n8n, or your own
endpoint).

Most Companies House actors are one-shot *lookup* tools: you give them a company
number or name and they return details. This one is a **stateful monitor**. Point
it at a niche, schedule it hourly or daily, and it maintains its own memory of
what it has already seen so you get a clean, no-duplicates stream of fresh
sales leads — the same thing lead-list vendors charge a subscription for.

***

### Who it's for

| Buyer | Use |
|---|---|
| Accountants / bookkeepers | New incorporations in your city → onboarding outreach in week 1 |
| B2B vendors (banking, insurance, payroll, software, fulfilment, telecoms) | Every new Ltd in your target sector, the day it forms |
| Recruiters / staffing agencies | New entrants in a vertical, before they build a team |
| Commercial property / coworking | New companies with a residential registered office = likely to need space |
| Market researchers / journalists | Formation-rate signal for a sector or region |
| Anyone paying for a "new UK companies this week" list | Replace it with a $0.01/record feed you control |

***

### Quick start (one-time, ~3 minutes)

1. **Get a free Companies House API key.**
   Go to <https://developer.company-information.service.gov.uk/>, create an
   account, then **Your applications → Register an application** (Environment:
   **Live**), open it and **Create new key** of type **REST**. Copy the key.

2. **Configure the actor.** Paste the key into `apiKey`, set `sicCodes` and/or
   `locationQuery` for your niche (see recipes below).

3. **Run once** to seed the state and sanity-check the output. Set
   `incorporatedInLastDays` higher (e.g. 7–14) for this first run if you want a
   backfill.

4. **Schedule it.** Apify Console → **Schedules → Create** → this actor → save
   your inputs → run **daily** (or hourly for fast-moving niches). Leave
   `incorporatedInLastDays` at `1` — later runs are incremental regardless.

5. *(Optional)* Set `webhookUrl` to a Slack incoming webhook / Zapier catch hook
   / n8n webhook node to get the new companies pushed to you each run.

***

### Input reference

| Field | Type | Default | Description |
|---|---|---|---|
| `apiKey` | string *(secret)* | — | **Required.** Companies House **REST** key (Live environment). Used for HTTP Basic auth. |
| `sicCodes` | string\[] | `[]` | 5-digit [SIC codes](https://resources.companieshouse.gov.uk/sic/). A company matches if it has **any** of them. Empty = every industry. |
| `locationQuery` | string | — | Free-text registered-office filter: a town (`"Manchester"`), county (`"Kent"`), or postcode district (`"EC1"`, `"M1"`). |
| `incorporatedInLastDays` | integer 1–30 | `1` | **First run only** (no saved state): how far back to look. Ignored on later runs — those go from the previous run forward. |
| `nameExcludes` | string\[] | `[]` | Drop companies whose name contains any of these, case-insensitive (e.g. `["HOLDINGS","NOMINEES","PROPERTY"]`). |
| `includeOfficers` | boolean | `false` | Also fetch each company's appointed officers (name, role, appointment date, nationality, occupation). Costs 1 extra API call per company. |
| `webhookUrl` | string | — | If set, POST a JSON body (see below) with this run's new companies. |
| `maxResults` | integer 1–5000 | `500` | Hard cap on companies emitted per run. Protects against a huge first run or a mis-scoped filter. |

#### SIC code recipes

| Niche | `sicCodes` |
|---|---|
| Software / SaaS | `["62011","62012","62020","62090"]` |
| Digital marketing / agencies | `["70210","73110","73120","74100"]` |
| E-commerce / online retail | `["47910","47990"]` |
| Construction & trades | `["41100","41201","41202","43999"]` |
| Restaurants & food service | `["56101","56102","56103","56210","56290"]` |
| Management consultancy | `["70221","70229"]` |
| Property / real estate | `["68100","68201","68209","68310","68320"]` |
| Financial services / fintech | `["64191","64301","64999","66190"]` |
| Health & wellness | `["86210","86900","96040"]` |
| Logistics / haulage | `["49410","52100","52290","53202"]` |
| Recruitment | `["78100","78200","78300"]` |

Full list: <https://resources.companieshouse.gov.uk/sic/>

#### Example inputs

**Software companies formed in Greater Manchester, with directors, alerting Slack:**

```json
{
  "apiKey": "YOUR_KEY",
  "sicCodes": ["62011", "62012", "62020", "62090"],
  "locationQuery": "Manchester",
  "includeOfficers": true,
  "nameExcludes": ["HOLDINGS", "NOMINEES"],
  "webhookUrl": "https://hooks.slack.com/services/T000/B000/xxxx",
  "maxResults": 300
}
```

**Every new company in a postcode area (any industry), daily digest to a webhook:**

```json
{
  "apiKey": "YOUR_KEY",
  "locationQuery": "BS1",
  "webhookUrl": "https://hook.eu2.make.com/xxxxxxxx"
}
```

***

### Output

#### Dataset — one item per new company

| Field | Example | Notes |
|---|---|---|
| `company_number` | `"14523901"` / `"SC512233"` | Unique registration number |
| `company_name` | `"EXAMPLE SOFTWARE LTD"` | As registered |
| `company_status` | `"active"` | Usually `active` for fresh incorporations |
| `company_type` | `"ltd"` | `ltd`, `plc`, `llp`, … |
| `date_of_creation` | `"2026-09-02"` | Incorporation date |
| `sic_codes` | `["62012"]` | May be empty if not yet filed |
| `registered_office_address` | `"1 Example St, London, EC1A 1AA, England"` | Single flattened string |
| `postal_code` | `"EC1A 1AA"` | Split out for territory sorting |
| `locality` | `"London"` | Split out for grouping |
| `link` | `https://find-and-update.company-information.service.gov.uk/company/14523901` | Public Companies House page |
| `scraped_at` | `"2026-09-03T09:00:00.000Z"` | When this actor emitted it |
| `officers` | `[{ "name": "DOE, Jane", "role": "director", "appointed_on": "2026-09-02", "nationality": "British", "occupation": "Director", "country_of_residence": "England" }]` | Only when `includeOfficers: true` |

Export from the Apify dataset as JSON, CSV, Excel, or via the dataset API into
your CRM / Google Sheet / spreadsheet.

#### Webhook payload

If `webhookUrl` is set and the run found new companies, the actor sends:

```
POST <webhookUrl>
Content-Type: application/json

{
  "source": "uk-newco-monitor",
  "window": { "from": "2026-09-02", "to": "2026-09-03" },
  "count": 12,
  "companies": [ /* the same objects as the dataset items */ ]
}
```

A Slack incoming webhook will render the raw JSON; for a formatted message put a
Zapier / Make / n8n step in between. Webhook failures are logged as a warning and
do **not** fail the run (the data is still in the dataset).

#### `RUN_SUMMARY` (default key-value store)

```json
{
  "window": { "from": "2026-09-02", "to": "2026-09-03" },
  "new_companies": 12,
  "seen_total": 843,
  "rate_limited": false,
  "finished_at": "2026-09-03T09:00:03.114Z"
}
```

***

### How incremental / dedup works

- **State store.** Cross-run state lives in a **named** key-value store,
  `uk-newco-monitor-state` (the default store is wiped between runs on the Apify
  platform, so it can't be used for memory). State is **namespaced by a hash of
  your filter** — running the same actor from two schedules with different SIC
  codes or locations keeps two independent seen-sets.
- **First run** (no state for that filter): searches incorporations in the last
  `incorporatedInLastDays` days.
- **Every later run:** searches from the **day before** the previous run up to
  today (Companies House incorporation dates are day-granular), then drops every
  `company_number` already in the seen-set. Overlapping date windows therefore
  never produce duplicates.
- The seen-set is capped at 20,000 numbers (~10 days of *all* UK incorporations;
  far more headroom for any real filter).
- `maxResults` caps a single run. If a filter matches more new companies than
  that in one run, the rest come through on the next run — nothing is lost.

***

### Rate limits & cost

- **Companies House:** 600 requests per 5 minutes across all endpoints. Each run
  uses ~`ceil(matches / 100)` search requests plus one per company when
  `includeOfficers` is on. A `429` ends the run early but **still saves
  progress** and pushes what it collected.
- **Apify compute:** a typical daily run for a focused niche finishes in a few
  seconds on the default memory. `includeOfficers` on a broad filter is the main
  cost driver (one call per company) — narrow the filter or turn officers off if
  runs get long.

***

### Scheduling guidance

| Niche size | Suggested schedule | `maxResults` |
|---|---|---|
| One city + tight SIC set | Daily | 200–500 |
| Whole region, any industry | Daily, or twice daily | 1000–2000 |
| National, single niche | Hourly if you need speed-to-lead, else daily | 500 |

Companies House posts new incorporations continuously through the working day,
so an hourly schedule genuinely gets you same-day leads.

***

### Local development

```bash
npm install
npm test          # 14 unit tests, fully mocked — no network, no key needed

## real run against the live API:
mkdir -p storage/key_value_stores/default
echo '{"apiKey":"YOUR_KEY","sicCodes":["62012"],"incorporatedInLastDays":3}' \
  > storage/key_value_stores/default/INPUT.json
npm start
```

Source layout:

| File | Responsibility |
|---|---|
| `src/companiesHouse.js` | API client: auth, `advanced-search` pagination, officers, error mapping. Injectable `fetch` for tests. |
| `src/filter.js` | Pure helpers: date-window logic, name-exclusion predicate. |
| `src/dedupe.js` | State persistence: config namespace hash, seen-set + last-run load/save. |
| `src/main.js` | Orchestration: read input → search → filter → dedup → dataset + webhook → save state. |

***

### Deploy your own copy

```bash
npm i -g apify-cli
apify login
apify push
```

Then in the Apify Console: **Publication → Publish**, set **Monetization → Pay
per result** (suggested **$0.01 / result**), and add a **Schedule**.

***

### Troubleshooting

| Symptom | Cause / fix |
|---|---|
| `Companies House rejected the API key (HTTP 401)` | Wrong key, or you created a **Test** key instead of **Live**. Regenerate a Live REST key. |
| Run emits companies you've seen before | You changed the filter — a different filter = a different state namespace, so it re-seeds. Expected. |
| First run returns nothing | No incorporations matched in the look-back window. Widen `sicCodes`, drop `locationQuery`, or raise `incorporatedInLastDays`. |
| `rate_limited: true` in the summary | Filter is too broad with `includeOfficers` on. Narrow it, turn officers off, or lower `maxResults`. |
| `sic_codes` empty on some results | The company hasn't filed its SIC codes yet (common in the first days). It still matches a `locationQuery`-only filter. |

***

### Data source & licensing

Data comes from the official **Companies House Public Data API**
(`api.company-information.service.gov.uk`). Companies House data is published
under the **[Open Government Licence v3.0](https://www.nationalarchives.gov.uk/doc/open-government-licence/version/3/)**
and is free to use, including commercially, with attribution. This actor only
reads public register data — no authentication beyond your own free API key, no
private data.

***

### Related actors

Part of a small suite of UK public-register monitors, same publisher, same
pattern (incremental, no duplicates, webhook-ready):

- **[UK Insolvency Notice Monitor](https://apify.com/minimal_ricegrass/uk-insolvency-monitor)** — winding-up, administration, bankruptcy (The Gazette)
- **[UK Food Business Monitor](https://apify.com/minimal_ricegrass/uk-food-monitor)** — new food businesses + hygiene-rating changes (FSA)
- **[UK Public Tender Monitor](https://apify.com/minimal_ricegrass/uk-tender-monitor)** — public-sector tenders by CPV / value (Contracts Finder)
- **[UK Company Change Monitor](https://apify.com/minimal_ricegrass/uk-company-change-monitor)** — watchlist diffing: status, charges, officers, filings

***

### Changelog

- **0.1** — Initial release: advanced-search by SIC / location, incremental
  dedup via namespaced state store, optional officer enrichment, optional
  webhook, `RUN_SUMMARY`.

# Actor input Schema

## `apiKey` (type: `string`):

Free REST key from https://developer.company-information.service.gov.uk/ (register an application, 'Live' key). Used for HTTP Basic auth.

## `sicCodes` (type: `array`):

Only companies with at least one of these 5-digit industry codes (e.g. 62012 = business software development). Leave empty to match all industries.

## `locationQuery` (type: `string`):

Free-text location filter, e.g. "London", "Manchester", or a postcode district like "EC1".

## `incorporatedInLastDays` (type: `integer`):

On the very first run (no saved state) fetch companies incorporated within this many days. Later runs only fetch what's new since the last run.

## `nameExcludes` (type: `array`):

Skip companies whose name contains any of these (case-insensitive), e.g. "HOLDINGS", "PROPERTY".

## `includeOfficers` (type: `boolean`):

Also fetch the list of appointed officers for each new company (1 extra API call per company).

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

If set, POST a JSON array of the new companies from this run to this URL (Slack incoming webhook, Zapier, Make, n8n, or your own endpoint).

## `maxResults` (type: `integer`):

Safety cap on how many companies to emit in a single run.

## Actor input object example

```json
{
  "sicCodes": [
    "62012",
    "62020"
  ],
  "incorporatedInLastDays": 1,
  "nameExcludes": [],
  "includeOfficers": false,
  "maxResults": 500
}
```

# Actor output Schema

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

The new UK companies found since the last run, as dataset items.

## `companiesTable` (type: `string`):

Same data in the Apify dataset table UI.

## `runSummary` (type: `string`):

Counts and the incorporation-date window for 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 = {
    "sicCodes": [
        "62012",
        "62020"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("minimal_ricegrass/uk-newco-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 = { "sicCodes": [
        "62012",
        "62020",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("minimal_ricegrass/uk-newco-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 '{
  "sicCodes": [
    "62012",
    "62020"
  ]
}' |
apify call minimal_ricegrass/uk-newco-monitor --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,minimal_ricegrass/uk-newco-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/6Vw0nmvJjKj5xHSd9/builds/bOBsqjqkOj3pT1cM2/openapi.json
