Install
openclaw skills install @scavio-ai/scavio-companies-houseSearch the UK Companies House register by name, then pull a company's full register entry, its officers, and its filing history as structured JSON. 4 endpoints, 1 credit each.
openclaw skills install @scavio-ai/scavio-companies-houseSearch the UK register by company name, then read the full register entry, the officers current and resigned, and the complete filing history. All four endpoints return structured JSON from the official UK registry.
Use this skill when the user asks to:
Get a free API key at https://scavio.dev (50 free credits to get started, no card required):
export SCAVIO_API_KEY=sk_live_your_key
Every request is a POST with a JSON body and:
Authorization: Bearer $SCAVIO_API_KEY
Base URL: https://api.scavio.dev. All paths are under /api/v1/companieshouse. Every endpoint costs 1 credit.
| Endpoint | Credits | What it returns |
|---|---|---|
POST /api/v1/companieshouse/search | 1 | Start here. Name search returning the company number |
POST /api/v1/companieshouse/company | 1 | Full register entry for one company |
POST /api/v1/companieshouse/officers | 1 | Officers current and resigned, 35 per page |
POST /api/v1/companieshouse/filing-history | 1 | Filings, most recent first, with the filed PDF |
Note the last path is hyphenated: /filing-history.
Search first. Everything except /search is keyed by the company number.
/companieshouse/search with query. The register matches current and former names, so a rebranded company is still findable under its old one./companieshouse/company with company_number./companieshouse/officers with the same number./companieshouse/filing-history with the same number.company_number is deliberately loose - there is no format check. The register 404s on /company/445790 and /company/sc090312 for companies that genuinely exist, so the transport zero-pads and upper-cases the value before sending it. That means a number copied off a letterhead, or one a spreadsheet stripped the leading zeros from, still resolves.
Registry prefixes supported: SC (Scotland), NI (Northern Ireland), OC / SO / NC (LLPs), FC (overseas), BR (UK establishment), CE (charitable incorporated organisation).
The three paginated endpoints behave differently at the end of the list, and it matters.
/search - page 1-50, 20 results per page, hard-capped at page 50. The register serves a 1000-result window per term whatever hit count it prints: it will claim 10,000 hits for a broad term and then answer page 51 with an HTTP 416. Narrow the query rather than paging deeper./officers - page, 35 per page, no upper bound. Past the last page the register answers an ordinary 200 with an empty list./filing-history - page, no upper bound, same empty-200 behaviour past the end.So on officers and filings, an empty page is the stop signal - and it is indistinguishable from a company that has no officers or no filings at all. Check page 1 before concluding anything.
/company returns a single object and takes no paging parameter.
/search)| Parameter | Type | Default | Description |
|---|---|---|---|
query | string | required | Company name or fragment (1-200 chars, non-blank). Matches current and former names. |
page | integer | 1 | 1-50. 20 results per page; the register only serves the first 1000 matches. |
/company)| Parameter | Type | Default | Description |
|---|---|---|---|
company_number | string | required | 1-20 chars. Zero-padded and upper-cased for you. |
/officers)| Parameter | Type | Default | Description |
|---|---|---|---|
company_number | string | required | |
page | integer | 1 | 35 per page. No upper bound - past the last page is a 200 with an empty list. |
/filing-history)| Parameter | Type | Default | Description |
|---|---|---|---|
company_number | string | required | |
page | integer | 1 | No upper bound - past the last page is a 200 with an empty list. |
import os, requests
BASE = "https://api.scavio.dev"
HEADERS = {"Authorization": f"Bearer {os.environ['SCAVIO_API_KEY']}"}
# 1. Find the company number - matches former names too
hits = requests.post(f"{BASE}/api/v1/companieshouse/search", headers=HEADERS,
json={"query": "Monzo Bank", "page": 1}).json()
# 2. Full register entry. Loose numbers are fine - "445790" and "sc090312" both work.
company = requests.post(f"{BASE}/api/v1/companieshouse/company", headers=HEADERS,
json={"company_number": "09446231"}).json()
# 3. Officers, current and resigned
officers = requests.post(f"{BASE}/api/v1/companieshouse/officers", headers=HEADERS,
json={"company_number": "09446231", "page": 1}).json()
# 4. Filings, most recent first (note the hyphen in the path)
filings = requests.post(f"{BASE}/api/v1/companieshouse/filing-history", headers=HEADERS,
json={"company_number": "09446231", "page": 1}).json()
Officers and filings have no upper page bound, so page until a page comes back empty - that is the only stop signal:
def walk(path, company_number, max_pages=10):
"""1 credit per page. Stop on the first empty page."""
pages = []
for page in range(1, max_pages + 1):
data = requests.post(f"{BASE}/api/v1/companieshouse/{path}", headers=HEADERS,
json={"company_number": company_number, "page": page}).json()["data"]
if not data:
break # empty page: past the end (or nothing to list at all)
pages.append(data)
return pages
Every response uses the envelope { data, response_time, credits_used, credits_remaining }.
FC company also returns home registry, legal form and governing law; a BR returns its parent; a CE returns the charity number.AA, CS01, SH03), description, register annotations and child documents, and a link to the filed PDF with its page count.Counting active officers. officers_count is every appointment ever made and resignations_count is how many of those ended, so the number of currently-appointed officers is the difference, not officers_count. There is no server-side active/resigned filter - the register does that client-side - so filter on each officer's status in the response.
A filing still being processed carries a processing_note instead of a document. That is not an error and the document is not missing permanently.
officers_count. Subtract resignations_count, or count rows whose status says active.processing_note is a filing the register has not finished processing. Say that, rather than "the document is unavailable".400 means an invalid or missing parameter, e.g. a blank query. Fix and retry.401 means the API key is invalid or missing. Check SCAVIO_API_KEY.404 means the company number does not resolve. The transport already pads and upper-cases, so re-check the number via /search rather than reformatting it yourself.429 means rate or usage limit exceeded. Wait before retrying. See https://scavio.dev/docs/rate-limits.502 / 503 mean upstream is temporarily unavailable - wait a few seconds and retry.SCAVIO_API_KEY is not set, prompt the user to export it before continuing.langchain-scavio has no Companies House tool - use the Scavio SDK directly:
pip install scavio
from scavio import ScavioClient
client = ScavioClient() # reads SCAVIO_API_KEY
hits = client.companies_house.search("Monzo Bank")
company = client.companies_house.company("09446231")
officers = client.companies_house.officers("09446231", page=1)
filings = client.companies_house.filing_history("09446231", page=1)
JavaScript / TypeScript:
npm install scavio
import { Scavio } from "scavio";
const scavio = new Scavio(); // reads SCAVIO_API_KEY
const hits = await scavio.companiesHouse.search({ query: "Monzo Bank" });
const filings = await scavio.companiesHouse.filingHistory({ company_number: "09446231" });