Last updated 9 October 2026View as Markdown
Errors
Every error has the same shape, an HTTP status that matches it, and a message written to be shown to a developer.
The shape
400 Bad Request
{
"error": {
"code": "INVALID_INPUT",
"message": "pension: Expected object, received number; gross.amount: must be pounds and pence"
}
}Branch on code, which won’t change. The message names each field that is wrong and why, so log it, but don’t match on its words.
Codes
| Code | Status | When | What to do |
|---|---|---|---|
INVALID_INPUT | 400 | A field is missing, wrong or unknown, the body isn’t JSON, or the inputs can’t go together (a Scottish tax code with region wales, a pension bigger than pay). | Fix the request: the message names the fields. Retrying won’t help. |
UNAUTHENTICATED | 401 | No Authorization: Bearer <key> header on an endpoint that needs a key. | Send the key. |
INVALID_KEY | 401 | The key isn’t one we issued, or it was revoked. | Check for a copying mistake, or make a new one on your dashboard. |
NOT_FOUND | 404 | No endpoint at that method and path. | Check the path against the endpoints. |
QUOTA_EXCEEDED | 402 | The free plan’s calculations for the month are used. | Wait for the 1st, or choose a plan with more: the error’s upgrade_url says where. See limits. |
RATE_LIMITED | 429 | More calls a second with one key than the plan allows, or too many sign-in attempts. | Wait the seconds in the Retry-After header, then retry. |
UNAVAILABLE | 503 | A part of the service is down. | Retry later, with backoff. |
INTERNAL | 500 | Our fault. | Retry once; if it persists, tell us the request. |
Handling them
Node.js
const response = await fetch('https://api.checktakehomepay.co.uk/v1/take-home', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.CHECKTAKEHOMEPAY_API_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({ gross: { amount: 45000, per: 'year' }, region: 'england' }),
});
const body = await response.json();
if (!response.ok) {
const { code, message } = body.error;
if (code === 'RATE_LIMITED') {
const wait = Number(response.headers.get('retry-after') ?? 1);
// wait, then retry
}
throw new Error(`${code}: ${message}`);
}Python
import os
import requests
response = requests.post(
"https://api.checktakehomepay.co.uk/v1/take-home",
headers={"Authorization": f"Bearer {os.environ['CHECKTAKEHOMEPAY_API_KEY']}"},
json={"gross": {"amount": 45000, "per": "year"}, "region": "england"},
)
body = response.json()
if not response.ok:
error = body["error"]
if error["code"] == "RATE_LIMITED":
wait = int(response.headers.get("Retry-After", "1"))
# wait, then retry
raise RuntimeError(f"{error['code']}: {error['message']}")Limits
Each plan sets how many calculations a month and how many calls a second: limits has them, what counts and the headers that show where you are.