Sex Offender Search API · v1.0.0

Date-of-birth matching

A date of birth narrows the search and labels every record with how the registry's birth evidence agreed. Here is how to send it and how to read it.

When to use this

Whenever you hold a date of birth — from an ID scan, an application, an admissions record — send it. Registries differ in what they publish: a full date, a birth year only, a published age only, or nothing. The API compares your date against whichever of those the record carries and reports the outcome in matchState, so a reviewer knows exactly how strong the identity evidence is. You will build a search that sends dob (or age when that is all you have) and a reader that turns matchState and dobPrecision into a confidence tier.

Before you start

  • A test key on https://sandbox.offendersearch.app. Sandbox people: Exact Match publishes a full date, 1980-01-01; the three Name Only registrants publish no date, year or age at all.
  • query.dob is an ISO date, YYYY-MM-DD. query.age is an integer. They are alternatives; dob is stronger.
  • Two tuning fields on query: ageTolerance (0–10, default 1) and onAgeMismatch (drop default, or flag).
The one thing people get wrong. Sending a dob does not require the registry to publish one. A record with no birth evidence is still returned on the name, with matchState: "no_dob_age_year" and unverified: true. Dropping those records would hide real registrants; your job is to review them, not to filter them out.

Step by step

  1. Send the date. Search Exact Match with dob 1980-01-01. The record comes back matchState: "dob_match", dobPrecision: "exact".
  2. Send a contradicting date. Search Exact Match with dob 1990-05-05. Zero records: a registry that publishes a full date that disagrees with yours rules the person out, and the record is filtered rather than shown.
  3. Send an age instead. Search Exact Match with age 46. The record comes back matchState: "age_match": the age derived from the published date falls within ageTolerance of yours.
  4. Search people with no birth evidence. Search Name Only with any dob. All three come back no_dob_age_year, unverified: true, dobPrecision: "unknown".

Steps 1–3 — the same person, three ways

# 1. full date — matchState dob_match
curl -s https://sandbox.offendersearch.app/v1/search -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Exact", "lastName": "Match", "dob": "1980-01-01"}}'

# 2. contradicting date — counts.records 0
curl -s https://sandbox.offendersearch.app/v1/search -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Exact", "lastName": "Match", "dob": "1990-05-05"}}'

# 3. age instead of a date — matchState age_match
curl -s https://sandbox.offendersearch.app/v1/search -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Exact", "lastName": "Match", "age": 46}}'
Step 3 response record (trimmed)
{
  "recordId": "rec_28db9639d712c4c61e8e",
  "matchConfidence": 0.9,
  "matchBasis": ["lastName", "firstName", "dob", "lastName:exact", "firstName:exact", "name_match"],
  "matchDetail": { "strategies": { "lastName": "exact", "firstName": "exact" },
                   "fieldsPresent": ["lastName", "firstName", "dob"], "verification": "age_match" },
  "dob": "1980-01-01", "birthYear": 1980, "dobPrecision": "exact", "age": "46",
  "dobVerification": "age_match",
  "matchState": "age_match",
  "unverified": false
}

Note the bare dob token in matchBasis: it says the record has a date of birth, not how it agreed. The agreement is matchState (echoed as matchDetail.verification). dobVerification is the engine's raw token and is kept for compatibility; branch on matchState.

Step 4 — no birth evidence on file

Response record for Name Only with dob 1988-04-12 (trimmed)
{
  "recordId": "rec_ca91307a7124dca36777",
  "matchConfidence": 0.6,
  "matchBasis": ["lastName", "firstName", "unverified_no_dob_or_age", "lastName:exact", "firstName:exact", "name_match"],
  "matchDetail": { "strategies": { "lastName": "exact", "firstName": "exact" }, "fieldsPresent": ["lastName", "firstName"],
                   "signals": ["unverifiable", "matchedLegalName"], "verification": "unverified_no_dob_or_age" },
  "dob": null, "birthYear": null, "dobPrecision": "unknown", "age": "",
  "matchState": "no_dob_age_year",
  "unverified": true
}

Tuning the age comparison

{ "query": { "firstName": "Jordan", "lastName": "Rivera", "dob": "1988-04-12",
             "ageTolerance": 2, "onAgeMismatch": "flag" } }

Eleven registries publish an age and no date. ageTolerance is how many years of slack that comparison allows (default 1, because a published age with an unpublished birthday is consistent with two birth years). onAgeMismatch: "flag" returns a name match whose published age contradicts your date as matchState: "age_mismatch", always unverified: true, instead of dropping it — useful when "we checked and the age disagrees" is a fact you want to show. A contradicting full date is always filtered; the sandbox confirms this: Exact Match with dob 1990-05-05 and onAgeMismatch: "flag" still returns zero records.

Reading the result

`matchState`The registry published…Meaning for this job
dob_matcha full date, equal to yoursStrongest confirmation. Lifts every name-strategy cap on the score.
year_matcha birth year only (some states), equal to your yearGood evidence, not a confirmed date. Raises the score floor to 0.80. Review.
age_matchan age (or a date you compared by age), within ageToleranceConsistent, not confirmed. Review.
no_dob_age_yearnothing to compareName is the only evidence; unverified: true. Review against photo, address, age range.
age_mismatchan age that contradicts your dateOnly with onAgeMismatch: "flag"; always unverified. Usually noise.
null—You sent no dob or age. Not a failed check — nothing was checked.
FieldValuesRead it as
dobPrecisionexact, year, year_month, unknownHow much birth date the source published. Never compare a year record as a full date.
dobYYYY-MM-DD or nullOnly a full date; null for year-only and age-only records.
birthYearinteger or nullPresent whenever a year is known, including year-only records.
agestring, "" when unknownPublished, or derived from an exact date. A string on the record; query.age is an integer.
unverifiedbooleantrue when your identifiers could not be checked against the record.

Edge cases and pitfalls

  • `"dob": "1980"` is a `422` with a list-shaped detail (loc: ["body", "query", "dob"]). Send a full ISO date, or send age when the year is all you know.
  • A `dob_mismatch` record is never shown; it is filtered. If a reviewer insists a person "should have come back", the usual reason is a wrong date on your side — re-run without dob and compare.
  • `matchState: null` is not `no_dob_age_year`. null means you sent nothing to check; no_dob_age_year means you did and the registry had nothing. The sandbox shows both: Exact Match without dob is null at 0.9; Name Only with dob is no_dob_age_year at 0.6.
  • Truncation protects dated records. When a query carrying a dob hits a registry's candidate cap (incompleteReason: "truncated"), records whose birth year matches are examined first; what gets cut is from the unknown-birth-date group.
  • Year-only states. A registrant in a year-only registry can never be dob_match; year_match with dobPrecision: "year" is the best that registry can say. Do not hold it to the full-date tier.
  • Age drift. A published age is anchored to the date the registry page was read, not today; that is what the default ageTolerance of 1 absorbs. Raise it for a high-recall pass and expect more same-name strangers.

Production checklist

  • dob sent whenever you hold one, as YYYY-MM-DD; age only as a fallback.
  • Review tiers keyed on matchState; dob_match is the only tier eligible for an automatic path.
  • no_dob_age_year records are reviewed, not dropped.
  • ageTolerance and onAgeMismatch set deliberately and documented in your own runbook.

Product overview