Selecting registries and jurisdictions
By default a search asks all 58 US registries. Here is how to ask fewer on purpose, what that costs you in coverage, and how to tell a filter from a scope.
When to use this
Three different questions get confused under "search Texas": *is this person registered by Texas*, *does this person live in Texas*, and *only ask the Texas registry*. The API answers each with a different field — jurisdictions, query.state and locationScoped — and reports what each one did to the fan-out in counts and warnings. You will build a search that uses the right one for the question you are actually asking, and a reader that notices when a scope has hidden something.
Before you start
- A test key on
https://sandbox.offendersearch.app. The sandbox person Out State is registered by Oklahoma's registry and lives in Texas (registrationState: "OK",addressStates: ["TX"]) — exactly the case these fields are about. - Codes: the 58 registry ids (
AK … WY, plusDC,PR,GU,AS,CNMI,USVI,TRIBALand the national index) and any two-letter code a registry covers, in any case.GET /v1/sourceslists them, no key required. - An unrecognised code is a
422, never an empty result:["XX"]and["TEXAS"]are refused;["tx"]is accepted.
query.state does not choose registries. It keeps records whose registrationState or one of whose addressStates is that state, and still asks all 58 registries. Only jurisdictions or locationScoped: true narrow the fan-out — and when they do, a registry that was never asked contributes a zero that means *unknown*, not *no match*.Step by step
- Filter by state. Search Out State with
"state": "TX". One record: the person lives in Texas.sourcesQueriedis 58 — nothing was narrowed; the Oklahoma registry that holds the record was asked. - Scope by state. Add
"locationScoped": true. Now only the registries covering Texas run;sourcesQuerieddrops to 2,sourcesSkippedByScopeis 56, and the record is gone — it is held by a registry that was not asked. ANARROWED SEARCH:warning says so. - Name a registry. Drop
stateand send"jurisdictions": ["OK"]. The Oklahoma registry (and a tribal registry whose coverage lists Oklahoma) run; the record is back,sourcesQueried2, nothing skipped by scope. - Look up a code. Call
GET /v1/sourcesto see each registry's id, the codes it covers, its legal status and health.
Steps 1–3 — three requests, three answers
# 1. filter: all 58 registries asked, records kept if registered OR resident in TX
curl -s https://sandbox.offendersearch.app/v1/search -H "X-API-Key: $OFFENDERSEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{"query": {"firstName": "Out", "lastName": "State", "state": "TX"}}'
# 2. scope: only the registries that cover TX are asked
curl -s https://sandbox.offendersearch.app/v1/search -H "X-API-Key: $OFFENDERSEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{"query": {"firstName": "Out", "lastName": "State", "state": "TX"}, "locationScoped": true}'
# 3. name the registry: the Oklahoma roster
curl -s https://sandbox.offendersearch.app/v1/search -H "X-API-Key: $OFFENDERSEARCH_KEY" \
-H "Content-Type: application/json" \
-d '{"query": {"firstName": "Out", "lastName": "State"}, "jurisdictions": ["OK"]}'{
"searchId": "srch_5e1443b11d9ab4002bed9443",
"status": "complete",
"counts": { "records": 0, "recordsReturned": 0, "sourcesQueried": 2, "sourcesComplete": 2, "sourcesIncomplete": 0, "sourcesSkippedByScope": 56 },
"sourceStatus": [
{ "source": "TRIBAL", "status": "ok", "matched": 0, "fromCache": true, "incomplete": false, "incompleteReason": null },
{ "source": "TX", "status": "ok", "matched": 0, "fromCache": true, "incomplete": false, "incompleteReason": null }
],
"records": [],
"warnings": [
"NARROWED SEARCH: `locationScoped: true` ran only the 2 registries that cover the requested state and did NOT query the other 56. A person REGISTERED by another state's registry while RESIDING in the one you asked for is not in this answer — 7.0% of our records list a residence outside the registry holding them. …"
],
"sandbox": true
}status is complete and records is empty, and yet the person is registered. The scope, not the registries, produced the zero — which is why sourcesSkippedByScope and the warning exist. If you scope, carry them with the outcome.
{
"counts": { "records": 1, "sourcesQueried": 2, "sourcesComplete": 2, "sourcesIncomplete": 0, "sourcesSkippedByScope": 0 },
"sourceStatus": [
{ "source": "OK", "status": "ok", "matched": 1, "fromCache": true, "incomplete": false, "incompleteReason": null },
{ "source": "TRIBAL", "status": "ok", "matched": 0, "fromCache": true, "incomplete": false, "incompleteReason": null }
],
"records": [ { "recordId": "rec_21b7a06132139de2ba35", "registrationState": "OK", "addressStates": ["TX"], "matchConfidence": 0.9, "matchState": null } ],
"warnings": []
}Step 4 — the catalog
curl -s https://api.offendersearch.app/v1/sources | head -c 600[
{ "id": "AL", "name": "Alabama Sex Offender Registry (ALEA)", "covers": ["AL"], "registries": 1,
"scope": "state", "status": "live", "legal": { "commercialUse": "restricted" },
"health": { "lastSuccessAt": "2026-10-25T03:12:40+00:00", "typicalLatencyMs": 820, "ageSeconds": 39600, "freshFor": ["daily", "weekly"] } },
{ "id": "TX", "name": "Texas DPS Sex Offender Registry", "covers": ["TX"], "registries": 1,
"scope": "state", "status": "live", "legal": { "commercialUse": "unknown" },
"health": { "lastSuccessAt": "2026-10-25T02:58:03+00:00", "typicalLatencyMs": 1210, "ageSeconds": 40500, "freshFor": ["daily", "weekly"] } }
]On the sandbox the same call returns the production catalog with every health field null, because the sandbox holds no ingest; the health values above are illustrative of the production shape. Read ids and names from the call, never from a hard-coded list.
Reading the result
| Field | Values | What it tells you |
|---|---|---|
counts.sourcesQueried | integer | 58 on a default search, including one with query.state. Smaller only with jurisdictions or locationScoped: true. |
counts.sourcesSkippedByScope | integer | Registries not asked because of locationScoped. Above 0, a zero in records is not a clear. |
warnings[] | NARROWED SEARCH: … | Present whenever a scope reduced the fan-out. |
sourceStatus[].source | registry id | Which registries actually ran; matched is each one's contribution. |
sourceStatus[].status | ok, error, restricted, no_coverage | restricted: a legal gate kept this registry out of a commercial search — surfaced, not dropped. no_coverage: the registry does not cover this query. |
records[].registrationState | two-letter code | The registry that holds the person. |
records[].addressStates[] | codes | Where the person's addresses are. The two together say which half of a state filter matched. |
SourceInfo.legal.commercialUse | restricted, prohibited, unknown (allowed documented, not in use) | A registry marked prohibited or not billable is excluded from commercial scopes; a search restricted to such registries meters nothing. |
Edge cases and pitfalls
- Full state names belong in `query.state`, not in `jurisdictions`.
"state": "Florida"is accepted and meansFL;"jurisdictions": ["FLORIDA"]is a422."state": "Narnia"is also a422:Unknown state 'Narnia'. - A code can select more than one registry.
["OK"]ran two registries above because a tribal registry's coverage lists Oklahoma. CountsourceStatusrows rather than assuming one. - To narrow to residents, filter on
addressStatesin your own code after astatesearch; to narrow to a registry's roster, usejurisdictions: ["FL"].statealone gives you the union. - Scoping is cheaper but hides out-of-state registrants: about 7% of records list a residence outside the registry that holds them. Reserve
locationScopedfor cases where the registration state is genuinely known. - `jurisdictions` in a batch envelope applies to every row; a row cannot widen it back.
- `live.jurisdictions` is a different list — it names registries to re-check live, and does not narrow the Regular fan-out. See Regular vs. live searches.
- A `jurisdictions`-only request is valid:
{"query": {}, "jurisdictions": ["WY"]}is a scoped sweep and paginates at 500 records per page via the API; a nameless search is metered per record under the location-record policy.
Production checklist
- Default searches send no
jurisdictionsand nolocationScoped; scopes are an explicit product decision. sourcesSkippedByScope > 0is carried with the outcome and shown to reviewers.- Codes come from
GET /v1/sources, not from a hard-coded list; a422onjurisdictionsis treated as a bug, not an empty result. registrationStateandaddressStatesare both stored with a hit.
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