Criminal Records API · v1.0.0

Filtering criminal searches

Filters, residence vs jurisdiction locations, chargeMatch, the counts that show what was removed, and five worked examples.

Livehttps://api.offendersearch.appSandboxhttps://sandbox.offendersearch.app

Criminal search filters: narrow a named search

A criminal search always starts from a name. Add filters to keep only the records you care about (felonies, convictions, court cases, a date window, a custody status) and locations to say where the person lived or where their records are. Both are optional request keys on POST /v1/criminal/search; a search without them answers exactly as before, and a filtered search is billed like any other search.

Filters test the normalized fields every record carries, so the same request works in every jurisdiction, whatever words a court or jail uses. The source’s own words are still returned beside them.

A filter never hides a record silently. Every record a filter or location removes is counted in counts.filteredOut, by the filter that removed it, and every record kept only because its value is unknown is counted in counts.unknownKept. If filtering is ever switched off, a filtered search is refused with 503 filters_unavailable; it is never answered unfiltered.

How criminal record filters combine

Every filter you send must pass (AND). The values inside one list are alternatives (OR): "severity": ["felony", "misdemeanor"] keeps either. A list filter also takes a single string.

  • Record filters test the whole record: recordCategory, custodyStatus, hasPhoto, sources, excludeVacatedOrSealed, the date ranges (offenseDate, filedDate, dispositionDate, each tested against the record’s latest date of that kind) and lookbackYears (the latest of the three within that many years of today).
  • Charge filters test the record’s charges: severity, outcome, convictedOnly, chargeCategories, statute (the start of the published citation) and chargeText (text anywhere in the published description). severityBasis narrows severity to the bases you trust and does nothing on its own. convictedOnly: true adds convicted to the outcomes a charge may have.

chargeMatch: any vs all

chargeMatch decides how charge filters meet. With "any" (the default) one charge must pass every charge filter, so “a felony conviction” means one charge that is both. With "all" each filter may be met by a different charge. Take this record, searched with "severity": ["felony"], "convictedOnly": true:

One case, two charges (abridged)
"cases": [ { "caseNumber": "2019-CF-0412", "charges": [
  { "description": "BURGLARY OF HABITATION", "disposition": "DISMISSED" },   // felony, dismissed
  { "description": "THEFT < $750",           "disposition": "GUILTY" }      // misdemeanor, convicted
] } ]
chargeMatchResult
"any" (default)Filtered out, counted under filteredOut.severity: no single charge is both a felony and a conviction.
"all"Returned with matchesFilters: [{case: 0, charge: 0}, {case: 0, charge: 1}]: the felony is met by one charge and the conviction by the other.

A record that passes a charge filter carries matchesFilters: the {case, charge} positions, in normalized.cases[].charges[], of the charges that met them. Every charge is still returned.

Criminal filter counts: filteredOut, unknownKept, quarantined

A search that sends filters or locations adds three counts and echoes what it applied:

  • counts.filteredOut: records removed, keyed by the filter that removed each one. A record is counted once, under the first filter it failed. Location misses are locations, or noResidence when a residence test had no published address to test. A record that fails the per-charge test is counted under the first charge filter you sent.
  • counts.unknownKept: returned records that passed a filter only because their value is unknown, for severity, outcome, custodyStatus and the date ranges.
  • counts.quarantined: candidate records whose normalized values are withheld (normalized.quality: "quarantined"). Their own fields are returned as usual.
  • appliedFilters: the filters as validated, with unknown and chargeMatch always present.

Unknown values: keep or exclude

Courts and jails do not all publish everything. filters.unknown decides what severity, outcome, convictedOnly, custodyStatus and the date filters do with a value that is not known. "keep" (the default) returns the record and counts it in unknownKept, so a felony that a court never labelled is not lost. "exclude" removes it and counts it in filteredOut.

Records marked vacated, expunged or sealed are returned, marked normalized.vacatedOrSealed: true, unless you send excludeVacatedOrSealed: true.

Locations: residence vs jurisdiction in a criminal search

A person can live in one county and have records in another. Each entry in locations says which you mean with kind:

