Error Reference
All API errors follow a consistent format with a machine-readable code and human-readable message.
Error format
Every error response uses this structure:
{ "error": { "code": "error_code", "message": "A human-readable description of what went wrong.", "documentation_url": "https://docs.govdata.dev/errors" } }
| Field | Description |
|---|---|
code |
Machine-readable error code (use this for programmatic handling) |
message |
Human-readable description (may change, don't parse this) |
documentation_url |
Link to relevant documentation |
Error codes
400 Bad Request
| Code | Description |
|---|---|
bad_request |
Malformed request body or missing required parameters |
invalid_region |
Invalid region slug. Valid values: england_wales_ni, scotland |
invalid_year |
Year parameter is not a valid 4-digit number |
401 Unauthorized
| Code | Description |
|---|---|
unauthorized |
Missing, malformed, or invalid API key. Check your Authorization header. |
403 Forbidden
| Code | Description |
|---|---|
forbidden |
Your API key does not have permission to access this resource. |
404 Not Found
| Code | Description |
|---|---|
not_found |
The requested resource doesn't exist. Check the URL and resource identifier. |
invalid_tax_year |
Tax year not found. Format: YYYY-YY (e.g., 2025-26) |
no_active_tax_year |
No active tax year configured (returned from /current endpoint) |
422 Unprocessable Entity
| Code | Description |
|---|---|
invalid_type |
Invalid NI calculation type. Valid values: employed, self_employed |
429 Too Many Requests
| Code | Description |
|---|---|
rate_limit_exceeded |
Per-minute rate limit exceeded. See Rate Limits. |
insufficient_credits |
Insufficient credits. Purchase a credit pack or enable auto top-up on your billing page. |
500 Internal Server Error
An unexpected error occurred on our end. If this persists, please contact support.
Handling errors
res = Net::HTTP.start(uri.hostname, uri.port, use_ssl: true) { |http| http.request(req) } case res.code when "200" JSON.parse(res.body)["data"] when "401" raise "Invalid API key" when "429" sleep(res["Retry-After"].to_i) retry else error = JSON.parse(res.body)["error"] raise "#{error["code"]}: #{error["message"]}" end
response = requests.get(url, headers=headers) if response.status_code == 200: return response.json()["data"] elif response.status_code == 401: raise Exception("Invalid API key") elif response.status_code == 429: wait = int(response.headers.get("Retry-After", 60)) time.sleep(wait) # retry... else: error = response.json()["error"] raise Exception(f"{error['code']}: {error['message']}")
const response = await fetch(url, { headers }); if (response.ok) { const { data } = await response.json(); return data; } if (response.status === 429) { const wait = parseInt(response.headers.get("Retry-After") || "60"); await new Promise(r => setTimeout(r, wait * 1000)); // retry... } const { error } = await response.json(); throw new Error(`${error.code}: ${error.message}`);