# Record normalization

> The source's words at the top level; the closed vocabularies in `normalized`. Nothing is overwritten.

- **HTML:** https://offendersearch.app/docs/guides/criminal/normalization
- **Base URL (live):** https://api.offendersearch.app — live keys (`os_live_…`)
- **Base URL (sandbox):** https://sandbox.offendersearch.app — test keys (`os_test_…`), never billed
- **Authentication:** `X-API-Key` request header
- **OpenAPI:** https://offendersearch.app/openapi.json · https://offendersearch.app/openapi.yaml
- **Docs index for agents:** https://offendersearch.app/llms.txt

## The rule

Every source word is kept in its own field — `sex`, `race`, `currentStatus`, `physical.hairColor`, `charges[].disposition`, `recordType`. The `normalized` object holds the closed-vocabulary classification computed from per-source tables, profile rules and global rules, falling back to `unknown`. `recordCategory` is the normalized twin of `recordType`; `normalized.custodyStatus` of `currentStatus`; `normalized…outcome` of `disposition`. `sources[].code` is always the public jurisdiction code.

## Field by field

| Source words (top level) | Classification (`normalized`) | Vocabulary |
| --- | --- | --- |
| `recordType` | `recordCategory` (also top level) | `court`, `jail`, `prison`, `warrant`, `registry`, `exclusion`, `other` |
| `currentStatus` | `custodyStatus` | twelve values incl. `unknown` |
| `charges[].disposition` | `cases[].charges[].outcome`, `convicted`; `outcomes[]`, `hasConviction`, `convictionCount` | twelve outcomes |
| `charges[].severity`, `description`, `statute` | `cases[].charges[].severity`, `severityBasis`, `category`; `topSeverity`, `chargeCategories[]` | severities, bases, sixteen categories |
| `sex`, `race`, `physical.hairColor`, `physical.eyeColor` | `sex`, `race`, `hair`, `eyes` | NCIC codes (`M F X U`; `W B I A H U`; hair and eye codes) |
| `address`, `state`, `county` | `residence`, `jurisdiction` (NormalizedPlace with `geoPrecision`: `rooftop`, `street`, `zip`, `none`) | — |
| `dob.*` | `dob.dobPrecision` | `full`, `month_year`, `year`, `age`, `none` |
| dates in `cases[]`, `booking`, `warrant`, … | ISO where they parse; otherwise the source text | — |

`normalized.quality` is `ok` or `quarantined`; `normalized.rulesVersion` names the rule set. `include: ["normalizedDetail"]` adds the `rule` that produced each charge's classification; `include: ["sourceData"]` is reserved for the per-source annex and today only clears `omittedFields`.

## Related

- [Normalized fields reference](https://offendersearch.app/docs/criminal/normalized-fields)
- [Data model rules](https://offendersearch.app/docs/platform/data-model)
