> Every Check Take Home Pay API error code, its HTTP status, what causes it and what to do, with handling code in Node.js and Python.

Web version: https://checktakehomepay.co.uk/docs/errors · Last updated: 2026-10-09

# 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](https://checktakehomepay.co.uk/app/keys). |
| `NOT_FOUND` | 404 | No endpoint at that method and path. | Check the path against the [endpoints](https://checktakehomepay.co.uk/docs#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](https://checktakehomepay.co.uk/docs/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](https://checktakehomepay.co.uk/contact) 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](https://checktakehomepay.co.uk/docs/limits) has them, what counts and the headers that show where you are.
