Rate Limits

Companies House API Rate Limits: Headers, Retry Code and Batching

The documented limit is 600 requests per 5-minute window. The number is the easy part. This page covers the response headers that tell you where you stand, a copy-paste retry and backoff function, what can get an application banned, and how to batch a large job.

Need a key first? How to get one. Getting 401s? Key not working.

What is the actual rate limit?

Companies House enforces a limit of 600 requests per 5 minutes per API key. That works out to 2 requests per second on average. If you exceed this, the API returns an HTTP 429 Too Many Requests response and stops serving data for the rest of that 5-minute window; at the end of the window the allowance resets to the full 600. The window starts with your first request, not on the clock, so firing 600 requests in ten seconds buys roughly 290 seconds of 429s rather than a wait until the next round five minutes. There is no burst allowance on top. Companies House also reserves the right to ban, without notice, applications that regularly exceed or try to bypass the limit, and increases on request are possible but small (official rate-limiting guide).

The limit applies per key, not per IP. If you have multiple applications or services sharing one key, their usage is pooled. At 600 req/5 min, a batch job processing 1,000 companies would drain your entire allowance in under 9 minutes - and that assumes every call succeeds first time.

What does a 429 response look like?

Companies House's rate-limiting guide does not list headers, but every response we have seen from the live API (checked 15 October 2026) carries X-Ratelimit-Limit, X-Ratelimit-Remain, X-Ratelimit-Reset and X-Ratelimit-Window headers, so a well-behaved client can watch its remaining allowance and pause before it runs out. X-Ratelimit-Reset is a Unix timestamp in epoch seconds, and X-Ratelimit-Limit is the limit for your key, which may not be the default: the key we tested reports a higher figure than the documented one, so read the header rather than hard-coding a number. Once the allowance is gone, every request gets a 429 until the reset time in that header. In production systems that ignore the headers, a 429 usually manifests as a silent data gap - your app receives an error, logs it (or does not), and moves on, leaving you with incomplete records.

Read the headers on every response

bash
curl -s -D - -o /dev/null -u "$CH_API_KEY:" \
  https://api.company-information.service.gov.uk/company/00000006 | grep -i ratelimit

# X-Ratelimit-Limit: 1200
# X-Ratelimit-Remain: 1062
# X-Ratelimit-Reset: 1792081614
# X-Ratelimit-Window: 5m

The same four headers come back on a 404, so a lookup for a company that does not exist still counts against the window. They are also listed in the API's CORS expose-headers, so a browser can read them.

A retry and backoff function you can copy

It does two things: it slows down before the window is empty, and on a 429 it waits until X-Ratelimit-Reset (epoch seconds), falling back to exponential backoff with jitter if that header is missing. Retrying a 429 immediately only adds more rejected calls to a window that is already spent.

python
import os
import random
import time

import requests

BASE = "https://api.company-information.service.gov.uk"
session = requests.Session()
session.auth = (os.environ["CH_API_KEY"], "")  # key as username, empty password


def ch_get(path, max_attempts=5):
    for attempt in range(max_attempts):
        r = session.get(BASE + path, timeout=10)
        remain = int(r.headers.get("X-Ratelimit-Remain", 1))
        reset = int(r.headers.get("X-Ratelimit-Reset", 0))  # epoch seconds

        if r.status_code != 429:
            if remain <= 5 and reset:  # nearly empty: wait for the reset
                time.sleep(max(reset - time.time(), 0) + 1)
            return r

        # 429: wait for the reset if the header is present, else back off.
        wait = max(reset - time.time(), 1) if reset else 2 ** attempt
        time.sleep(min(wait, 300) + random.uniform(0, 1))  # jitter
    return r

With the Python client

If you would rather not maintain that function, the companies-house package reads the rate-limit headers on every response, sleeps until the window resets when you are nearly out, and retries a 429 after the reset time a bounded number of times before raising an error instead of hanging.

python
import os
from companies_house import CompaniesHouse   # pip install companies-house

ch = CompaniesHouse(os.environ["CH_API_KEY"])
print(ch.company("445790")["company_name"])  # TESCO PLC
print(ch.rate_limit.remaining, "requests left in this window")