kindMatchesNotes
residenceWhere the person lives: the published address (state, county, city, zipcode, or a radius).A record with no published address cannot match; it is counted as noResidence.
jurisdictionWhere the record is: the state and county of the court, jail or agency.city, zipcode and a radius are refused with 422 invalid_location.
any (default)Either of the two.city, zipcode and a radius test the residence only.
  • Up to 10 locations, ORed. A record is kept when it matches any of them, and lists the 0-based indexes of the ones it matched in matchedLocations.
  • A radius applies to the residence only. lat, lng and radiusMiles (0.1 to 100 miles) go together and are measured to the published address. A residence without coordinates cannot be tested and counts as noResidence.
  • Locations define the candidates. Each location runs its own search: a jurisdiction location searches its state and county, a residence location searches its ZIP or city when it has one, and any or a radius searches the name across every record. The candidates are pooled, then each is tested against every location. A name too broad to read in full is refused with 422 query_too_broad, never answered in part.
  • One way to say where. Send locations or the flat state, county, city and zipcode query keys, not both: both is 422 ambiguous_location. A county matches with or without the word “County”.

Criminal search filter examples

Five common searches, each with its request and an abridged response.

1. A known person and two places they lived

You know the name and date of birth, a ZIP the person lived in, and that they also spent time in Oklahoma. One location tests the residence; the other matches either an Oklahoma address or an Oklahoma record.

POST /v1/criminal/search
curl -X POST https://api.offendersearch.app/v1/criminal/search \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "query": { "lastName": "Rivera", "firstName": "Jordan", "dob": "1988-04-12" },
        "locations": [
          { "kind": "residence", "zipcode": "77060" },
          { "kind": "any", "state": "OK" }
        ]
      }'
200 OK (abridged)
{
  "status": "complete",
  "counts": { "records": 2, "recordsReturned": 2,
              "sourcesQueried": 0, "sourcesComplete": 0, "sourcesIncomplete": 0,
              "candidatesCapped": false, "exclusionsIncluded": false,
              "filteredOut": { "locations": 3, "noResidence": 1 },
              "unknownKept": {}, "quarantined": 0 },
  "records": [
    { "externalId": "TX-HARRIS-DC:case:201944871",
      "name": { "first": "JORDAN", "middle": "", "last": "RIVERA", "suffix": "" },
      "matchState": "dob_match",
      "address": { "type": "residence", "line1": "16966 NORTHCHASE DR APT 313",
                   "city": "HOUSTON", "county": "Harris", "state": "TX", "zipcode": "77060",
                   "lat": null, "lng": null },
      "matchedLocations": [0] },
    { "externalId": "OK-TULSA:booking:2021-118342",
      "name": { "first": "JORDAN", "middle": "", "last": "RIVERA", "suffix": "" },
      "matchState": "dob_match",
      "matchedLocations": [1] }
  ],
  "appliedFilters": {
    "locations": [ { "kind": "residence", "zipcode": "77060" },
                   { "kind": "any", "state": "OK" } ],
    "unknown": "keep", "chargeMatch": "any"
  }
}
  • Each record says which location it matched in matchedLocations (0 is the ZIP, 1 is Oklahoma).
  • filteredOut.noResidence: 1 is a candidate that matched neither location and had no published address to test against the ZIP. It was not dropped silently: it is counted.

2. Felony convictions in the last 7 years, anywhere

Charge filters test charges. With the default chargeMatch: "any", one charge must be both a felony and a conviction, and the record's latest offense, filing or disposition date must be within 7 years.

POST /v1/criminal/search
curl -X POST https://api.offendersearch.app/v1/criminal/search \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "query": { "lastName": "Rivera", "firstName": "Jordan", "dob": "1988-04-12" },
        "filters": { "severity": ["felony"], "convictedOnly": true, "lookbackYears": 7 }
      }'
200 OK (abridged)
{
  "status": "complete",
  "counts": { "records": 2, "recordsReturned": 2,
              "sourcesQueried": 0, "sourcesComplete": 0, "sourcesIncomplete": 0,
              "candidatesCapped": false, "exclusionsIncluded": false,
              "filteredOut": { "severity": 4, "lookbackYears": 1 },
              "unknownKept": { "severity": 1 }, "quarantined": 0 },
  "records": [
    { "externalId": "TX-HARRIS-DC:case:201944871",
      "cases": [ { "caseNumber": "201944871",
                   "charges": [ { "statute": "PC 22.02", "description": "AGG ASSAULT W/DEADLY WEAPON",
                                  "severity": "felony", "disposition": "GUILTY" } ] } ],
      "normalized": {
        "recordCategory": "court", "topSeverity": "felony",
        "hasConviction": true, "convictionCount": 1,
        "cases": [ { "stage": "closed", "caseClass": "felony",
                     "charges": [ { "category": "assault", "notACharge": false,
                                    "severity": "felony", "severityBasis": "published",
                                    "degree": null, "outcome": "convicted", "convicted": true,
                                    "custodyEvent": null } ] } ] },
      "matchesFilters": [ { "case": 0, "charge": 0 } ] }
  ],
  "appliedFilters": { "severity": ["felony"], "convictedOnly": true, "lookbackYears": 7,
                      "unknown": "keep", "chargeMatch": "any" }
}
  • matchesFilters points at the charge that met every charge filter, by its position in normalized.cases[].charges[]. Every charge on the record is still returned.
  • unknownKept.severity: 1 is a returned record whose severity could not be classified. It is kept because unknown defaults to keep; send "unknown": "exclude" to remove such records instead.

