Errors
Every non-2xx response from the Keeper Health API follows the same JSON envelope. Branch your client code on error.type, not on error.message — the type is a stable machine-readable slug, while messages may be reworded for clarity over time. See Code Examples for end-to-end clients that handle these errors.
Error envelope
{
"error": {
"type": "validation_error",
"message": "One or more fields in the request body are invalid.",
"details": [
{
"loc": ["body", "npis"],
"msg": "npis exceeds the per-request limit of 1000",
"type": "value_error"
}
]
}
}| Field | Type | Description |
|---|---|---|
error.type | string | Stable machine-readable slug, e.g. validation_error. Branch on this. |
error.message | string | Short human-readable summary. Safe to log and surface. |
error.details | array | Optional. Structured field-level information. Present on validation errors; omitted otherwise. |
Error types
error.type | HTTP | Meaning |
|---|---|---|
validation_error | 400 | A request field failed validation. details lists each failing field. See Create Search and Rate Limits for the hard per-request limits. |
invalid_request | 400 | The request body is missing, not valid JSON, or the Idempotency-Key header is malformed. See Create Search → Idempotency. |
unauthorized | 401 | The API key is missing, malformed, or invalid. See Authentication. |
forbidden | 403 | The API key has been revoked or the organization is inactive. See Authentication → Help. |
not_found | 404 | The job_id does not exist, or belongs to a different organization. See Get Search Status. |
rate_limited | 429 | You've exceeded the rate limit. A Retry-After header is set. See Rate Limits. |
enqueue_failed | 503 | The job could not be enqueued. Transient — safe to retry. Retry-After is set. |
service_unavailable | 503 | A backend dependency is temporarily unavailable. Safe to retry. Retry-After is set. |
internal_error | 500 | Something broke on our end. Log the response and contact support. |
The Retry-After header
429 and 503 responses include a Retry-After header with the number of seconds to wait before retrying. Honor it rather than inventing your own backoff schedule. See Rate Limits for details.
Retry-safe vs. not retry-safe
Safe to retry (ideally with an Idempotency-Key so you don't create duplicate jobs — see Code Examples for language-specific retry loops):
503 service_unavailable/503 enqueue_failed— honorRetry-After429 rate_limited— honorRetry-After- Network errors, connection drops, timeouts
Not retry-safe — fix the request first:
400 validation_error/400 invalid_request— the request is malformed. Retrying unchanged produces the same 400.401 unauthorized— your key is wrong. Do not retry until fixed.403 forbidden— your key is revoked or your organization is inactive.
A note on job failures
A job that ends in a terminal failure state (status: "failed" in the body of a GET /v1/searches/{job_id} response) still returns HTTP 200 — the status check itself succeeded; the job is just done and didn't succeed. Read the status field in the JSON body, not the HTTP status code, to detect terminal job failure. See Get Search Status for details.
See also
- Authentication — resolves
401and403 - Rate Limits — resolves
429and explainsRetry-After - Create Search — resolves
400 validation_error(hard limits and field shapes) - Get Search Status — resolves
404and explains terminal-statefailedjobs - Code Examples — polling loops with proper error handling