Offendersearch
Criminal API Reference

The Criminal Record object

One normalized record across every jurisdiction, the dob object, lifecycle, and re-fetch by id.

Base URL https://api.offendersearch.app

One normalized schema, five record kinds

Every match in a search response is a normalized Criminal Record — one object shape regardless of where the underlying data came from. It carries identity (name, aliases, the source’s own externalId), the birth-evidence dob object, demographics, a custody/status block, court cases[] with their charges[], per-jurisdiction provenance in sources[], a lifecycle block, and the match metadata that explains why the record is in your results. The same schema is returned by POST /v1/criminal/search and by GET /v1/criminal/records/{recordId}, so one type definition covers the whole product.

recordType is always the constant "criminal_record". The finer kind of the underlying record — a jail booking, a prison inmate record, a court case, a warrant, or a registry entry — is not a field you read. It is a shape you recognise from which fields are populated, plus sources[].level (county / state / federal) and the record-kind suffix on sources[].code. Which fields to expect for each kind is the field-availability matrix in the record-types section below.

Not a consumer report. Records from the standard endpoints may not be used for any purpose under the Fair Credit Reporting Act (15 U.S.C. § 1681 et seq.) — employment, tenant screening, credit, insurance, or any other FCRA-covered use. Every response repeats this verbatim in its legal.notice field, and there is no request parameter that removes it. FCRA-regulated records come only from orders on the FCRA API. A high matchConfidence on a name-only match is a candidate to review, not a confirmed person.

Reading empty values — null vs "" vs []

No key is ever omitted from a record. Every key documented here is present on every record, every time — so you test the value, never for key existence. There is exactly one exception, and the response announces it: sourceData (see below). A record uses three different empties and they are not interchangeable:

ValueMeaning
""A string the source does not publish, or publishes blank. Used for text identity fields — name.first, externalId, matchBasis, a charge’s statute, a case’s caseNumber. Read it as “no value on file for this field”, never as a statement about the person.
nullGenuinely unknown or not applicable — a typed absence, used where an empty string would be a lie: a date (dob.date, releaseDate, filedDate), a number (dob.age, charge.counts), a nested object, or a demographic (sex, race). On sex it is not the string "Unknown" — a source that publishes that literal word arrives as sex: "Unknown", a populated field.
[]The record carries none of these list items — aliases, cases, a case’s charges, sources. Read [] as “none on file”: either the source publishes none of that category at all, or it does and this person has none. The two are not distinguished.

The single most important instance is a date. dob.date is null — never "", never a fabricated day — whenever the source does not publish a full calendar date. A null dob.date beside a populated dob.birthYear or dob.age means “we know the year/age, not the day”, not “we know nothing”. Read the whole dob object together, keyed on dobPrecision — see the birth-evidence object.

The one field held back — sourceData and omittedFields

The per-source annex (sourceData) — raw per-jurisdiction extras that do not fit the normalized shape, such as a booking’s bond figure — is omitted from the default response. It is the only field ever held back, and you never have to guess: the response carries a top-level omittedFields array, always.

omittedFields is always present
"omittedFields": ["sourceData"]   // a default search — the annex was left out
"omittedFields": []               // include: ["sourceData"] was sent — nothing omitted
The rule, stated once. If you build anything that says “nothing on file” for the annex, read omittedFields first. An absent sourceData with omittedFields: [] means the source published no annex for this person; with omittedFields: ["sourceData"] it means you did not request it — send "include": ["sourceData"] to get it. For every other field there is nothing to read: they are never shaped away, so their empties are always a fact about the record, not about your request. The annex is free-form JSONB and its inner keys vary by jurisdiction — do not code against a fixed shape inside it.

Identity — and the durable reference to store

  • name is four string parts (first, middle, last, suffix), each of which can be "". There is no full key — join them yourself for display. Casing is not normalized; matching is case-insensitive regardless. A "" first is meaningful for matching (kept and flagged firstName:absent); a "" last cannot satisfy a surname query and is dropped.
  • aliases is other names on file, in whatever shape the source uses — "SURNAME, GIVEN", "Given Surname" and bare tokens all occur. Do not parse them positionally.
  • externalId is the data source’s own stable key — a booking number, a Department of Corrections number, an offender id, or a case number, verbatim. What lands here varies by record kind (booking number for a jail booking, DOC number for an inmate, case number for a court/warrant record, offender id for a registry) and is a useful signal of the kind in itself. "" when the source publishes no stable per-person id.
