Regular vs. live searches
Regular answers from the continuously updated copy in under a second. Live asks the named registries now, costs a little more, and tells you exactly which ones answered.
When to use this
Every search is Regular unless you say otherwise: the request is answered from the continuously updated copy of all 58 US registries, and each record carries sources[].lastCheckedAt so you know when its registry was last read. A live search re-verifies the registries you name at query time and merges what they return through the same matcher. Use it for a contested or high-stakes decision, for a point-in-time answer you will file, or when a reviewer asks "is this still true right now".
You will build a search function that sends a live block, handles the two ways it can answer (inline, or as a background job you poll), and reads the live rows and the charge.
Before you start
- A test key on
https://sandbox.offendersearch.app. The sandbox answers aliveblock deterministically: each named registry completes and adds its row, with the productionusagequote; nothing is billed. - The
liveblock has three fields:jurisdictions(registry ids or two-letter codes to re-check),scope: "matched"(re-check whichever registries produced the Regular hits) andevidence(noneorpdf, covered in Evidence and supporting documents). Omitlivefor Regular;live: {}resolves no sources and is also Regular. - Price: the search itself plus $0.02 per completed live source, at most $2.00 per search. A source that fails is reported and not billed. Live reporting adds
usageandmeterEventsto the response (pricing).
POST /v1/search answers HTTP 200 with status: "pending", mode: "async" and a poll path — not a SearchResponse. Branch on body.status === "pending" and poll GET /v1/searches/{searchId} with your API key.Step by step
- Run a Regular search. Search Exact Match with
dob1980-01-01and noliveblock. Nousagekey, nometerEventskey; everysourceStatusrow hasfromCache: true. - Re-check one registry live. Add
"live": {"jurisdictions": ["OH"]}. One source resolves, the search runs inline, and the response gains a live row for OH plususage. - Re-check the registries that matched. Send
"live": {"scope": "matched"}instead. The Regular pass finds the person in OH, so OH is re-checked — without you naming it. - Ask for three registries and poll. Send
"live": {"jurisdictions": ["OH", "TX", "FL"]}. The answer is the200acknowledgement; poll untilstatusiscompleteorpartial.
Step 2 — one registry live, inline
curl -s https://sandbox.offendersearch.app/v1/search \
-H "X-API-Key: $OFFENDERSEARCH_KEY" -H "Content-Type: application/json" \
-d '{"query": {"firstName": "Exact", "lastName": "Match", "dob": "1980-01-01"},
"live": {"jurisdictions": ["OH"]}}'{
"searchId": "srch_4bd856ef486fc1bff31f8454",
"status": "complete",
"counts": { "records": 1, "recordsReturned": 1, "sourcesQueried": 58, "sourcesComplete": 58, "sourcesIncomplete": 0, "sourcesSkippedByScope": 0 },
"sourceStatus": [
{ "source": "OH", "status": "complete", "matched": 1, "fromCache": false, "note": null,
"incomplete": false, "incompleteReason": null, "cappedOmitted": false }
],
"records": [ { "recordId": "rec_28db9639d712c4c61e8e", "personRef": "prs_28db9639d712c4c61e8e", "matchState": "dob_match", "matchConfidence": 0.9 } ],
"warnings": [],
"usage": { "liveChecks": 1, "liveChargeUsd": 0.02, "capped": false, "display": "1 live checks · $0.02" },
"meterEvents": [],
"sandbox": true
}The live row is appended after the cached rows: fromCache: false, status: "complete". In production meterEvents carries [{ "kind": "live_source", "quantity": 1, "chargeCeilingUsd": 2.0 }]; the sandbox bills nothing, so it is [] — present, because it is present on every live answer.
Step 4 — three registries: the acknowledgement, then the poll
{
"searchId": "srch_e4370c7d8000e31a71105937",
"status": "pending",
"mode": "async",
"liveSourcesRequested": 3,
"poll": "/v1/searches/srch_e4370c7d8000e31a71105937",
"reason": "3 live sources (> 2 or not sync-eligible) — running asynchronously",
"sandbox": true
}{
"searchId": "srch_e4370c7d8000e31a71105937",
"status": "complete",
"sourceStatus": [
{ "source": "OH", "status": "complete", "matched": 1, "fromCache": false, "incomplete": false, "incompleteReason": null },
{ "source": "TX", "status": "complete", "matched": 0, "fromCache": false, "incomplete": false, "incompleteReason": null },
{ "source": "FL", "status": "complete", "matched": 0, "fromCache": false, "incomplete": false, "incompleteReason": null }
],
"records": [ { "recordId": "rec_28db9639d712c4c61e8e", "matchState": "dob_match" } ],
"usage": { "liveChecks": 3, "liveChargeUsd": 0.06, "capped": false, "display": "3 live checks · $0.06" },
"meterEvents": []
}While the job runs the poll body is {"searchId", "status": "pending" | "running", "counts": {"records": 0, "sourcesQueried": N, "sourcesIncomplete": 0}, "sourceStatus": [], "records": [], "proof": {"status": "none"}}. Poll every 2–5 seconds and cap your wait at a few minutes. The sandbox finishes before the first poll.
Reading the result
| Field | Values | What it means on a live search |
|---|---|---|
sourceStatus[].fromCache | true on Regular rows, false on live rows | The live rows are the ones you paid for. Filter on this. |
sourceStatus[].status (live row) | complete, partial | partial comes with incomplete: true and an incompleteReason (unavailable, error, not_searched, …). Regular rows use ok, error, restricted, no_coverage. |
status (envelope) | complete, partial | Any failed live source makes the whole answer partial. |
usage.liveChecks | integer | Sources that completed — the true count even when the charge is capped. |
usage.liveChargeUsd | number | min(liveChecks × 0.02, 2.00); usage.capped says whether the ceiling applied. |
meterEvents[] | [{kind: "live_source", quantity, chargeCeilingUsd}] | The billable units, for your own reconciliation. Present only when a live block ran. |
records[].sources[].lastCheckedAt | ISO timestamp | On a live hit, now. On a Regular hit, when the registry was last read. |
Which registries can be re-checked live, and each registry's health, are published by GET /v1/sources (no key needed) and explained on Regular & Live.
Edge cases and pitfalls
- A two-letter code can resolve to more than one source.
live.jurisdictionsexpands a code to each registry that covers it, which can include a national index. ReadliveSourcesRequestedon the acknowledgement, or the live rows on the result, rather than assuming one source per code. - The poll needs an API key.
GET /v1/searches/{searchId}does not accept a dashboard session, and it answers404for another account's id, an unknown id, or a job older than about an hour. Collect the result promptly; the stored records stay readable atGET /v1/searches/{searchId}/resultsfor 14 days. - Live records win. When a live source returns the same person the Regular pass found, the live copy replaces the cached one in
records[]. - A failed source is not billed and shows as a
partiallive row. Decide in advance whether apartiallive answer is acceptable for your use case or should be retried. - Batch does not take `live`.
POST /v1/batchis Regular only; alivekey in a row is a422. RunPOST /v1/searchorPOST /v1/searchesfor a live check. - Inside `POST /v1/searches` a
liveblock runs in the job and is billed when the sources complete; the202arrives before anything is charged. - `live: {}` is Regular. Only
jurisdictionsorscoperesolve sources.
Production checklist
- Every call that sends
livehandles both the inlineSearchResponseand the200acknowledgement withpoll. - Polling uses the API key, a 2–5 second interval and a hard cap.
usageandmeterEventslogged per live search and reconciled against Dashboard → Usage.- Live reserved for the decisions that need it; Regular for volume.
Related products
Product overview
- Sex offender search API
- Sex offender registry API
- Offender search API
- Background check API
- Bulk sex offender search
- Background check API pricing
- Background check API integration
- Integrations
- Free background check API
- Pay-as-you-go background check API
- Background check API in Python
- Background check API in Node.js
- NSOPW API