3. In custody or wanted, within 25 miles of Dallas

A radius tests where the person lives, so kind: "any" with a radius matches on the residence only. A location with no ZIP or city searches the name across every record, so a very common name can be refused with 422 query_too_broad; add a first name or date of birth.

POST /v1/criminal/search
curl -X POST https://api.offendersearch.app/v1/criminal/search \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "query": { "lastName": "Rivera", "firstName": "Jordan" },
        "locations": [ { "kind": "any", "lat": 32.78, "lng": -96.80, "radiusMiles": 25 } ],
        "filters": { "custodyStatus": ["in_custody", "wanted"] }
      }'
200 OK (abridged)
{
  "status": "complete",
  "counts": { "records": 1, "recordsReturned": 1,
              "sourcesQueried": 0, "sourcesComplete": 0, "sourcesIncomplete": 0,
              "candidatesCapped": false, "exclusionsIncluded": false,
              "filteredOut": { "noResidence": 6, "locations": 2, "custodyStatus": 3 },
              "unknownKept": {}, "quarantined": 0 },
  "records": [
    { "externalId": "TX-DALLAS:booking:26-0041177",
      "currentStatus": "in_custody",
      "normalized": { "recordCategory": "jail", "custodyStatus": "in_custody",
                      "residence": { "city": "GARLAND", "state": "TX", "zip5": "75040",
                                     "lat": 32.93, "lng": -96.62, "geoPrecision": "street" } },
      "matchedLocations": [0] }
  ],
  "appliedFilters": {
    "custodyStatus": ["in_custody", "wanted"],
    "locations": [ { "kind": "any", "lat": 32.78, "lng": -96.8, "radiusMiles": 25.0 } ],
    "unknown": "keep", "chargeMatch": "any"
  }
}
  • noResidence: 6 are candidates with no published address, or an address without coordinates, so the radius could not be tested. They are counted, not hidden.
  • appliedFilters echoes each location with the keys it was read as.

4. Violent or weapons charges, with a published severity only

severityBasis works together with severity: list every severity you accept and the bases you trust. "unknown": "exclude" removes charges and records whose severity could not be decided at all.

POST /v1/criminal/search
curl -X POST https://api.offendersearch.app/v1/criminal/search \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "query": { "lastName": "Rivera", "firstName": "Jordan", "dob": "1988-04-12" },
        "filters": {
          "chargeCategories": ["assault", "weapons"],
          "severity": ["felony", "misdemeanor", "infraction"],
          "severityBasis": ["published"],
          "unknown": "exclude"
        }
      }'
200 OK (abridged)
{
  "status": "complete",
  "counts": { "records": 1, "recordsReturned": 1,
              "sourcesQueried": 0, "sourcesComplete": 0, "sourcesIncomplete": 0,
              "candidatesCapped": false, "exclusionsIncluded": false,
              "filteredOut": { "chargeCategories": 2, "severity": 2 },
              "unknownKept": {}, "quarantined": 0 },
  "records": [
    { "externalId": "TX-HARRIS-DC:case:201944871",
      "matchesFilters": [ { "case": 0, "charge": 0 } ] }
  ],
  "appliedFilters": {
    "severity": ["felony", "misdemeanor", "infraction"], "severityBasis": ["published"],
    "chargeCategories": ["assault", "weapons"],
    "unknown": "exclude", "chargeMatch": "any"
  }
}
  • With chargeMatch: "any", one charge must be an assault or weapons charge and carry a severity the source published itself.
  • severityBasis must be sent with severity; on its own it is refused with a 422.

5. Convicted court records, vacated or sealed left out

A screening-style search: court records only, a conviction on at least one charge, and records marked vacated or sealed left out by choice. include: ["facets"] adds counts over the whole result.

POST /v1/criminal/search
curl -X POST https://api.offendersearch.app/v1/criminal/search \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "query": { "lastName": "Rivera", "firstName": "Jordan", "dob": "1988-04-12" },
        "filters": { "recordCategory": ["court"], "outcome": ["convicted"],
                     "excludeVacatedOrSealed": true },
        "include": ["facets"]
      }'
