# Matching, confidence & partial names

> A required surname anchor, prefix and exact name strategies, a conservative nickname table, no fuzzy surname matching, and the exact matchConfidence arithmetic.

- **HTML:** https://offendersearch.app/docs/criminal/matching
- **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

## The surname is the anchor

These rules hold in every mode — on the cache and on a live source — because matching is applied on top of every source's output.

| Rule | Detail |
| --- | --- |
| `lastName` is required | A query with no surname is refused with `422 guard_unbounded_query`. Both `lastName` and the shorthand `last` are accepted. |
| Case-insensitive | `smith`, `Smith`, `SMITH` are identical. |
| The surname must corroborate | A record whose surname neither equals nor prefix-matches your `lastName` is **dropped**. This is the only hard name filter. |
| A missing query field does not filter | Omit `firstName` and the first name is not considered at all (`firstName:notQueried`). |
| A record missing a first name is KEPT | We cannot disprove your `firstName`, so the record is kept and flagged `firstName:absent`, with a small confidence penalty. |

## The two surname strategies — nameStrategy

| `nameStrategy` | Surname matches | Default? |
| --- | --- | --- |
| `prefix` | the record's surname **equals** your value, **or starts with** it | **Yes** |
| `exact` | the record's surname **equals** your value | no |

> **There is no fuzzy (typo) surname matching in this API.** `Smyth` does not match `Smith`; `Ross` does not match `Rose`. In this domain a false positive is the expensive error — telling a customer that an innocent person has a criminal record is materially worse than a miss — so the matcher never introduces a surname it was not given.

## First-name matching and nicknames

When you supply `firstName`, it is scored **after** the surname corroborates. Both sides are folded through a small, conservative **nickname table** first — `Bob`/`Bobby`/`Rob` → `Robert`, `Bill`/`Will` → `William`, `Jim` → `James` — in both directions. A nickname hit reads as `firstName:exact` (the two folded names became equal). A first-name **mismatch does not drop the record** — it only lowers its confidence, because the surname is the only hard filter.

## Prefix (partial-name) matching — prefixMatch

`prefixMatch` requests prefix matching on a named field explicitly, and is where the **3-character floor** lives. It accepts `"firstName"`, `"lastName"`, `"both"`, or a list.

```json
{ "query": { "lastName": "hamil", "firstName": "al", "prefixMatch": "lastName" } }
```

| You sent | 2-char surname behaviour |
| --- | --- |
| nothing (default) | falls back to **exact** — no error (so `Li`, `Ng`, `Ho`, `Vo` work) |
| `nameStrategy: "prefix"` | **422 `prefix_too_short`** — you asked for prefix |
| `prefixMatch: "lastName"` | **422 `prefix_too_short`** — you asked for prefix |
| `nameStrategy: "exact"` | exact — no error, no floor |

## The match vocabulary on the wire

Every record tells you **how** it matched, in `matchBasis` (space-separated `field:how` tokens) and `matchDetail` (one plain sentence). The surname/given tokens are `exact`, `prefix`, `absent`, `mismatch`, `notQueried`; the dob tokens are `match`, `year`, `monthYear`, `age`, `ageFromDob`, `absent`.

```json
{
  "name": { "first": "ROBERT", "middle": "E", "last": "SMITH", "suffix": "" },
  "matchConfidence": 0.95,
  "matchBasis": "lastName:exact firstName:exact dob:match",
  "matchDetail": "name and full date of birth agree",
  "matchState": "dob_match",
  "unverified": false
}
```

## How matchConfidence is built

`matchConfidence` is not a probability — it is a **sum of fixed evidence contributions, clamped to `[0.0, 0.99]`**, so a name-only match can never present as certainty.

| Contribution | Adds |
| --- | --- |
| Surname **exact** (base) | `+0.55` |
| Surname **prefix** | `+0.42` |
| First name **exact** (after nickname folding) | `+0.25` |
| First name **prefix** (either direction) | `+0.12` |
| First name **absent** | `−0.05` |
| First name **mismatch** | `−0.20` |
| DOB `dob_match` | `+0.40` |
| DOB `year_match` | `+0.30` |
| DOB `age_match` | `+0.18` |

> **A high `matchConfidence` on a name-only match is still a candidate, not a confirmation.** For anything consequential, gate on `matchState: "dob_match"` with `lastName:exact` and `firstName:exact` — and honour the FCRA restriction: this data may not be used for FCRA-covered decisions at all.

---

## Related

- Previous: [Search - POST /v1/criminal/search](https://offendersearch.app/docs/criminal/search.md)
- Next: [Searching by date of birth](https://offendersearch.app/docs/criminal/date-of-birth.md)
- Index: [Criminal Search API reference](https://offendersearch.app/docs/criminal.md)
