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. This data 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. 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”.