externalId is the durable anchor, not a global primary key. It is unique only within its source — two different jurisdictions can independently issue the same booking or case number for different people. To reference a specific person durably, store the sources[].code + externalId pair. The recordId path parameter on the re-fetch endpoint is a merge-derived label, not a stable key — it is not even serialized onto a records[] entry — so the same person can carry a different one on a state-scoped search and a nationwide search. There is no stable cross-source person id; the identity comparison across jurisdictions is name + dob + state.

dob — the birth-evidence object

One object, several kinds of evidence. Read it as a whole; dobPrecision tells you which parts are real. This is the discipline that lets one query reach every kind of birth evidence — extended for the jail-roster reality that many custody sources publish only an age.

dobPrecisiondatebirthYearbirthMonthageWhat is known
fullsetsetusually setusually setThe full date, trustworthy to the day.
month_yearnullsetsetmaybeYear and month, no day.
yearnullsetnullmaybeOnly the year.
agenullnullnullsetOnly an age — the common jail-roster case.
nonenullnullnullnullNo birth evidence at all.

date is non-null if and only if dobPrecision is full, so you can hand it to a date parser without a length check — it is a real full date or it is null, never a bare year or a 1/1 sentinel. A recognised sentinel (a 1/1 placeholder, 9999-09-09, and the like) is collapsed to the best real tier it can support (year, else age, else none); the January 1st is never emitted as if it were a real birthday. birthYear and dobPrecision are also flattened to the record level (record.birthYear, record.dobPrecision) so year/precision logic can read one top-level field.

You never have to compensate for a source’s poorer precision in your query. A dob you send is matched against a full date, else a year (and month, when both sides publish one), else an age — one query reaches all of them. Widening happens when we match; it never happens in what we store or send. And do not synthesize a date of birth from an age — the API returns dob.date: null for age-only records rather than a plausible wrong date, and you should not manufacture one either. Full behaviour is in Searching by date of birth.

Demographics, custody & status

sex and race are free text, not normalized — "Male", "M" and "F" co-occur, sometimes within one response when a search spans two jurisdictions. null means the source published nothing. state and county describe where the record is held — a jurisdiction, not necessarily where the person lives. A top-level address (line1/city/county/state/zipcode) is promoted where the source publishes a residence (registry and wanted records); an incarcerated inmate has a custodyFacility, not a home address. There is no lat/lng radius yet.

Seven fields describe the custody picture — the part of the schema the identity-only products have no equivalent for:

  • currentStatus — a closed vocabulary: in_custody, released, on_parole, on_probation, active_warrant, wanted, registered, disposed, absconder, deceased, unknown. "unknown" is the default and a real answer: the source published no status, not that we failed to read one.
  • currentlyIncarcerated / everIncarcerated — plain booleans, never null; false unless the record affirmatively places the person in custody. currentlyIncarcerated: false is not “released” — it is also true of someone never booked, of a court-only record, and of a warrant. Read everIncarcerated and the dates for the fuller picture.
  • dateIncarcerated / releaseDate / custodyFacility — Optional, default null. dateIncarcerated is the booking date on a jail booking, the admission date on a DOC inmate, and typically null on a court-only or warrant record.
  • mugshotUrl — a single photo URL where the source publishes one; a criminal record carries at most one photo, as a scalar. It is a hotlink to the source’s own server, not a copy we host — fetch and cache it server-side rather than putting the URL in a browser <img src>.

cases[] and charges[]

The court-record side: cases, each carrying its charges. cases: [] when the record is a pure custody/roster entry — a jail booking or a bare inmate row often has no court case attached, because the custody facts live in the status block, not in a case. Things to key a parser on:

  • A charge’s severity: null is “not classified by the source”, not “minor”. The vocabulary is felony / misdemeanor / infraction and nothing else; anything the source did not classify is null. Do not infer severity from a description string.
  • counts: null is neither zero nor one — it means the source did not say how many. Reading it as 1 undercounts a multi-count charge row.
  • offenseDate is when the crime happened, not when it was adjudicated — it can be years before dispositionDate. Do not substitute one for the other.
  • cases[].county is where the court sits, never a residence. A person booked in one county with a court case in another will show different values in county, cases[].county and sources[].code — that is real data.
  • A case’s sentence object is present only when the source publishes one — the key is omitted entirely otherwise, so “absent” is distinguishable from an empty sentence.

