Companies House Streaming API: streams, stream keys, timepoints and a reconnecting client
What the Streaming API is
The Companies House Streaming API is a set of long-running HTTP endpoints that push register changes to your client as they happen, instead of you polling the REST API. In the words of the overview guide, it "delivers the same information that is available through the on-demand REST API GET requests, but instead pushes data to your client as it changes". Conceptually it is like downloading an infinitely long file: one JSON document per line, for as long as the connection stays open.
Use it when you keep your own copy of the register, or a large part of it, and need it current within minutes. If you only care about a few dozen companies, a scheduled REST check or a watchlist is simpler (see the last section).
The nine streams
Each stream has its own path on https://stream.companieshouse.gov.uk and returns the same resource as the matching REST endpoint. The reference lists nine:
| Stream | Path | What changes it carries |
|---|---|---|
| Company information | /companies | Company profile: name, status, registered office, accounts and confirmation statement dates |
| Filing history | /filings | New filing history items and their document links |
| Officers | /officers | Director and secretary appointments and changes |
| Persons with significant control | /persons-with-significant-control | PSC notifications and changes |
| PSC statements | /persons-with-significant-control-statements | Statements such as "no registrable person" |
| Charges | /charges | Mortgages and charges, satisfactions |
| Insolvency cases | /insolvency-cases | Insolvency case records |
| Disqualified officers | /disqualified-officers | Disqualification records |
| Company exemptions | /company-exemptions | Exemptions, for example from PSC requirements |
There is no filter parameter. The only query parameter is timepoint, so every client receives every event on that stream and filters on its side.
The event envelope
Every line on every stream uses the same envelope, and the resource itself sits in data:
{
"resource_kind": "company-profile#company-profile",
"resource_uri": "/company/00445790",
"resource_id": "00445790",
"data": { "company_number": "00445790", "company_status": "active" },
"event": {
"type": "changed",
"timepoint": 123456789,
"published_at": "2026-10-21T10:52:22",
"fields_changed": ["accounts.next_due"]
}
}
The example is abridged and the values are illustrative. event.type is changed or deleted, and data may be absent on a deleted event. fields_changed lists changed fields in dot notation, which is the cheapest way to react only to what matters, for example company_status.
Authentication: a separate stream key
The Streaming API uses HTTP Basic auth with a stream key as the username and an empty password, exactly like the REST API, but the keys are different. The authentication guide says applications "must be registered as such, the REST API and streaming API keys are not interchangable". On the Developer Hub, create an application and choose the stream key client type.
curl -u "$CH_STREAM_KEY:" "https://stream.companieshouse.gov.uk/companies"
The colon after the key matters: it marks an empty password before base64 encoding. We checked on 21 October 2026 that a REST key gets a 401 from stream.companieshouse.gov.uk. Other 401 and 403 causes, including Restricted IPs on the key, are in Companies House API 401 and 403 errors.
Timepoints: resuming after a disconnect
Every event carries a timepoint, and reconnecting with ?timepoint=<last processed value> replays everything after it. Each stream is backed by a queue so that clients can disconnect and come back later. Without a timepoint, you only receive events that happen after you connect.
Three rules from the overview guide:
- Store the timepoint of the last successfully processed event, not the last received one.
- If the timepoint is too far in the past, or you consume too slowly, you get 416. Companies House does not publish how far back the queue goes, so treat 416 as "resync from a snapshot or the REST API".
- Companies House produces snapshot datasets that include the timepoint at which they were taken, so you can load a snapshot and start the stream from there. The free monthly bulk files are described in Companies House bulk data; check that the snapshot you load states a timepoint before you rely on it for a seamless start.
A reconnecting client in Python
The client below follows the documented rules: it skips heartbeats, saves the timepoint after each processed event, waits a minute on 429, resets on 416 and backs off on network errors.
import json
import os
import time
from pathlib import Path
import requests
STREAM_KEY = os.environ["CH_STREAM_KEY"] # a stream key, not a REST key
URL = "https://stream.companieshouse.gov.uk/companies"
STATE = Path("companies.timepoint")
WATCH = {"00445790", "00519500"} # company numbers you care about
def handle(msg):
data = msg.get("data") or {}
number = data.get("company_number") or msg["resource_uri"].rsplit("/", 1)[-1]
if number not in WATCH:
return
event = msg["event"]
print(event["published_at"], number, event["type"],
data.get("company_status"), event.get("fields_changed"))
def run():
while True:
params = {}
if STATE.exists():
params["timepoint"] = STATE.read_text().strip()
try:
with requests.get(URL, auth=(STREAM_KEY, ""), params=params,
stream=True, timeout=(10, 300)) as r:
if r.status_code == 401:
raise SystemExit("401: check you are using a stream key")
if r.status_code == 429:
time.sleep(60) # required: wait one minute
continue
if r.status_code == 416:
STATE.unlink(missing_ok=True) # timepoint too old: resync
time.sleep(10)
continue
r.raise_for_status()
for line in r.iter_lines(chunk_size=None):
if not line.strip():
continue # heartbeat: a blank line
msg = json.loads(line)
handle(msg)
STATE.write_text(str(msg["event"]["timepoint"]))
except (requests.ConnectionError, requests.Timeout,
requests.exceptions.ChunkedEncodingError):
time.sleep(5) # transient network error
continue
except requests.HTTPError:
pass
time.sleep(10) # stream closed or retryable error
if __name__ == "__main__":
run()
Run it with CH_STREAM_KEY=... python stream.py. chunk_size=None makes requests hand over data as it arrives instead of waiting to fill a buffer. The 300-second read timeout is a safety net: heartbeats keep a healthy connection busy, and a silent socket is treated as dead.
What we tested. We do not hold a stream key, so we have not run this client against a live stream. On 21 October 2026 we compiled it, ran handle() on sample envelopes and confirmed that the endpoint returns 401 without a valid stream key. The reconnect logic follows the documented status codes.
For production, write the timepoint to your database in the same transaction as the change, or every few hundred events, rather than to a file on each line, and make handle() idempotent: after a reconnect you may see an event twice.
Pitfalls
Most broken stream clients fail on one of these:
- Heartbeats. The API sends a blank line periodically to keep the connection alive. Parsing it as JSON throws; skip it.
- Buffering. A proxy or HTTP client that buffers the response until the connection closes will never deliver anything. Read line by line.
- Two connections per account. A third connection closes the oldest one. Two reconnecting workers on the same credentials can keep disconnecting each other.
- Reconnect storms. Reconnecting is expensive for Companies House. On 429 you must wait one minute; ignoring it extends the limit, and clients that never back off can have their IP blocked. Retry other retryable errors after 10 seconds.
- Slow consumers. If your handler is slower than the stream, the server-side queue grows and the connection is closed. Push events onto your own queue and process them elsewhere.
- Wrong key type. REST keys and stream keys are not interchangeable, and REST rate limits (600 requests per five minutes per key) are a separate matter: see rate limits.
What the stream does not carry
The stream tells you that something changed, not the balance sheet behind it. The company stream carries the profile, including accounts due dates and the overdue flag, and the filings stream tells you that new accounts were filed with a link to the document. Neither carries net assets, cash or creditors as data. To get figures you fetch the document and parse iXBRL yourself, and large groups often file PDF only. Field coverage for parsed figures is in Companies House financial data as JSON.
A lighter option for a list of companies
If you are monitoring tens of companies rather than the whole register, a stream is a lot of infrastructure: a stream key, a process that never stops, timepoint storage and filtering of every event in the UK. Overdue accounts are a common trigger, and the volume is real: on 1 October 2026, 93,785 active companies were past their accounts due date (overdue accounts statistics).
Our watchlist does the filtering and the accounts parsing for you. Add company numbers with POST /v1/watchlist/add and poll GET /v1/alerts?format=json once a day, or use the watch_add and get_alerts tools on our MCP server. Alerts cover status changes, overdue accounts, newly published accounts, new charges, negative net assets and falls of 30% or more in net assets or cash. The free plan watches 5 companies and API Pro watches 50; the quickstart has working examples. Our register snapshot is monthly, so for minute-by-minute status changes the stream is still the right tool. Get a free key on the developer docs page.
Sources
- Streaming API: getting started (overview)
- Streaming API: authentication
- Streaming API: reference
- Streaming API: company profile stream resource
- Companies House API: rate limiting
- Developer Hub: how to create an application
FAQ
Do I need a separate key for the Companies House Streaming API?
Yes. Register an application on the Developer Hub with the stream key client type. Companies House says REST API and streaming API keys are not interchangeable, and a REST key returns 401 on stream.companieshouse.gov.uk. Authentication is otherwise the same: HTTP Basic with the key as username and an empty password.
Can I stream changes for only my companies?
No. The streams take no filter apart from a timepoint, so you receive every event on that stream and filter by company number in your own code. For a short list of companies, a scheduled REST check or a watchlist service is usually cheaper to run than a permanent stream connection.
What does a 416 error from the Streaming API mean?
The timepoint you sent is invalid or too old for the queue behind the stream, or your client consumed events too slowly. Reconnect without a timepoint, then fill the gap from a snapshot or the REST API. Companies House does not publish how far back timepoints remain valid, so store yours often.
How many connections can I open to the Streaming API?
Two concurrent connections per account. A further connection closes the oldest one, so several workers sharing one key will disconnect each other. Repeated reconnecting also triggers rate limiting with a 429, after which you must wait one minute before trying again.
Does the Streaming API include company accounts figures?
No. The filing history stream tells you that accounts were filed and links to the document, and the company stream updates due dates and the overdue flag. Balance sheet numbers are not sent as data; you have to download the filing and parse the iXBRL, or use a service that has parsed it already.
Try it with a free key. 50 requests a month, REST and MCP, no card. Company register, iXBRL financial history since 2008 and watchlist alerts.
Get a free API key