Matching and confidence
The API tells you how each name matched and how the birth date agreed. Build the review queue on those two facts, not on the score alone.
What you will build
A triage function that sorts every returned record into accept (identity confirmed well enough to act on automatically), review (a real candidate a person must look at) or noise (a widened match you choose to drop), using matchDetail.strategies, matchedName.type and matchState. Along the way you pick a match mode for your use case and learn when to widen a search with prefixMatch or nameMatch.
Who does this: anyone screening people by name — admissions, volunteer coordinators, visitor desks, platforms serving them. The result is a queue in which the strong hits are separated from the common-name noise before a reviewer sees them.
Before you start
- A test key on
https://sandbox.offendersearch.app. Sandbox people used here: Exact Match (dob1980-01-01, one legal-name hit), Alias Match (a registrant whose legal name is different; the hit is through an alias) and Name Only (three same-name candidates, no birth dates). - Three match modes on the request:
strict,balanced(default),broad. Two finer controls onquery:prefixMatchandnameMatch. The full vocabulary is on Matching & confidence; this guide uses it rather than restating it. - Two response fields carry the answer:
matchDetail.strategies(one key per name field,exact,nickname,initial,prefix,fuzzy,middleorabsent, plusaliaswhen an alias produced the hit) andmatchState(how the birth date agreed).
matchConfidence measures how complete the record is, not whether the person at your desk is the registrant. Exact Match scores 0.9 with a dob sent (matchState: "dob_match") and 0.9 with no dob at all (matchState: null). Gate on strategies and matchState; use the score to order the queue.Step by step
- Run the default mode.
balancedis the default: exact surname, and nicknames, initials, 3-character prefixes and one-edit typos on the given name, with aliases searched. It is the right mode for a national search; measured against a large competitor corpus it matched 98.28% andbroadrecovered only 0.11% more. - See an alias hit, then switch to strict. Search Alias Match in
balancedand again with"match": "strict". The alias hit disappears instrict, which searches the registered legal name only. - Widen deliberately. Use
prefixMatchfor a partial name (minimum 3 characters; it forcesbroad) ornameMatchto switch individual strategies on per field. Widening is a choice you make per request, never a default. - Triage. Implement the function below and feed it
records[]. Sendreviewitems to a person with the photo, age, address andsources[].recordUrl.
Step 2 — the alias hit in balanced, gone in strict
{ "query": { "firstName": "Alias", "lastName": "Match" } }{
"recordId": "rec_ca28e3ec410c9ee37d7d",
"personRef": "prs_ca28e3ec410c9ee37d7d",
"matchConfidence": 0.55,
"matchBasis": ["lastName", "firstName", "dob", "lastName:exact", "firstName:exact", "alias:exact", "alias_match"],
"matchDetail": {
"strategies": { "lastName": "exact", "firstName": "exact", "alias": "exact" },
"fieldsPresent": ["lastName", "firstName", "dob"],
"signals": ["matchedAlias"],
"matchedNameType": "alias",
"verification": null
},
"matchedName": { "value": "Alias Match", "type": "alias" },
"name": { "first": "Jordan", "middle": "Sample", "last": "Placeholder", "suffix": "", "full": "Jordan Sample Placeholder" },
"aliases": ["Alias Match", "J. Placeholder"],
"dob": "1977-07-07", "dobPrecision": "exact",
"matchState": null, "unverified": false
}The registered name is Jordan Sample Placeholder; the name you sent is one of the aliases. matchedName.type is alias, strategies.alias is exact, and the score is capped at 0.55 — the lowest the engine produces — because an alias-only hit is never presented as certainty. The same request with "match": "strict" returns counts.records: 0.
Step 3 — widening on purpose
{ "query": { "firstName": "Jordan", "lastName": "Riv", "prefixMatch": "lastName" } }
// 3+ characters required (a shorter prefix is a 422); forces match=broad;
// an exact hit always ranks above a prefix hit; prefix hits cap at 0.60.Step 4 — the triage function
STRONG_DOB = {"dob_match"}
WEAK_DOB = {"year_match", "age_match", "no_dob_age_year", None}
def triage(record, queried=("lastName", "firstName")):
"""accept | review | noise for one record of POST /v1/search."""
strategies = record["matchDetail"]["strategies"]
legal = record["matchDetail"]["matchedNameType"] == "legal"
all_exact = all(strategies.get(f) == "exact" for f in queried)
if all_exact and legal and record["matchState"] in STRONG_DOB:
return "accept" # exact legal name + confirmed full date of birth
if record["matchState"] == "age_mismatch":
return "noise" # only present when you sent onAgeMismatch: "flag"
if strategies.get("lastName") == "fuzzy" and record["matchState"] in WEAK_DOB:
return "noise" # broad-mode surname typo with nothing confirming it
return "review" # nickname / initial / prefix / alias / middle, or a weak date
def queue(result):
rows = sorted(result["records"], key=lambda r: -(r["matchConfidence"] or 0))
return {"accept": [r for r in rows if triage(r) == "accept"],
"review": [r for r in rows if triage(r) == "review"],
"incomplete": result["status"] != "complete"}Run it on the three sandbox people: Exact Match with dob lands in accept; Alias Match lands in review (alias, no date sent); each Name Only record lands in review (no_dob_age_year). "Accept" here means the identity evidence is strong enough for your automatic path — it is still a registry match to be confirmed against the source page, not a verdict about the person.
Reading the result
| Field | Values | What it means for the queue |
|---|---|---|
matchDetail.strategies.lastName | exact, prefix, fuzzy | exact is the only surname strategy in strict and balanced; prefix/fuzzy appear only in broad and cap the score at 0.60. |
matchDetail.strategies.firstName | exact, nickname, initial, prefix, fuzzy, middle, absent | exact has no cap; nickname caps at 0.75; the rest at 0.60. absent means the registry publishes no given name — kept, never a confirmation. |
matchDetail.strategies.alias | exact, prefix, fuzzy (present only on an alias hit) | An alias produced the hit; matchedName.type is alias; cap 0.55. Always review. |
matchDetail.matchedNameType | legal, alias | Which name matched. Auto-accept only legal. |
matchState | dob_match, year_match, age_match, no_dob_age_year, age_mismatch, null | A confirmed full date lifts every cap; a confirmed year raises the floor to 0.80. null means you sent no dob/age. |
matchConfidence | 0–1, or null on a nameless search | Order the queue with it. Several strategies share a ceiling, so it cannot tell them apart. |
matchBasis[] | mixed tokens, e.g. lastName:exact, alias:prefix, a bare dob | A flat explanation for a reviewer. Prefer matchDetail in code. |
Strategy ranks, ceilings and the exact rules behind each mode are in Matching & confidence. The birth-date side of the decision has its own guide: Date-of-birth matching.
Edge cases and pitfalls
- Typos in `match` are refused, not widened:
"match": "strick"answers422withUnknown match 'strick'. Allowed: balanced, broad, strict. - `broad` fuzzes the surname and returns roughly 13.5% surname false positives by design, every one labelled
lastName:fuzzyinmatchBasis. It also reads far more candidate rows and can come backpartialat national scope; scope it withjurisdictionsorlocationScoped. - Prefix hits need 3 characters.
{"lastName": "Ma", "prefixMatch": "lastName"}is a422:prefixMatch prefix matching on lastName requires at least 3 characters. - A common surname with no given name or date can hit the per-registry candidate cap and come back
partialwithincompleteReason: "truncated". Add afirstName,doboragerather than retrying. - Compound surnames match on one token: a
Smithquery matchesHamilton-Smithasexact, neverGoldsmith. - No phonetic matching.
Smyth→Smithworks because it is one edit;Schmidt→Smithdoes not. Send a second query for a variant you need. - A missing `strategies` key for a field you queried means that field did not drive the match; treat it as
review, as the triage function does.
Production checklist
- Mode chosen per use case and sent explicitly (
balancedfor screening;strictwhere you hold the exact legal name;broadonly scoped). - Auto-accept requires
strategiesallexact,matchedNameType: "legal"andmatchState: "dob_match"; everything else is reviewed by a person. - Reviewers see
matchedName,aliases[],images[],age,addresses[]andsources[].recordUrl, and record verdicts as clearances. - The queue is ordered by
matchConfidencebut never gated by it alone.
Related products
Product overview
- Sex offender search API
- Sex offender registry API
- Offender search API
- Background check API
- Bulk sex offender search
- Background check API pricing
- Background check API integration
- Integrations
- Free background check API
- Pay-as-you-go background check API
- Background check API in Python
- Background check API in Node.js
- NSOPW API