sources[] — provenance, with neutral codes

A person can appear in more than one jurisdiction — booked in one county, a court case in another, an inmate record statewide. They come back as one record, not three, and sources[] names every jurisdiction that record was built from, one entry per jurisdiction.

KeyWhat it holds
codeThe neutral PUBLIC jurisdiction code — <ST>-<COUNTY> (a county, e.g. TX-DALLAS) or <ST>-<RECORD-KIND> (a statewide/federal system, e.g. TX-DOC, OK-COURT, US-WARRANT). The suffix vocabulary is fixed: JAIL, COURT, DOC, REGISTRY, WARRANT. It is the same vocabulary live.jurisdictions accepts, so you can send it straight back.
levelcounty / state / federal.
recordUrlThe source’s own page for this record, where one exists — "" where the source has no per-record URL.
lastCheckedAtISO-8601 — when we last saw this person at this jurisdiction. This is the record’s currency, and the only freshness answer. On a merged record each entry carries its own, and they can differ.
livetrue when this entry was re-verified live in the current request. On a cached search it is false. The top-level liveChecked says the whole record was re-verified this request; sources[].live says which specific jurisdiction entry was the live one.
code is always the neutral PUBLIC code — it names a state and a place and nothing about how the data was obtained. The neutralization is enforced at the wire boundary and applies to every code the API emits — on a record, in sourceStatus[], and in any warnings sentence. Pair sources[].code with externalId for the durable reference; read sources[] when the question is “which jurisdictions is this person in?”.

lifecycle and match metadata

Records are never hard-deleted — a person disappearing from a source is data, reported in lifecycle rather than by the record vanishing. The wire block carries six keys: firstSeen, lastSeen, presentInLastSweep, droppedOut, droppedOutDetected, and dropInterpretation. A droppedOut: true record is still returned (kept and flagged), because disappearance is information — a booking that dropped out probably means release. Read lifecycle before treating a record as “current”. Treat dropInterpretation as an open string keyed on record kind — you will see released (roster kinds), source_removed (court/registry kinds) and unknown; bucket anything else.

Five fields explain why this record is in your results — matchState, unverified, matchConfidence, matchBasis, matchDetail. They are written at search time and describe the relationship between your query and this record, not the person, so the same person can carry different values in two searches — do not store them as facts about the person.

  • matchState — the coarse DOB/age outcome: dob_match (full date confirmed), year_match, age_match (consistent within ±1 year), no_dob_age_year (nothing comparable — present because the name matched), or null when you queried no date/age. A dob_mismatch record is dropped and never appears.
  • unverified — the coarse twin: true iff matchState == "no_dob_age_year". A DOB-filtered response is a mixture; branch on unverified for the coarse split, on matchState for the full picture.
  • matchConfidence — a float in 0.0–0.99. It is a ceiling on evidence held, not a probability; the API never claims a certainty of 1.0. A firstName:mismatch record is still returned (lower confidence), because the surname is the only hard name filter — threshold on matchConfidence / matchState and decide your own gate.
  • matchBasis / matchDetail — space-separated field:how tokens (machine-readable), and the same reading as one plain sentence. The criminal matchDetail is a string, not a nested object.

The full arithmetic, thresholds and token vocabulary live in Matching & confidence.

The five record types — what each represents

Five kinds appear, and they populate genuinely different field groups. To tell the kind from a record, read sources[].code’s record-kind suffix and sources[].level, then corroborate with which field groups are filled — custody fields vs cases[]. A county code with no suffix (TX-DALLAS) is a county jail booking; a suffixed statewide code (TX-DOC) is a statewide system of that kind.

  • Jail booking (county) — a custody event. Booking date and charges lead; cases[] is often []; DOB is frequently age-only (rosters publish an age, not a date). By far the largest record type.
  • DOC inmate (state/federal prison) — a statewide custody record. Carries a facility, an admission date, the conviction case(s), and often a parole/release date. DOB precision varies by state.
  • Court case — a disposition record. The cases[]/charges[] block is the whole point; no custody facility. Defendant birth evidence is the most variable of any type — full DOB, year-only, or none (privacy redaction), by state.
  • Warrant / wanted — an open matter. The strongest status signal of any type (active_warrant / wanted); the underlying charge is often present as an open case (no disposition), or cases: [] where only the wanted notice is published. Can be federal.
  • Registry (statewide) — a specialized state registry (violent-offender, drug/meth, career, animal-abuse, and similar — not sex-offender, which is a separate product). Identity-led: name, often a full DOB, a classification, sometimes a photo. Custody fields are typically empty.

