Bosco Labs

Blog › Check UK company insolvency with the Companies House API

Check UK company insolvency with the Companies House API

Bosco Labs ·

Read company_status from the company profile. If it says liquidation, administration, receivership, voluntary-arrangement or insolvency-proceedings, the company is in a formal insolvency process. Then follow links.insolvency to get the cases, with their dates and the practitioners appointed. That's everything Companies House knows.

What it doesn't know about is a winding-up petition that's been filed but not heard yet. Those show up in The Gazette first. If you're about to pay a supplier or sign with a customer, check both.

Get a key and make the first call

The Companies House API is free. Register on the developer hub, create an application and add a REST API key. Authentication is HTTP Basic with the key as the username and an empty password. The limit is 600 requests per five minutes per key, which is plenty for checks and too little for crawling the register.

curl -s -u "$CH_API_KEY:" \
  https://api.company-information.service.gov.uk/company/00445790

That's Tesco PLC. You'll get back a JSON profile with the name, status, registered office, SIC codes and a links object.

The fields that matter

company_status is the one to branch on. The documented values are active, dissolved, liquidation, receivership, administration, voluntary-arrangement, converted-closed, insolvency-proceedings, registered, removed, closed and open. Most companies you'll check are active or dissolved.

company_status_detail is easy to miss and worth reading. A value of active-proposal-to-strike-off means the company still shows as active, but the registrar is about to remove it. For a new supplier that's a red flag even though the main status looks fine.

date_of_cessation tells you when a dissolved or closed company stopped existing.

You'll find older code that reads has_insolvency_history and has_been_liquidated. Both are now marked deprecated in the API spec, with a note to use links.insolvency instead. They were also sticky: a company that went through a voluntary arrangement ten years ago and recovered still carries true. Use the link.

Python

This fetches the profile, then the insolvency cases if there are any. It uses requests and nothing else.

import os
import requests

BASE = "https://api.company-information.service.gov.uk"
INSOLVENT_STATUSES = {
    "liquidation",
    "administration",
    "receivership",
    "voluntary-arrangement",
    "insolvency-proceedings",
}

session = requests.Session()
session.auth = (os.environ["CH_API_KEY"], "")


def insolvency_check(company_number: str) -> dict:
    r = session.get(f"{BASE}/company/{company_number}", timeout=10)
    if r.status_code == 404:
        return {"company_number": company_number, "found": False}
    r.raise_for_status()
    profile = r.json()

    result = {
        "company_number": company_number,
        "found": True,
        "name": profile.get("company_name"),
        "status": profile.get("company_status"),
        "status_detail": profile.get("company_status_detail"),
        "in_insolvency_process": profile.get("company_status") in INSOLVENT_STATUSES,
        "cases": [],
    }

    if "insolvency" in profile.get("links", {}):
        r = session.get(f"{BASE}/company/{company_number}/insolvency", timeout=10)
        if r.status_code != 404:
            r.raise_for_status()
            for case in r.json().get("cases", []):
                dates = case.get("dates", [])
                result["cases"].append({"type": case.get("type"), "dates": dates})
    return result


print(insolvency_check("00445790"))

Company numbers are eight characters. English and Welsh companies are usually all digits with leading zeros, so pad short numbers with zfill(8). Scottish companies start with SC and Northern Irish ones with NI. They're all on the same register and the same API.

Reading the cases

Each case has a type and a list of dated events. The type matters more than people expect. A members-voluntary-liquidation is a solvent wind-down: the directors have sworn the company can pay its debts in full. It's still a company that's closing, so you probably don't want a new contract with it, but it isn't a creditor problem. A creditors-voluntary-liquidation or compulsory-liquidation is.

Administration is different again. The company may keep trading for months while the administrator looks for a buyer. If you supply them, talk to the administrator before you ship anything on credit.

What the register doesn't tell you

There are a few gaps here, and they're the ones that hurt.

Petitions come first. When a creditor petitions the court to wind a company up, the petition is advertised in The Gazette before the hearing. Companies House doesn't show a pending petition. A compulsory liquidation case, with its petitioned-on date, only appears once the court has made an order and the paperwork is filed. That can be weeks after the petition went public, and it's exactly the window where you'd want to know.

Status updates also lag. Insolvency practitioners file forms with Companies House, and the profile changes when those forms are processed. Expect days, sometimes longer.

And a clean status says nothing about whether the company pays its bills. Late filing of accounts is a better early signal than status. The profile has accounts.overdue and confirmation_statement.overdue, and both are worth surfacing.

The Gazette has its own search and data feeds for insolvency notices. If you build this yourself, search there by company number and name, and treat a recent winding-up petition as a stop until someone has looked at it.

Doing it in one call

If you'd rather not glue the register, The Gazette and a sanctions screen together yourself, our counterparty-check does it in one request. For a UK company it reads Companies House, our copy of The Gazette's insolvency notices, the officers and people with significant control, and screens the company and those people against the OFAC, UK, EU and UN lists.

import os
import httpx

r = httpx.post(
    "https://api.boscolabs.dev/v1/counterparty-check",
    headers={"Authorization": f"Bearer {os.environ['BOSCO_API_KEY']}"},
    json={"query": "00445790", "country": "GB"},
    timeout=60,
)
r.raise_for_status()
row = r.json()["rows"][0]

for check in row["checks"]:
    print(f"{check['check']:20} {check['status']:10} {check['detail']}")

print("needs attention:", row["summary"]["needs_attention"])
print("unverified:", row["summary"]["unverified_checks"])

Each check comes back as pass, fail, review or unverified, with the source and a link. unverified means we couldn't check it, never that it's fine. It costs $0.05 per company, and a key comes with $5 of free credit.

There are limits you should know about. Our Gazette copy is refreshed nightly, so a notice published this morning shows up tomorrow. A full check takes 5 to 30 seconds because it waits on several sources. And this is a screening aid, so a fail or review still needs a person to open the linked source.

We also ran a small benchmark against a model using web search. Insolvency is the check where both sides still had gaps, and the write-up shows which companies and why.

Bosco Labs makes counterparty-check.

Bosco Labs: official-source data for AI agents, as an HTTP API, MCP server and Apify Actors. API docs.