Result completeness & source status
On a live search, an incomplete answer is labelled - counts, sourceStatus and incompleteReason.
Base URL https://api.offendersearch.appThe 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. A failed live check is reported, not hidden, and not billed.{ "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 }incompleteReason — the closed vocabulary
incompleteReason is non-null only when incomplete is true. The transport reasons are the retryable 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 you should 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 mapping to no covered jurisdiction. Resolve it or drop it. |
not_searched | A requested code that resolved to no live jurisdiction. Confirm it against the catalog. |
const RETRYABLE = new Set(["timeout", "source_down", "rate_limited",
"captcha_unsolved", "proxy_required"]);
const shortAndRetryable = res.sourceStatus.filter(
s => s.incomplete && RETRYABLE.has(s.incompleteReason)
);
if (shortAndRetryable.length) {
// Hold. Do NOT record "not found" — re-run the live check.
}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 |
Never infer absence from an empty live result without checking counts.sourcesIncomplete == 0 first.
if (res.counts.sourcesIncomplete > 0) {
// Record "not determined", not "not found". Re-run the live check.
}A worked mixed live response
A live search across two sources where one completed and one was blocked. Read it in order: status is "partial"; counts.sourcesIncomplete is 1 (TX-HARRIS was blocked, so its 0 means unknown); warnings[0] names it and its reason, captcha_unsolved, is retryable. The blocked source is not billed.
{
"status": "partial",
"counts": { "records": 3, "recordsReturned": 3,
"sourcesQueried": 2, "sourcesComplete": 1, "sourcesIncomplete": 1 },
"warnings": [
"INCOMPLETE SEARCH: 1 of 2 sources could not be searched to completion (TX-HARRIS). The records below are a LOWER BOUND: an absent person may simply not have been reached. This is NOT evidence that someone is absent — retry, or narrow the query."
],
"sourceStatus": [
{ "code": "TX-DALLAS", "source": "TX-DALLAS", "ok": true, "incomplete": false,
"incompleteReason": null, "records": 3, "live": true },
{ "code": "TX-HARRIS", "source": "TX-HARRIS", "ok": false, "incomplete": true,
"incompleteReason": "captcha_unsolved", "records": 0, "live": true }
],
"records": [ "… 3 records, a mixture of matchStates …" ],
"usage": { "liveChecks": 1, "liveChargeUsd": 0.02, "capped": false },
"searchId": "crs_9a3b71c0e28d4f6a5b12",
"legal": { "notice": "Not a consumer report. …" }
}counts
records— total matched, before the page slice. Notrecords.length.recordsReturned— how many records this response carries — the current page.sourcesQueried— live sources queried this request.0on a cached search.sourcesComplete— of those, how many finished.status: "complete"means exactlysourcesComplete == sourcesQueried.sourcesIncomplete— the one count that means something may be missing.> 0→ your result is a lower bound.0on a cached search.
sourcesQueried: 0 next to sourcesIncomplete: 0 on a cached search means “complete”, not “nothing was searched”. See Pagination for how counts.records drives the pager.