# Result completeness & source status

> On a live search, counts.sourcesIncomplete and the closed incompleteReason enum tell you whether an empty result is a confirmed absence or only a lower bound.

- **HTML:** https://offendersearch.app/docs/criminal/result-completeness
- **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 incompleteness signal is a property of live verification

A **cached** search always returns a whole answer from the corpus in one query, so `sourceStatus[]` is `[]`, `status` is `"complete"`, and an empty `records` genuinely means "nobody in the corpus matched". The "is it whole?" question only arises for **live** sources, where we reach out in real time.

## sourceStatus[] — the live-verification audit trail

On a cached search this is `[]`. On a live search, one entry per live source. **`ok` says the source responded; `incomplete` says whether the search finished.** A source can be `ok: true` and `incomplete: true` at the same time — read `incomplete`, never `ok`, to decide whether a `0` is *no match* or *unknown*.

```json
{ "code": "TX-HARRIS", "source": "TX-HARRIS", "ok": false, "incomplete": true,
  "incompleteReason": "captcha_unsolved", "records": 0,
  "lastCheckedAt": "2026-08-25T15:04:12+00:00", "live": true }
```

> A **failed** live check is reported, not hidden, and **not billed** — you pay only for checks that completed.

## incompleteReason — the closed vocabulary

`incompleteReason` is non-`null` **only** when `incomplete` is `true`. The retryable reasons are the transport ones; `truncated_over_cap` is stable (narrow instead); the rest are not retryable automatically. In every case, an incomplete source's `0` means *unknown*, not *no match*.

| `incompleteReason` | What to do |
| --- | --- |
| `timeout` | ★ **Retry** — a capacity event that clears. |
| `source_down` | ★ **Retry**, with backoff. |
| `rate_limited` | ★ **Retry** later. |
| `captcha_unsolved` | **Retry** — transient; do not record absence. |
| `proxy_required` | **Retry.** |
| `truncated_over_cap` | **Do not retry — narrow.** Add a first name or a date of birth. |
| `paid_gateway` | Not retryable automatically. Record *not determined*. |
| `login_required` | Not retryable automatically. Record *not determined*. |
| `unsupported_site` | A `live.websites` entry that maps to no covered jurisdiction. Resolve it or drop it. |
| `not_searched` | A requested code that resolved to no live jurisdiction. Confirm it against the jurisdictions catalog. |

## Reading an empty live result

Two very different events produce the same empty list on a live search, and the response tells you which one you are holding.

| What happened | What the response says |
| --- | --- |
| Every source completed and nobody matched. **This is an answer.** | `status: "complete"`, `counts.sourcesIncomplete: 0` |
| One or more sources could not be checked to the end. **This is a lower bound.** | `status: "partial"`, `counts.sourcesIncomplete > 0`, and a `warnings[]` sentence naming the sources |

```js
if (res.counts.sourcesIncomplete > 0) {
  // Record "not determined", not "not found". Re-run the live check.
}
```

## counts

| Key | Meaning |
| --- | --- |
| `records` | Total matched, before the page slice. Not `records.length`. |
| `recordsReturned` | How many records this response carries — the current page. |
| `sourcesQueried` | **Live** sources queried this request. `0` on a cached search. |
| `sourcesComplete` | Of those, how many were searched to the end. `status: "complete"` means exactly `sourcesComplete == sourcesQueried`. |
| `sourcesIncomplete` | ★ **The one count that means something may be missing.** `> 0` → your result is a lower bound. `0` on a cached search. |

> `sourcesQueried: 0` next to `sourcesIncomplete: 0` on a cached search means "complete", not "nothing was searched".

---

## Related

- Previous: [Searching by date of birth](https://offendersearch.app/docs/criminal/date-of-birth.md)
- Next: [Pagination & response size](https://offendersearch.app/docs/criminal/pagination.md)
- Index: [Criminal Search API reference](https://offendersearch.app/docs/criminal.md)
