Jurisdiction and location filtering
Two kinds of place: where the record is (jurisdiction) and where the person lives (residence). The request fields tell them apart, and so should you.
What you'll build
A search that is scoped to the places that matter for your case: the state and county a facility sits in, the places a person has lived, or the specific jurisdiction a records website belongs to. Scoping is how a common-name search stays answerable (state and firstName are what the query_too_broad refusal asks for) and how a reviewer sees the records that could be the person rather than every namesake in the country.
Before you start
- A test key (
os_test_…) onhttps://sandbox.offendersearch.app. The sandbox people live in Sampleville, ZIP00000;Felony Recordis an Ohio (OH) court record,Misdemeanor Recorda Texas (TX) one, so state scoping is visible there. - Jurisdiction codes.
<ST>-<COUNTY>for a county (TX-BEXAR,TX-PALM-BEACH-style hyphenation) and<ST>-<RECORD-KIND>for a statewide or federal system (TX-DOC,OK-COURT,US-WARRANT). Take them fromGET /v1/criminal/jurisdictionsorGET /v1/criminal/sources; some county codes carry more tokens than the county name, so never build them by hand. - The full filter reference: Filtering & locations and Jurisdictions & codes.
Flat place fields and locations[] are exclusive.
query.state, county/jurisdiction, city, zipcode/zip and address/streetAddress are the simple form. locations[] is the rich form (kind, radius, up to ten places, ORed). Send one form or the other; both together is 422 ambiguous_location.Step by step
- Scope by jurisdiction.
state(and optionallycounty) filter on the jurisdiction that holds the record — the court, jail or agency — not on where the person lives.statealso drives thecoverageblock. - Scope by residence.
city,zipcodeandaddressmatch the record's published residenceaddress. A record with no residence on file can never match them, so they are narrower than they look. - Use locations[] for several places or a radius. Each entry has a
kind(residence,jurisdiction,any), a place, and optionallylat/lng/radiusMiles(0.1–100). A record is kept when it matches any entry and lists which inmatchedLocations. - Look up and resolve codes.
GET /v1/criminal/jurisdictions?state=TX&live=truelists what is covered and live-reachable;POST /v1/criminal/resolvemaps a records website you already know to its code. Neither is metered.
# 1. jurisdiction scope — OH returns the record, TX does not
curl -s https://sandbox.offendersearch.app/v1/criminal/search \
-H "X-API-Key: os_test_your_key" -H "Content-Type: application/json" \
-d '{ "query": { "lastName": "Record", "firstName": "Felony", "dob": "1975-06-15", "state": "OH" } }'
# 2. several places, ORed, with the rich form (no flat state/county in query)
curl -s https://sandbox.offendersearch.app/v1/criminal/search \
-H "X-API-Key: os_test_your_key" -H "Content-Type: application/json" \
-d '{ "query": { "lastName": "Rivera", "firstName": "Jordan", "dob": "1988-04-12" },
"locations": [ { "kind": "residence", "zipcode": "77060" },
{ "kind": "jurisdiction", "state": "OK" },
{ "kind": "any", "lat": 32.78, "lng": -96.8, "radiusMiles": 25 } ] }'
# 3. what is covered in Texas and reachable live
curl -s "https://sandbox.offendersearch.app/v1/criminal/jurisdictions?state=TX&live=true" \
-H "X-API-Key: os_test_your_key"
# 4. map a website you know to a code
curl -s https://sandbox.offendersearch.app/v1/criminal/resolve \
-H "X-API-Key: os_test_your_key" -H "Content-Type: application/json" \
-d '{ "website": "https://sheriff.examplecounty.gov/inmates" }'{
"status": "complete",
"counts": { "records": 2, "recordsReturned": 2, "sourcesQueried": 0, "sourcesComplete": 0, "sourcesIncomplete": 0,
"candidatesCapped": false, "exclusionsIncluded": false,
"filteredOut": { "locations": 5, "noResidence": 3 }, "unknownKept": {}, "quarantined": 0 },
"appliedFilters": {
"locations": [ { "kind": "residence", "zipcode": "77060" },
{ "kind": "jurisdiction", "state": "OK" },
{ "kind": "any", "lat": 32.78, "lng": -96.8, "radiusMiles": 25 } ],
"unknown": "keep", "chargeMatch": "any"
},
"records": [
{ "state": "TX", "county": "Harris", "address": { "type": "residence", "line1": "…", "city": "Houston", "county": "Harris", "state": "TX", "zipcode": "77060", "lat": null, "lng": null },
"matchedLocations": [0] },
{ "state": "OK", "county": "Tulsa", "address": null, "matchedLocations": [1] }
]
}{
"coverage": { "jurisdictions_total": 1410, "jurisdictions_live_connector": 727, "jurisdictions_pending_connector": 683, "states_covered": 50 },
"jurisdictions": [
{ "code": "TX-BEXAR", "state": "TX", "county": "Bexar", "recordKind": "jail-roster", "level": "county", "live": true },
{ "code": "TX-DOC", "state": "TX", "county": "(statewide)", "recordKind": "doc-inmate", "level": "state", "live": true }
],
"count": 2
}
{ "input": "https://sheriff.examplecounty.gov/inmates", "supported": true, "code": "TX-BEXAR",
"state": "TX", "county": "Bexar", "note": "equivalent data available for this jurisdiction" }Reading the result
| Field | Where | Read as |
|---|---|---|
query.state, query.county | request | Jurisdiction filters: the state and county that hold the record. county accepts Harris or Harris County; jurisdiction is an alias. |
query.city, query.zipcode, query.address | request | Residence filters on the published address. zipcode is a prefix match ("750" reaches 75001); address is a case-insensitive substring of line1; city is exact. |
locations[].kind | request | jurisdiction tests state/county of the record; residence tests the address; any tests either — but a radius, city or zipcode always describe a residence and are 422 invalid_location on jurisdiction. |
locations[].lat/lng/radiusMiles | request | All three together; 0.1–100 miles. A residence without coordinates cannot be tested and is counted under filteredOut.noResidence. |
records[].matchedLocations | response | Indexes into your locations[] that this record satisfied. Present only when locations was sent. |
counts.filteredOut | response | What the scoping removed, by reason (locations, noResidence, plus any filters keys). Present whenever filters, locations, a non-default sort or include: ["facets"] was sent. |
appliedFilters | response | The locations as validated — the keys each was read as. Use it to debug a filter that matched nothing. |
coverage.scope | response | {state, county, countyRecognized}: countyRecognized: false means no source of that county's own is in the catalog and only statewide/federal sources answered. |
jurisdictions[].recordKind | GET /v1/criminal/jurisdictions | jail-roster, court, doc-inmate, registry, warrant, exclusion. Maps to a record's recordCategory (jail, court, prison, registry, warrant, exclusion). |
supported, code, note | POST /v1/criminal/resolve | supported: false with code: null is an outcome, not an error. The response names only state, county and the neutral code. |
Edge cases and pitfalls
- `state` filters the jurisdiction, not the residence. A person who lives in Oklahoma with a Texas court case is found by
state: "TX", not"OK". Uselocations[]withkind: "any"when you mean "either". - A residence filter silently excludes records with no address. Many court and prison records publish none. Prefer
state/countyfor scoping and usecity/zipcodeonly when the address matters. - A county, city or ZIP alone does not rescue a too-broad name. The candidate walk is bounded by surname, first name and state; a
query_too_broadrefusal asks forfirstNameorstate. - `locations` with a live search of more than two sources is `422 filters_with_async_live`. Scoping works with stored searches and with live checks that run inline; narrow
live.jurisdictionsfirst. - Limits are refusals, not clamps. An empty
locationslist isinvalid_location; eleven entries istoo_many_locations;radiusMiles: 150isradius_too_large;radiusMiles: 0.05isinvalid_location. - `query.website` scopes; `live.websites` verifies. A website in
queryresolves to a state/county scope for a cached search (an explicitstate/countywins over it); an unsupported site adds aWEBSITE NOT COVERED:sentence towarnings[]and no scope. The same site inlive.websitesbecomes a live check. - `sort` is validated on every search but applied only with filters or locations.
sort: "newest"is422 invalid_filter_value; the values arerelevance,offenseDate,filedDate,dispositionDate.
Production checklist
- Load jurisdiction codes from
GET /v1/criminal/jurisdictionsat startup or daily; never hard-code them. - Choose one form per request: flat
queryplace fields orlocations[]. - Send
stateon every search where you know it; it narrows, drivescoverage, and keeps common names answerable. - Log
appliedFiltersandcounts.filteredOutwith each search so a reviewer can see what was excluded and why. - Handle
invalid_location,too_many_locations,radius_too_largeandambiguous_locationas request bugs, not retries.