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

CodeStatusWhenWhat to do
INVALID_INPUT400A 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.
UNAUTHENTICATED401No Authorization: Bearer <key> header on an endpoint that needs a key.Send the key.
INVALID_KEY401The key isn’t one we issued, or it was revoked.Check for a copying mistake, or make a new one on your dashboard.
NOT_FOUND404No endpoint at that method and path.Check the path against the endpoints.
QUOTA_EXCEEDED402The 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_LIMITED429More 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.
UNAVAILABLE503A part of the service is down.Retry later, with backoff.
INTERNAL500Our 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.