We ran the success path against the live API and the 429 path against a stub. We did not provoke a real 429 on a key that other systems share, so whether a 429 carries the reset header is untested; the function does not depend on it.

What triggers a ban

The official wording is that Companies House "reserve the right to ban without notice applications that regularly exceed or attempt to bypass the rate limits". Taken literally, one 429 is not the trigger; repeatedly running into the limit is, and so is trying to get around it. The guide does not define "bypass"; our reading is that spreading load over extra keys or applications to multiply the allowance would count. The guide gives no thresholds and no warning mechanism, so a retry loop that keeps hammering a spent window is exactly the pattern to avoid. If you genuinely need more, the guide says to contact Companies House about a higher limit.

How to batch a large job

  • Budget below the ceiling: 600 requests in 5 minutes is 2 per second, so a steady 1 to 1.5 per second leaves room for other users of the same key.
  • Use one shared client per key, not one per worker, so every worker sees the same X-Ratelimit-Remain.
  • Cache what you have fetched. Most records change rarely, so re-fetching them is the largest avoidable cost.
  • Make the job resumable: store the last company processed, so a pause for the window reset does not restart the whole run.
  • Run bulk work off-peak and keep interactive traffic on a separate budget if you can.

Common causes of hitting the limit

  • Bulk lookups. Enriching a CRM export or screening a list of counterparties in one go. Even at modest scale - say, 200 companies - you will consume a third of your 5-minute budget in a single batch.
  • Polling for fresh data. If your application re-fetches company records on every page load or request, you are burning quota on data that almost certainly has not changed. Companies House records update infrequently - most companies file once a year.
  • Multiple services sharing one key. A backend API, a scheduled enrichment job, and an ad-hoc analyst script all hitting Companies House under the same credential adds up fast.
  • Retry storms. Naive retry logic with no exponential backoff turns a transient 429 into a cascade - every retry hits the already-full window and generates another 429.

How caching eliminates the problem

The real solution is not smarter retry logic - it is not making the upstream call in the first place. Registrum sits between your application and Companies House, serving cached responses for the vast majority of requests. Company profiles, directors and PSC data are retained for 30 days; financial data for 90, since accounts are filed annually and a week-old copy is identical to a fresh one. The underlying CH API is only called once per company per cache window, regardless of how many times your application requests the same company.

Every Registrum response includes an X-Cache-Status header so you can see exactly what happened. A value of HIT means the response came from cache and no CH API quota was consumed. MISS means Registrum fetched fresh data and cached it for subsequent callers. If the CH API is down or rate-limited, Registrum falls back to stale cache rather than returning an error - your application stays up.

Example: seeing the cache in action

bash
# First call - fetches from Companies House, caches the result
curl -i -H "X-API-Key: reg_live_..." \
  "https://api.registrum.co.uk/v1/company/00445790"

# Response headers include:
# X-Cache-Status: MISS
# X-Cache-TTL: 86400
# X-Request-ID: 7f3a9c...

# Second call to the same company - served from cache instantly
curl -i -H "X-API-Key: reg_live_..." \
  "https://api.registrum.co.uk/v1/company/00445790"

# Response headers:
# X-Cache-Status: HIT
# X-Cache-TTL: 83147   (seconds remaining in cache window)
# X-Request-ID: 4e1b2d...

A cache HIT consumes zero Companies House API quota. Your application can call the same endpoint thousands of times per day without touching the rate limit.

Practical recommendations

If you are calling the Companies House API directly and need to stay within limits: implement exponential backoff with jitter on 429 responses, add a local in-memory cache for records fetched within the last hour, and split bulk jobs into batches of 100 with a 30-second pause between them. That keeps you under the 600-per-window ceiling without adding significant latency.

If you would rather not manage any of this, use Registrum. The caching, circuit breaking, and stale-fallback logic are built in. Your application calls one endpoint and gets a consistent response - no quota management required.

Get your free API key

60 days of full access - every endpoint, no monthly cap. No credit card.

Was this page helpful?