# Errors & status codes

> Every status code the Criminal Search API returns, the stable error.code envelope you switch on, and why a 200 can still describe an incomplete live search.

- **HTML:** https://offendersearch.app/docs/criminal/errors
- **Base URL:** https://api.offendersearch.app
- **Authentication:** `X-API-Key` request header
- **OpenAPI:** https://offendersearch.app/openapi.json · https://offendersearch.app/openapi.yaml
- **Criminal reference as markdown:** https://offendersearch.app/docs/criminal.md

## The error shape

Criminal endpoints return errors as a single, consistent nested object across every endpoint. `code` is a **stable machine token**; `message` is human-readable and may be reworded. **Branch on `code`, render `message`.**

```json
{ "error": { "code": "guard_unbounded_query",
             "message": "a lastName predicate is required — an unbounded query returns a lottery, not an answer" } }
```

A `200` can still describe an **incomplete live search**: read `status` and `counts`, never `records` alone.

## Status codes

| HTTP | `code` | When you will see it |
| --- | --- | --- |
| `200` | — | A cached search, or a synchronous live search, completed. |
| `202` | — | A live search fanned out to more than two sources; poll `GET /v1/criminal/searches/{searchId}`. |
| `400` | `no_report_input` | A report request with neither a `searchId` nor an inline `query`. |
| `401` | `unauthenticated` | The `X-API-Key` header is missing or malformed. |
| `404` | `not_found` | No search or record carries the id you asked for, or it belongs to another account. |
| `413` | `batch_too_large` | A batch of more than 1000 rows. |
| `422` | `guard_unbounded_query` | `query.lastName` is missing — an unbounded query is declined rather than answered with a lottery. |
| `422` | `prefix_too_short` | An explicit name-prefix search below the three-character floor. |
| `422` | `no_live_sources` | A `live` request (carrying no website inputs) that resolved to no sources. |
| `422` | `no_website` | A `resolve` call with neither `website` nor `websites`. |
| `422` | `bad_batch` / `empty_batch` | An unparseable batch body or CSV with no header row, or zero queries in the batch. |

> **There is no request rate limit today** — no endpoint returns `429`, and no `Retry-After` header is emitted. Apply your own client-side concurrency control.

## One error, verbatim

```json
// 422 Unprocessable Entity — nameless query declined
{ "error": { "code": "guard_unbounded_query",
             "message": "a lastName predicate is required — an unbounded query returns a lottery, not an answer" } }
```

**`422` is a deliberate contract, not a failure.** A query with nothing to narrow on cannot return the same full result twice, so it is declined with a sentence you can show a user rather than answered with a subset that would read as the whole answer.

---

## Related

- Previous: [Access & keys](https://offendersearch.app/docs/criminal/authentication.md)
- Next: [Search - POST /v1/criminal/search](https://offendersearch.app/docs/criminal/search.md)
- Index: [Criminal Search API reference](https://offendersearch.app/docs/criminal.md)