Field availability by record type

A field not published by a record type comes back null / "" / [], never a fabricated value. Read null, not a guess, for an absent field.

Field groupjail bookingDOC inmatecourt casewarrantregistry
sources[].levelcountystatecounty / statecounty / state / federalstate
code suffixnoneDOCCOURTWARRANTREGISTRY
currentStatusin_custody / releasedin_custody / on_paroledisposedactive_warrant / wantedregistered
dob.dobPrecisionoften ageusually fullfull / year / nonemixed per personfull / year
dateIncarceratedbooking dateadmission datenullnullnull
releaseDatewhen releasedparole / EOSnullnullwhere published
custodyFacilitythe jailthe prisonnullnullnull
mugshotUrlusually setusually setoften nullusually seta subset
cases[]often []conviction case(s)the point of the recordunderlying case, often openconviction case or []
charges[].dispositionoften "" (pre-trial)GUILTY / etc.as publishedoften "" (open)as published
dropInterpretation when droppedreleasedreleasedsource_removedsource_removedsource_removed

The strongest matchState a record can reach is capped by the coarsest of (what the source published) and (what you queried): an age-only jail roster tops out at age_match, a year-only source at year_match. The API widens on your behalf so one dob query reaches all four kinds — see Searching by date of birth.

A complete record for each type

Synthetic identities; every key, type, empty value and structure is exactly as the API emits it. Note how the same object shape carries a jail booking, an inmate, a court case, a warrant and a registry entry — the kind is the pattern of populated fields, not a recordType value.