200 OK (abridged)
{
  "status": "complete",
  "counts": { "records": 3, "recordsReturned": 3,
              "sourcesQueried": 0, "sourcesComplete": 0, "sourcesIncomplete": 0,
              "candidatesCapped": false, "exclusionsIncluded": false,
              "filteredOut": { "recordCategory": 5, "excludeVacatedOrSealed": 1, "outcome": 2 },
              "unknownKept": { "outcome": 1 }, "quarantined": 0 },
  "records": [ "…" ],
  "facets": {
    "recordCategory": { "court": 3 },
    "severity": { "felony": 1, "misdemeanor": 1, "unknown": 1 },
    "outcome": { "convicted": 2, "dismissed": 1, "unknown": 1 },
    "jurisdiction.state": { "TX": 2, "OK": 1 },
    "chargeCategories": { "assault": 1, "theft": 1, "traffic": 1 },
    "custodyStatus": { "disposed": 2, "unknown": 1 }
  },
  "appliedFilters": { "recordCategory": ["court"], "outcome": ["convicted"],
                      "excludeVacatedOrSealed": true, "unknown": "keep", "chargeMatch": "any" }
}
  • facets counts every record that passed, not only this page. A record with several outcomes or charge categories counts once under each.
  • Without excludeVacatedOrSealed, a record marked vacated or sealed is returned with normalized.vacatedOrSealed: true.

Sorting and facets for filtered criminal searches

sort orders a search that sends filters or locations: relevance (the default), or offenseDate, filedDate or dispositionDate, newest first by the record’s latest date of that kind, with records that have none last.

include: ["facets"] adds facets: counts of every record that passed (not only the page) by record category, highest severity, outcome, state, charge category and custody status. include: ["normalizedDetail"] adds the rule that decided each charge’s outcome, for audits.

Filters work on cached searches and on live checks of up to 2 sources. A live search that has to run as a job refuses them with 422 filters_with_async_live.

Criminal filter errors

Every filter and location is checked before the search runs. A bad value is a 422 in the criminal error shape, with a message that names the key.

error.codeWhen
invalid_filter_valueA value outside a filter's list, a wrong type, an unknown filter key, a statute or chargeText under 2 characters, a lookbackYears outside 1–100, or a bad sort, unknown or chargeMatch. The message lists the allowed values.
invalid_date_rangeA date range with neither from nor to, a value that is not YYYY-MM-DD or not a real date, or from after to.
invalid_locationAn empty locations list; an unknown key, a bad state or ZIP, or no place in a location; lat, lng and radiusMiles not sent together or out of range; a radius below 0.1; a radius, city or zipcode on kind: "jurisdiction".
too_many_locationsMore than 10 locations.
ambiguous_locationlocations sent together with the flat state, county, jurisdiction, city, zipcode or address query keys.
radius_too_largeradiusMiles above 100.
filters_with_async_livefilters or locations on a live search that has to run as a job. Filters work on cached searches and on live checks of up to 2 sources.
filters_unavailable (503)Filtering is switched off for the moment. A filtered search is never answered unfiltered; retry after Retry-After seconds, or search without filters.

The SearchFilters object — criminal search filter fields

The filters of a criminal search. Every key is optional; filters are ANDed and the values inside one list are ORed. A list filter also takes a single string. Any other key is 422 invalid_filter_value.

Returned or accepted by POST /v1/criminal/search.

