# The Criminal Record object

> One normalized criminal record across every jurisdiction: identity, the birth-evidence object, cases, custody, lifecycle, provenance, and match metadata.

- **HTML:** https://offendersearch.app/docs/criminal/record-object
- **Base URL:** https://api.offendersearch.app
- **Authentication:** `X-API-Key` request header
- **OpenAPI:** https://offendersearch.app/openapi.json · https://offendersearch.app/openapi.yaml
- **Criminal reference as markdown:** https://offendersearch.app/docs/criminal.md

## One normalized schema

Every match in a search response is a normalized **Criminal Record** — identity, the birth-evidence `dob` object, `cases[]`, custody fields, a `lifecycle` block, per-source provenance in `sources[]`, and match metadata. The same schema is returned by `/v1/criminal/search` and by `GET /v1/criminal/records/{recordId}`.

## Example record

```json
{
  "recordType": "criminal_record",
  "externalId": "TX-DALLAS:booking:889201",
  "name": { "first": "ALEX", "middle": "Q", "last": "HAMILTON", "suffix": "" },
  "aliases": ["HAMILTON, A Q"],
  "dob": { "date": "1989-04-23", "birthYear": 1989, "birthMonth": 4,
           "age": 36, "ageAsOf": "2026-03-01", "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": null,
  "matchConfidence": 0.8, "matchBasis": "lastName:exact firstName:exact dob:absent",
  "matchState": "no_dob_age_year", "unverified": true,
  "cases": [],
  "sources": [ { "code": "TX-DALLAS", "level": "county",
                 "recordUrl": "https://…/booking/889201",
                 "lastCheckedAt": "2026-08-24T05:12:00+00:00", "live": false } ],
  "lifecycle": { "firstSeen": "2023-06-15T00:00:00+00:00",
                 "lastSeen": "2026-08-24T05:12:00+00:00", "presentInLastSweep": true,
                 "droppedOut": false },
  "liveChecked": false
}
```

## 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 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, a source that re-issued its ids), not "this person has no record". Re-search by name and state, then store the new `externalId`.

## Behaviours to code around

- **`sex` and `race` are free text, not normalized** — `"Male"`, `"M"`, `"F"` co-occur. Normalize client-side; `null` means "not published".
- **A `firstName:mismatch` record is still returned** (lower confidence), because the surname is the only hard name filter. Threshold on `matchConfidence`/`matchState`.
- **A `droppedOut: true` record is still returned**, because disappearance is data. Read `lifecycle` before treating a record as "current".

---

## Related

- Previous: [Batch & CSV search](https://offendersearch.app/docs/criminal/batch.md)
- Next: [Verification reports](https://offendersearch.app/docs/criminal/verification-reports.md)
- Index: [Criminal Search API reference](https://offendersearch.app/docs/criminal.md)
