Companies House API key not working: 401, 400 and 403 errors explained
A Companies House API key that does not work almost always fails in one of a handful of ways, and the API tells you which in a response header. Everything below was tested against the live API on 15 October 2026, or comes from the official authentication page. Do not have a key yet? Start with how to get one.
Read the error first
Every authentication failure carries a Ch-Authentication-Error response header and a JSON body with the same text. These are the three we saw:
- 401 "Empty Authorization header": no credentials arrived at all. The key is in the wrong place (see below) or your HTTP client dropped the header.
- 401 "Invalid Authorization": a header arrived but the key is not accepted. Wrong key, wrong key type, wrong environment, or the key sent as a Bearer token.
- 400 "Invalid Authorization header": the header itself is malformed. Usually a stray newline or space in the key, the key used as the password instead of the username, or a Basic header that was never base64-encoded.
Send the key as HTTP Basic auth, with an empty password
The official guidance is that the API "takes the username as the API key and ignores the password, so can be left blank", and its example is curl -u my_api_key: (authorisation guide). The header for that key is Basic bXlfYXBpX2tleTo=, which is the base64 of my_api_key: including the trailing colon.
# Key as the username, empty password (note the colon after the key). export CH_API_KEY="paste-your-key-here" curl -u "$CH_API_KEY:" https://api.company-information.service.gov.uk/company/00000006 # Building the header by hand: printf, not echo (echo adds a newline). curl -H "Authorization: Basic $(printf %s "$CH_API_KEY:" | base64)" \ https://api.company-information.service.gov.uk/company/00000006 # Show the status and the diagnostic header when it fails. curl -s -D - -o /dev/null -u "$CH_API_KEY:" \ https://api.company-information.service.gov.uk/company/00000006 | grep -iE "^HTTP|ch-authentication-error"
import os
import requests
r = requests.get(
"https://api.company-information.service.gov.uk/company/00000006",
auth=(os.environ["CH_API_KEY"], ""), # key as username, empty password
timeout=10,
)
print(r.status_code, r.headers.get("Ch-Authentication-Error"))
print(r.json().get("company_name") if r.ok else r.text)With the Python client (companies-house on PyPI) the first call is the same request with the Basic auth header built for you:
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")What we tried, with a valid key, and what came back:
key:encoded as the official example does: 200. The key alone, base64-encoded without the colon, also returned 200, but copy the documented form.- Base64 of the key produced with
echo, which appends a trailing newline: 400 Invalid Authorization header. Useprintf %s, or let your client build the header. A trailing space on the key, from a copy and paste or an.envfile, gave the same 400. - Key as the password with an empty username (
-u :KEY): 400. Authorization: Basic KEY:with nothing encoded: 400.X-API-Key: KEYheader or?api_key=KEYin the URL: 401. The API reads only the Authorization header.Authorization: Bearer KEY: 401. Bearer is for OAuth access tokens, not API keys.
Check the key type: REST, stream or OAuth
The developer hub creates three kinds of client under an application: an API key, a stream key and an OAuth web client (how to create an application). They are not interchangeable. In our test a stream key sent to the REST API returned 401, and a REST API key sent to the streaming host returned 401. An OAuth web client ID and secret are not a key at all: they are exchanged for an access token, which is then sent as Authorization: Bearer (API authentication). For reading public company data you want the plain API key.
Check the environment: live key or sandbox key
An application is created as either a test application for the sandbox or a live application. The sandbox has its own host, api-sandbox.company-information.service.gov.uk, and the official testing page says "API keys and OAuth client details must also be substituted for test versions when using the sandbox" (API testing). A live key sent to the sandbox host returned 401 in our test, and the same will happen the other way round. Check that the host and the key come from the same environment.
It works in curl but fails from a browser: 403 with an empty body
When we added an Origin header (which a browser always sends on a cross-origin call) to the same request that returned 200, the API answered 403 Forbidden with an empty body and no Access-Control-Allow-Origin header. The browser hides the 403 and reports a CORS error, which sends people looking at CORS settings that are not the cause. The preflight (OPTIONS) itself succeeded with 204, so it is the real request that is refused. This is how it behaved for our key, which is used server-side and has no browser origins configured; we did not test a key with origins set.
Companies House says keys can be restricted to a specific IP address or domain, and advises against embedding keys in code (developer guidelines). A key in browser JavaScript is visible to every visitor, so the robust fix is to call the API from your server and have your page call your server. If you do want direct browser calls, check the restrictions on the key in your application page.
It works on your laptop but not on the server
The same restriction feature is the likely suspect: a key limited to one IP address will fail from another. We could not test a restricted key, so treat this as the first thing to check rather than a confirmed diagnosis. Companies House documents no activation delay for a new key, so if a fresh key fails, work through the sections above before waiting.
It is a 429, not a 401
A 429 means the key is fine and you have used the allowance for the window. See the rate-limit guide for the response headers and a retry pattern.
Sources
- Authorisation guide (API key, Basic auth, empty password)
- API authentication (API and stream keys, OAuth bearer tokens)
- How to create an application (client types, test vs live)
- API testing (sandbox hosts and keys)
- Developer guidelines (key security, IP and domain restriction)
The error strings, status codes and header behaviour above are our own observations of the live API on 15 October 2026; Companies House does not document them.
If you would rather not manage a key
Registrum is a separate service that sits on top of Companies House data. You can query it over MCP with no key and no signup, or use a Registrum API key, and it handles the Companies House credentials and limits for you. Start at the quickstart.