FieldTypeDescription
recordCategory
optional
array of string
one of "court", "jail", "prison", "warrant", "registry", "exclusion", "other"
The kind of record (normalized.recordCategory).
severity
optional
array of string
one of "felony", "misdemeanor", "infraction"
Charge severity. With unknown: "keep" an unclassified charge also passes.
severityBasis
optional
array of string
one of "published", "statute_code", "statute_table", "case_type", "charge_text"
Narrows severity: a charge's severity counts only when it was decided on one of these bases. Must be sent with severity; on its own it is a 422 invalid_filter_value.
outcome
optional
array of string
one of "convicted", "pending", "dismissed", "nolle_prosequi", "acquitted", "deferred_or_diverted", "vacated", "warrant", "transferred", "closed_unspecified", "administrative"
Charge outcome class. With unknown: "keep" a charge whose outcome is unknown also passes.
convictedOnly
optional · default false
booleantrue adds convicted to the outcomes a charge may have. Alone it means a convicted charge; with outcome the charge may have any of those outcomes or convicted.
chargeCategories
optional
array of string
one of "supervision", "homicide", "sex_offense", "robbery", "domestic_violence", "dui", "assault", "weapons", "burglary", "fraud", "theft", "drug_distribution", "drug_possession", "traffic", "public_order", "other"
What the charge is about.
chargeMatch
optional · default "any"
string
one of "any", "all"
any: one charge must meet every charge filter. all: each charge filter may be met by a different charge on the record.
statute
optional
stringThe start of the published statute citation, ignoring spaces and case: 22.02 matches 22.02(a)(1). At least 2 characters.
Example "22.02"
chargeText
optional
stringText that appears anywhere in the published charge description, ignoring case. At least 2 characters.
Example "assault"
custodyStatus
optional
array of string
one of "in_custody", "released", "on_parole", "on_probation", "active_warrant", "wanted", "registered", "disposed", "absconder", "deceased", "excluded"
The record's custody status. With unknown: "keep" a record whose status is unknown also passes.
offenseDate
optional
DateRangeThe record's latest offense date must fall in this range.
filedDate
optional
DateRangeThe record's latest filing date must fall in this range.
dispositionDate
optional
DateRangeThe record's latest disposition date must fall in this range.
lookbackYears
optional
integerThe latest of the record's offense, filing and disposition dates must be within this many years of today. A whole number from 1 to 100.
Example 7
hasPhoto
optional · default false
booleantrue keeps only records with a published photograph.
sources
optional
array of stringKeep only records from these source codes.
Example ["TX-HARRIS-DC"]
excludeVacatedOrSealed
optional · default false
booleantrue leaves out records marked normalized.vacatedOrSealed. By default they are returned, marked.
unknown
optional · default "keep"
string
one of "keep", "exclude"
What severity, outcome, convictedOnly, custodyStatus and the date filters do with a value that is not known. keep returns the record and counts it in counts.unknownKept; exclude removes it and counts it in counts.filteredOut.

The DateRange object — date filter fields

An inclusive ISO date range. Send from, to or both; from after to, a non-ISO value or an impossible date is 422 invalid_date_range.

Returned or accepted by POST /v1/criminal/search.

FieldTypeDescription
from
optional
string (date)First date in the range.
Example "2019-01-01"
to
optional
string (date)Last date in the range.
Example "2026-10-23"

The Location object — residence and jurisdiction fields

One place in locations[]. It needs at least one of state, county, city, zipcode, or a radius (lat, lng and radiusMiles together). Any other key is 422 invalid_location.

Returned or accepted by POST /v1/criminal/search.

FieldTypeDescription
kind
optional · default "any"
string
one of "residence", "jurisdiction", "any"
residence matches where the person lives (the published address). jurisdiction matches where the record is (the court, jail or agency's state and county). any matches either. A radius, city and zipcode describe a residence: on jurisdiction they are 422 invalid_location, and on any they test the residence only.
state
optional
stringTwo-letter state code.
Example "TX"
county
optional
stringCounty name; Harris and Harris County match the same county.
Example "Harris"
city
optional
stringCity of the residence, matched exactly, ignoring case.
Example "Houston"
zipcode
optional
stringFive-digit ZIP of the residence. A ZIP+4 is read as its first five digits; zip is accepted as another name for this key.
Example "77060"
lat
optional
numberLatitude of the radius centre, -90 to 90. Requires lng and radiusMiles.
Example 32.78
lng
optional
numberLongitude of the radius centre, -180 to 180. Requires lat and radiusMiles.
Example -96.8
radiusMiles
optional
numberMiles from lat/lng to the person's published residence, 0.1 to 100. Above 100 is 422 radius_too_large; below 0.1 is 422 invalid_location. A residence without coordinates cannot be tested and counts as noResidence.
Example 25

The SearchFacets object — facet counts on a filtered search

Counts over every record that passed the filters: the whole result, not the page. Each key maps a value to a number of records.

Returned or accepted by POST /v1/criminal/search, GET /v1/criminal/searches/{searchId}.

FieldTypeDescription
recordCategory
objectRecords by normalized.recordCategory.
Example {"court":9,"jail":5}
severity
objectRecords by their highest charge severity; unknown when none is classified.
Example {"felony":2,"misdemeanor":9,"unknown":3}
outcome
objectRecords by outcome class; a record with several outcomes counts once under each.
Example {"convicted":6,"dismissed":4,"pending":4}
jurisdiction.state
objectRecords by the state where the record is; the 60 largest.
Example {"TX":12,"OK":2}
chargeCategories
objectRecords by charge category; a record with several categories counts once under each.
Example {"traffic":7,"theft":3,"assault":2}
custodyStatus
objectRecords by normalized.custodyStatus.
Example {"released":10,"in_custody":4}

Product overview