I built this actor; it's a paid tool on Apify with a free trial credit.
Every UK company is on the Companies House register: about 5.5 million active ones, with around 2,500 to 3,000 new ones every working day. For each one the register publishes a company number, status, type, incorporation date, registered office address and SIC codes (the official "nature of business" codes). That makes it the best free source for UK B2B lead lists, and the new-incorporations feed in particular is gold for accountants, banks, insurers, web designers and anyone else who sells to brand-new businesses.
Companies House does have an official API, but it needs a key, has rate limits and its advanced search stops at 5,000 results. I'm an 18-year-old engineering student, and I built UK Companies House Scraper to turn register searches into JSON without any of that. This post covers calling it from Python and Node, what a row looks like, how it gets past the 5,000 limit, and a small project: a daily list of new companies in one industry.
How it works
The public site (find-and-update.company-information.service.gov.uk) has an advanced search with the filters you'd want: name contains, SIC code, registered office address, status, company type and incorporation date range. Its results page has a "Download results" button that returns the first 5,000 matches as CSV in a single request, about 650 KB in under two seconds. The actor uses both: it reads the results page for the match count, then downloads the CSV.
The CSV has a quirk worth knowing if you ever parse it yourself: nothing is quoted. Company names contain commas (THE OLD BAKERY, BUCKINGHAM LTD) and the SIC column looks like [10710, 47240], so splitting on commas breaks. The actor matches each line against the fixed column layout instead, anchored on the 8-character company number. It also asks for the count first because the CSV export answers a search with zero matches by hanging for 60 seconds and returning a 504.
When a search matches 5,000 or more companies, the actor splits it by incorporation date and searches each slice. Over long spans most companies are recent, so the slices get narrower towards today; a slice that is still too big is split again, and a single very busy day is split by company type. Everything is merged and deduplicated by company number. In testing that gave the full register count every time: 82,142 of 82,142 active companies with "consulting" in the name in 80 seconds (92 requests), and 124,751 of 124,751 active software companies (SIC 62012) in about two minutes.
With includeDetails on, it also opens each company's overview page for accounts and confirmation statement due dates, overdue flags, "proposal to strike off" notes and previous names. It never opens the People tab: no directors, no PSCs, company data only.
Python example
pip install apify-client from apify_client import ApifyClient client = ApifyClient("<YOUR_APIFY_TOKEN>") run = client.actor("rel8ble/companies-house-scraper").call(run_input={ "companyNames": ["consulting"], "sicCodes": ["62020", "70229"], "locations": ["Leeds", "Manchester"], "companyTypes": ["ltd"], "incorporatedFrom": "2025-01-01", "maxResults": 0, }) for c in client.dataset(run["defaultDatasetId"]).iterate_items(): print(c["companyNumber"], "|", c["companyName"], "|", c["incorporationDate"], "|", c["postalCode"]) In my test run that was 4 searches (2 SIC codes x 2 towns), 322 unique companies in under 4 seconds. Companies that match both SIC codes are saved once.
Node example
npm install apify-client import { ApifyClient } from 'apify-client'; const client = new ApifyClient({ token: '<YOUR_APIFY_TOKEN>' }); const run = await client.actor('rel8ble/companies-house-scraper').call({ companyNumbers: ['00445790', 'SC548639', 'OC300001'], }); const { items } = await client.dataset(run.defaultDatasetId).listItems(); for (const c of items) console.log(c.companyName, '-', c.status, '- accounts due', c.accountsNextDueBy); Company numbers given directly always come back with the full profile.
What a row looks like
{ "companyNumber": "12488363", "companyName": "JAPANESE BAKERY LONDON LTD", "status": "active", "companyType": "ltd", "companyTypeLabel": "Private limited company", "incorporationDate": "2020-02-27", "registeredOfficeAddress": "28 Dorey House High Street Brentford England TW8 8LD", "postalCode": "TW8 8LD", "country": "England", "sicCodes": ["10710", "47240"], "sicDescriptions": [ "10710 - Manufacture of bread; manufacture of fresh pastry goods and cakes", "47240 - Retail sale of bread, cakes, flour confectionery and sugar confectionery in specialised stores" ], "companyUrl": "https://find-and-update.company-information.service.gov.uk/company/12488363" } (The full row has a few more fields; with details on you also get accountsNextDueBy, confirmationStatementNextDueBy, accountsOverdue, statusDetail and previousNames.) The SIC descriptions come from the official SIC 2007 list, so they are there even without details.
Small project: new companies in your industry, every morning
If you sell to new businesses, the best moment to reach them is the week they incorporate. This script runs daily, pulls yesterday's and today's incorporations for a few SIC codes, keeps only companies it hasn't seen and prints one line each.
import json, os from apify_client import ApifyClient client = ApifyClient("<YOUR_APIFY_TOKEN>") SEEN = "seen_companies.json" seen = set(json.load(open(SEEN))) if os.path.exists(SEEN) else set() run = client.actor("rel8ble/companies-house-scraper").call(run_input={ "sicCodes": ["62012", "62020", "73110"], # software, IT consultancy, advertising agencies "incorporatedInLastDays": 1, "maxResults": 0, }) new = [] for c in client.dataset(run["defaultDatasetId"]).iterate_items(): if c["companyNumber"] in seen: continue seen.add(c["companyNumber"]) new.append(c) for c in sorted(new, key=lambda x: x["postalCode"] or ""): print(f'{c["companyName"]} | {c["incorporationDate"]} | {c["postalCode"]} | {c["sicDescriptions"][0] if c["sicDescriptions"] else ""} | {c["companyUrl"]}') json.dump(sorted(seen), open(SEEN, "w")) print(len(new), "new companies") Run it on cron or an Apify schedule and push the lines into a CRM or a sheet. For the whole country rather than a few codes, drop sicCodes: all of last week's incorporations (14,875 when I tested) came back in 16 seconds.
Things to know
- Price: $0.25 per 1,000 companies, no start fee, details included. A day of UK incorporations is roughly $0.60 to $0.75.
- The 5,000 limit is the register's export limit. Deep search is on by default and only kicks in when you ask for more; the run summary shows the register's count, what was saved and the coverage percent.
- One SIC code per search: the register filters one code at a time, so several codes run as several searches, merged without duplicates.
- Registered office, not trading address: small companies often use their accountant's address. And the register has no phone numbers, e-mails or websites.
- Company data only: no officers, directors or PSCs. If you contact the companies, UK GDPR and PECR still apply to you.
Questions or broken fields: the Issues tab on the actor page comes straight to me.
UK Companies House Scraper on Apify
Need a custom scraper or a ready dataset? Hire me on Fiverr: https://www.fiverr.com/rel8ble
This article was drafted with AI and published by me.