Jail booking (county) — age-only DOB, cases: []
{
  "recordType": "criminal_record",
  "externalId": "TX-DALLAS:booking:889201",
  "name": { "first": "ALEX", "middle": "Q", "last": "HAMILTON", "suffix": "" },
  "aliases": ["HAMILTON, A Q"],
  "dob": {
    "date": null, "birthYear": null, "birthMonth": null,
    "age": 36, "ageAsOf": "2026-03-01", "dobPrecision": "age"
  },
  "birthYear": null, "dobPrecision": "age",
  "sex": "M", "race": "White", "state": "TX", "county": "Dallas",
  "currentStatus": "in_custody",
  "currentlyIncarcerated": true, "everIncarcerated": true,
  "dateIncarcerated": "2026-03-01", "releaseDate": null,
  "custodyFacility": "Dallas County Jail",
  "mugshotUrl": "https://example-roster.gov/mugshots/889201.jpg",
  "matchConfidence": 0.73,
  "matchBasis": "lastName:exact firstName:exact dob:age",
  "matchDetail": "name agrees; age within tolerance (no birth date published)",
  "matchState": "age_match", "unverified": false,
  "cases": [],
  "sources": [
    { "code": "TX-DALLAS", "level": "county",
      "recordUrl": "https://example-roster.gov/booking/889201",
      "lastCheckedAt": "2026-08-24T05:12:00+00:00", "live": false }
  ],
  "lifecycle": {
    "firstSeen": "2026-03-01T00:00:00+00:00",
    "lastSeen": "2026-08-24T05:12:00+00:00",
    "presentInLastSweep": true, "droppedOut": false,
    "droppedOutDetected": null, "dropInterpretation": "unknown"
  },
  "liveChecked": false
}
DOC inmate (statewide prison) — full DOB, conviction case
{
  "recordType": "criminal_record",
  "externalId": "TX-DOC:offender:01288340",
  "name": { "first": "JORDAN", "middle": "", "last": "REYES", "suffix": "" },
  "aliases": [],
  "dob": {
    "date": "1985-11-02", "birthYear": 1985, "birthMonth": 11,
    "age": 40, "ageAsOf": "2026-08-20", "dobPrecision": "full"
  },
  "birthYear": 1985, "dobPrecision": "full",
  "sex": "M", "race": "Hispanic", "state": "TX", "county": null,
  "currentStatus": "in_custody",
  "currentlyIncarcerated": true, "everIncarcerated": true,
  "dateIncarcerated": "2019-07-15", "releaseDate": "2031-07-14",
  "actualReleaseDate": null,
  "custodyFacility": "Example State Penitentiary Unit",
  "custody": { "custodyClass": "Medium", "description": "General Population", "facilityType": "State Prison" },
  "sentence": {
    "length": "15y", "minDate": "2029-01-14", "maxDate": "2031-07-14",
    "probation": "", "fine": null, "dischargeDate": null,
    "years": 15, "months": 0, "goodTimeLaw": "TRUTH_IN_SENTENCING", "truthInSentencing": "Y"
  },
  "parole": {
    "eligibilityDate": "2027-01-15", "nextReviewDate": "2027-04-01",
    "reviewStatus": "PENDING", "lastDecision": null
  },
  "address": {
    "type": "residence", "line1": "1400 Maple St", "city": "DALLAS",
    "county": "Dallas", "state": "TX", "zipcode": "75201", "lat": null, "lng": null
  },
  "priorFelony": true, "veteranStatus": "Non-Veteran",
  "mugshotUrl": "https://example-doc.gov/photo/01288340.jpg",
  "matchConfidence": 0.95,
  "matchBasis": "lastName:exact firstName:exact dob:match",
  "matchDetail": "name and full date of birth agree",
  "matchState": "dob_match", "unverified": false,
  "cases": [
    {
      "caseNumber": "F-2019-11552",
      "court": "182nd District Court",
      "county": "Harris", "state": "TX", "level": "state",
      "filedDate": "2019-03-10", "status": "disposed",
      "charges": [
        { "statute": "TPC 29.03", "description": "AGGRAVATED ROBBERY",
          "severity": "felony", "disposition": "GUILTY",
          "dispositionDate": "2019-07-10", "offenseDate": "2018-12-22",
          "plea": "NOT GUILTY", "counts": 1 }
      ]
    }
  ],
  "sources": [
    { "code": "TX-DOC", "level": "state",
      "recordUrl": "https://example-doc.gov/inmate/01288340",
      "lastCheckedAt": "2026-08-25T02:00:00+00:00", "live": false }
  ],
  "lifecycle": {
    "firstSeen": "2019-07-16T00:00:00+00:00",
    "lastSeen": "2026-08-25T02:00:00+00:00",
    "presentInLastSweep": true, "droppedOut": false,
    "droppedOutDetected": null, "dropInterpretation": "unknown"
  },
  "liveChecked": false
}
Court case — dispositions, no custody facility
{
  "recordType": "criminal_record",
  "externalId": "OK-COURT:case:CF-2021-4471",
  "name": { "first": "MORGAN", "middle": "T", "last": "OKAFOR", "suffix": "" },
  "aliases": ["OKAFOR, MORGAN"],
  "dob": {
    "date": "1990-06-18", "birthYear": 1990, "birthMonth": 6,
    "age": null, "ageAsOf": null, "dobPrecision": "full"
  },
  "birthYear": 1990, "dobPrecision": "full",
  "sex": "F", "race": "Black", "state": "OK", "county": "Oklahoma",
  "currentStatus": "disposed",
  "currentlyIncarcerated": false, "everIncarcerated": false,
  "dateIncarcerated": null, "releaseDate": null,
  "custodyFacility": null, "mugshotUrl": null,
  "matchConfidence": 0.95,
  "matchBasis": "lastName:exact firstName:exact dob:match",
  "matchDetail": "name and full date of birth agree",
  "matchState": "dob_match", "unverified": false,
  "cases": [
    {
      "caseNumber": "CF-2021-4471",
      "court": "Oklahoma County District Court",
      "county": "Oklahoma", "state": "OK", "level": "county",
      "filedDate": "2021-09-02", "status": "disposed",
      "charges": [
        { "statute": "21 O.S. 1713", "description": "LARCENY OF MERCHANDISE FROM RETAILER",
          "severity": "misdemeanor", "disposition": "DEFERRED",
          "dispositionDate": "2022-01-19", "offenseDate": "2021-08-11",
          "plea": "GUILTY", "counts": 2 },
        { "statute": "21 O.S. 1451", "description": "BURGLARY IN THE SECOND DEGREE",
          "severity": "felony", "disposition": "DISMISSED",
          "dispositionDate": "2022-01-19", "offenseDate": "2021-08-11",
          "plea": "", "counts": 1 }
      ]
    }
  ],
  "sources": [
    { "code": "OK-COURT", "level": "state",
      "recordUrl": "https://example-court.gov/case/CF-2021-4471",
      "lastCheckedAt": "2026-08-23T22:40:00+00:00", "live": false }
  ],
  "lifecycle": {
    "firstSeen": "2021-09-03T00:00:00+00:00",
    "lastSeen": "2026-08-23T22:40:00+00:00",
    "presentInLastSweep": true, "droppedOut": false,
    "droppedOutDetected": null, "dropInterpretation": "unknown"
  },
  "liveChecked": false
}
Warrant / wanted (federal) — open matter, year-only DOB
{
  "recordType": "criminal_record",
  "externalId": "US-WARRANT:notice:2025-00417",
  "name": { "first": "CASEY", "middle": "", "last": "NGUYEN", "suffix": "" },
  "aliases": ["NGUYEN, CASEY", "C NGUYEN"],
  "dob": {
    "date": null, "birthYear": 1982, "birthMonth": null,
    "age": null, "ageAsOf": null, "dobPrecision": "year"
  },
  "birthYear": 1982, "dobPrecision": "year",
  "sex": "M", "race": null, "state": "US", "county": null,
  "currentStatus": "active_warrant",
  "currentlyIncarcerated": false, "everIncarcerated": false,
  "dateIncarcerated": null, "releaseDate": null,
  "custodyFacility": null,
  "mugshotUrl": "https://example-warrants.gov/photo/2025-00417.jpg",
  "matchConfidence": 0.85,
  "matchBasis": "lastName:exact firstName:exact dob:year",
  "matchDetail": "name agrees; birth year agrees (no full date to compare)",
  "matchState": "year_match", "unverified": false,
  "cases": [
    {
      "caseNumber": "1:25-cr-00417",
      "court": "U.S. District Court, Example District",
      "county": "", "state": "US", "level": "federal",
      "filedDate": "2025-02-11", "status": "open",
      "charges": [
        { "statute": "18 U.S.C. 2113", "description": "BANK ROBBERY",
          "severity": "felony", "disposition": "",
          "dispositionDate": null, "offenseDate": "2025-01-30",
          "plea": "", "counts": 1 }
      ]
    }
  ],
  "sources": [
    { "code": "US-WARRANT", "level": "federal",
      "recordUrl": "https://example-warrants.gov/notice/2025-00417",
      "lastCheckedAt": "2026-08-25T06:15:00+00:00", "live": false }
  ],
  "lifecycle": {
    "firstSeen": "2025-02-12T00:00:00+00:00",
    "lastSeen": "2026-08-25T06:15:00+00:00",
    "presentInLastSweep": true, "droppedOut": false,
    "droppedOutDetected": null, "dropInterpretation": "unknown"
  },
  "liveChecked": false
}
Registry (statewide) — identity-led
{
  "recordType": "criminal_record",
  "externalId": "FL-REGISTRY:offender:774120",
  "name": { "first": "TAYLOR", "middle": "R", "last": "OSBORNE", "suffix": "JR" },
  "aliases": [],
  "dob": {
    "date": "1978-03-09", "birthYear": 1978, "birthMonth": 3,
    "age": null, "ageAsOf": null, "dobPrecision": "full"
  },
  "birthYear": 1978, "dobPrecision": "full",
  "sex": "M", "race": "White", "state": "FL", "county": null,
  "currentStatus": "registered",
  "currentlyIncarcerated": false, "everIncarcerated": true,
  "dateIncarcerated": null, "releaseDate": "2012-05-30",
  "custodyFacility": null,
  "mugshotUrl": "https://example-registry.gov/img/774120.jpg",
  "matchConfidence": 0.95,
  "matchBasis": "lastName:exact firstName:exact dob:match",
  "matchDetail": "name and full date of birth agree",
  "matchState": "dob_match", "unverified": false,
  "cases": [
    {
      "caseNumber": "2009-CF-013322",
      "court": "Circuit Court, Example County",
      "county": "Example", "state": "FL", "level": "state",
      "filedDate": "2009-08-04", "status": "disposed",
      "charges": [
        { "statute": "FL 810.02", "description": "BURGLARY OF A DWELLING",
          "severity": "felony", "disposition": "GUILTY",
          "dispositionDate": "2010-02-17", "offenseDate": "2009-07-19",
          "plea": "GUILTY", "counts": 1 }
      ]
    }
  ],
  "sources": [
    { "code": "FL-REGISTRY", "level": "state",
      "recordUrl": "https://example-registry.gov/offender/774120",
      "lastCheckedAt": "2026-08-22T09:05:00+00:00", "live": false }
  ],
  "lifecycle": {
    "firstSeen": "2012-06-01T00:00:00+00:00",
    "lastSeen": "2026-08-22T09:05:00+00:00",
    "presentInLastSweep": true, "droppedOut": false,
    "droppedOutDetected": null, "dropInterpretation": "unknown"
  },
  "liveChecked": false
}

One person, two jurisdictions — the merged record

The canonical shape, with include: ["sourceData"] sent so omittedFields is []. This is one person merged across two jurisdictions — a statewide DOC entry and a county jail booking — which is why sources[] has two entries (each with its own lastCheckedAt) and everIncarcerated is true while currentlyIncarcerated is false (was in custody, now out, with a releaseDate).

A merged Criminal Record
{
  "recordType": "criminal_record",
  "externalId": "TX-DOC:offender:01288340",
  "name": { "first": "ALEX", "middle": "Q", "last": "HAMILTON", "suffix": "" },
  "aliases": ["HAMILTON, A Q", "AL HAMILTON"],
  "dob": {
    "date": "1989-04-23", "birthYear": 1989, "birthMonth": 4,
    "age": 37, "ageAsOf": "2026-08-20", "dobPrecision": "full"
  },
  "birthYear": 1989, "dobPrecision": "full",
  "sex": "M", "race": "White", "state": "TX", "county": "Dallas",
  "currentStatus": "released",
  "currentlyIncarcerated": false, "everIncarcerated": true,
  "dateIncarcerated": "2023-05-31", "releaseDate": "2024-02-10",
  "custodyFacility": "Dallas County Jail",
  "mugshotUrl": "https://example-roster.gov/mugshots/889201.jpg",
  "matchConfidence": 0.95,
  "matchBasis": "lastName:exact firstName:exact dob:match",
  "matchDetail": "name and full date of birth agree",
  "matchState": "dob_match", "unverified": false,
  "cases": [
    {
      "caseNumber": "CR-2023-004182",
      "court": "Dallas County Criminal District Court No. 3",
      "county": "Dallas", "state": "TX", "level": "county",
      "filedDate": "2023-06-14", "status": "disposed",
      "charges": [
        { "statute": "TPC 30.02", "description": "BURGLARY OF HABITATION",
          "severity": "felony", "disposition": "GUILTY",
          "dispositionDate": "2024-02-09", "offenseDate": "2023-05-30",
          "plea": "GUILTY", "counts": 1 }
      ]
    }
  ],
  "sources": [
    { "code": "TX-DOC", "level": "state",
      "recordUrl": "https://example-doc.gov/inmate/01288340",
      "lastCheckedAt": "2026-08-25T02:00:00+00:00", "live": false },
    { "code": "TX-DALLAS", "level": "county",
      "recordUrl": "https://example-roster.gov/booking/889201",
      "lastCheckedAt": "2026-08-24T05:12:00+00:00", "live": false }
  ],
  "lifecycle": {
    "firstSeen": "2023-06-15T00:00:00+00:00",
    "lastSeen": "2026-08-25T02:00:00+00:00",
    "presentInLastSweep": true, "droppedOut": false,
    "droppedOutDetected": null, "dropInterpretation": "unknown"
  },
  "liveChecked": false,
  "sourceData": {
    "_payload": { "bond": "…", "…": "the raw per-source annex — free-form, per-jurisdiction, present only because include:[\"sourceData\"] was sent" }
  }
}

Read it in order: full DOB (dobPrecision: "full", so dob.date is a real day); matched on surname and first name exactly against a full date (matchState: "dob_match"), scoring 0.95 — surname exact 0.55 + first exact 0.25 + dob_match 0.40, clamped to the 0.99 ceiling; not currently in custody but has been; appears in two jurisdictions; and carries its raw annex because include: ["sourceData"] was sent.

GET /v1/criminal/records/{recordId}

Re-fetch a single record by the source’s own stable identifier — its externalId. This is a point lookup: no name predicate. The same record schema documented above comes back.

The lookup key is externalId, not recordId. recordId is a merge-derived label and is not stored as a lookup key. And an externalId is unique only within its source — store the sources[].code + externalId pair for a durable reference. A 404 means “this identifier is not current” (a booking that dropped from the roster), not “this person has no record”.

Product overview