Sex Offender Search API · v1.0.0

Front-desk visitor screening

A registry check at visitor sign-in: what to send from the ID, the Clear, Review and Match states, the staff screen, repeat visitors and the audit trail.

Livehttps://api.offendersearch.appSandboxhttps://sandbox.offendersearch.app

Front-desk visitor screening: what to send from the ID

This guide is for teams building a sex offender registry check into a front-desk sign-in: a visitor management product, a school platform, or a district’s own app. It covers the request, how to turn the answer into one of three states a desk can act on, what staff should see, and what to keep. The product side of the same workflow is on the visitor screening API and school visitor screening API pages.

Send three fields from the visitor’s ID: firstName, lastName and dob as YYYY-MM-DD. A driver’s licence barcode carries all three. Add your own ids for the visitor (subjectId) and for the school or district (clientId); the sections on repeat visitors and platforms explain why.

POST /v1/search
curl -X POST https://api.offendersearch.app/v1/search \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
        "query": { "firstName": "James", "lastName": "Smith", "dob": "1984-03-09" },
        "subjectId": "visitor-1042",
        "clientId": "district-17/school-3"
      }'

Every field is documented on Search. Omit jurisdictions: the default search covers 58 registries, so a visitor registered in another state is found at the desk as readily as a local one.

Why date of birth matters in visitor screening

A name alone is not an identity. A common name matches many registrants, and none of them can be told apart from the visitor without more information. With the date of birth, every record whose full date of birth disagrees with the ID is left out, and the records that remain say how their date of birth was checked in matchState.

Name only
{ "query": { "firstName": "James", "lastName": "Smith" } }

→ every registrant named James Smith, across the 58 registries
  "counts": { "records": 40, … }        (illustrative)
  each record: "matchState": null        — nothing was checked
Name and date of birth
{ "query": { "firstName": "James", "lastName": "Smith", "dob": "1984-03-09" } }

→ registrants whose date of birth disagrees are left out
  "counts": { "records": 1, … }
  "matchState": "dob_match"              — the date of birth matched

With a date of birth, a common name usually returns zero or one person. A few registries publish only a birth year or an age, or no birth information at all; a record from one of them stays in the answer, labelled year_match, age_match or no_dob_age_year, because it cannot be ruled out. Those are the records your desk reviews. The full vocabulary is on Date of birth.

Reading a visitor screening result: Clear, Review, Match

Map every response to one of three states. Take the strongest state any record reaches: one Match record makes the whole check a Match.

StateRuleWhat it means at the desk
Clearstatus is "complete" and counts.records is 0.No registrant matches the visitor. Continue the sign-in and print the badge.
ReviewAt least one record, and none meets the Match rule. This covers year_match, age_match, no_dob_age_year, a dob_match below 0.90, and every record from a search sent without a dob (matchState: null).A possible match. A staff member compares the photo and details with the ID before the visitor goes further.
MatchAt least one record has matchState: "dob_match" and matchConfidence of 0.90 or higher.A strong match: the visitor’s full date of birth matches a registrant with the same name. Alert an administrator quietly and follow the site’s protocol.

Why the Match rule needs both fields

matchConfidence measures how much identity evidence the record carries: a full date of birth and both names on file score 0.90, and 1.00 when more than one registry holds the person. A dob_match record is never capped, so it scores 0.90 or higher whenever both names are on file. But an exact-name record with a date of birth on file also scores 0.90 when you sent no dob, and nothing about the visitor was checked. Only matchState says the visitor’s date of birth was compared, so the rule reads both.

matchState on the recordmatchConfidenceDesk state
dob_match, first and last name on file0.90 – 1.00Match
dob_match, a name missing from the record0.70 – 0.80Review
year_match (the registry publishes only a birth year)up to 0.85Review
age_match (the registry publishes only an age)up to 0.70Review
no_dob_age_year (no birth information on the record)up to 0.70Review
null — the search was sent without a dobup to 1.00Review
Every state is a statement about the registry search, not about the person. Clear means no registrant matched the details sent. Review means a person could not be ruled out. Match means a registrant’s name and full date of birth agree with the ID. A staff member confirms every Review and Match against the ID and the registry photo before anyone acts on it.

Visitor screening desk UI: what staff see

1. Scan the IDFirst name, last name and date of birth.
2. SearchPOST /v1/search with subjectId and clientId.
3. Map the answerClear, Review or Match. The visitor’s screen shows none of it.
Clear
  1. Print the badge.
  2. Log the searchId, time and state.
Review
  1. Visitor waits; staff see the candidates.
  2. Compare photo, date of birth and details with the ID.
  3. Not this person: record a clearance, print the badge.
  4. Cannot rule it out: escalate to an administrator.
Match
  1. Visitor waits; no badge prints.
  2. Quiet alert to the designated administrator, with the photo.
  3. Administrator confirms against the ID and follows the written protocol.

The visitor sees nothing

Never show a result on a screen the visitor or the line can see. For Clear the kiosk prints a badge; for Review and Match it shows the same neutral message, such as “The front office will finish your sign-in”, and alerts staff on their own screen or device.

What the staff screen shows for each candidate

  • The photo, images[0].url, beside the photo on the ID.
  • Name, dob (or birthYear or age where that is all the registry publishes), sex, height and weight.
  • Where the person is registered, registrationState, and their addresses.
  • How the record matched, in plain words: “Date of birth matches the ID” for dob_match, “Only the birth year could be compared” for year_match, “The registry lists no date of birth” for no_dob_age_year, and so on.
  • A link to the registry’s own page for the record, source.recordUrl.

Handling a Review

  1. Compare the photo and the date of birth (or birth year, or age) with the ID. Check height, weight and other details the registry lists.
  2. If they plainly differ, the staff member records that the candidate is not this visitor. Your app calls POST /v1/clearances with verdict: "not_this_person", the staff member as reviewer and a short note, and the sign-in continues.
  3. If the staff member cannot rule the candidate out, escalate to an administrator, who decides under the site’s written protocol. Do not deny entry on a Review alone.

Handling a Match

No badge prints. The designated administrator gets a quiet alert with the photo and the match details, confirms the person against the ID, and follows the district’s written protocol. State laws differ on when a registrant may be on school grounds, so the response to a confirmed match belongs to district policy and counsel, not to your app.

Repeat visitors: clearances for visitor screening

A parent who visits every week and shares a name with a registrant should be reviewed once, not every week. Send your stable id for the visitor as subjectId on every search. When a reviewer decides a returned person is not that visitor, record it with POST /v1/clearances. From then on, a search with the same subjectId and clientId still runs in full, but the cleared person is removed from records[] and listed in cleared[], and counts.clearedSuppressed says how many were removed. The visitor comes back Clear.

  • Exact person only. A clearance names one personRef, never a name or a date of birth, so a different registrant with the same name still comes through.
  • Changes bring them back. If the cleared person’s name, date of birth, offenses or registration status change, or a later search matches them more strongly, they return to records[] with clearance.stale: true for a new review.
  • clearanceMode is "suppress" by default. Send "annotate" to keep cleared people in records[] with a clearance object, for a staff screen that shows them greyed out, or "ignore" to answer as if no clearance existed.
  • Proof of review. The clearance cites the searchId that returned the person, which must be in your 14-day search history.
  • No extra charge, no expiry until revoked, and DELETE /v1/clearances/{clearanceId} revokes one.

The full flow, every field and the re-surfacing rules are on Clearances.

Visitor screening for platforms serving many schools and districts

A platform that runs sign-in for many schools uses one OffenderSearch account and sends the school or district with each search:

  • One account, one key per environment. Every key on the account can search; create a separate key for each environment or service so any one can be rotated or revoked on its own (see Authentication).
  • clientId per school or district. Send your own id for the school or district, up to 128 printable characters, on every search and every clearance. Clearances are scoped to it: one district’s decisions never change another district’s results, even for the same registrant and the same visitor id. It takes effect alongside a subjectId.
  • subjectId unique across your platform. Use the visitor id your platform already has. Both ids are stored only as per-account hashes, never as the values you sent.
  • Usage per school or district. Usage and the invoice are per account, in the dashboard. To report or bill each district, count the searchIds you stored against each clientId in your audit log (below).

Pre-registered visitors, such as a field-trip chaperone list, can be screened before the day with Batch & CSV, which takes the same subjectId, clientId and clearanceMode per row.

Visitor screening audit trail and data retention

Store one row per check, Clear ones included:

FieldWhy
searchIdIdentifies the search. It is what a clearance and a verification report cite.
TimestampWhen the check ran, from your own clock at sign-in.
Desk stateClear, Review or Match, as your app computed it.
clientId, subjectIdWhich school and which visitor, for reporting and for the next visit.
Review outcomeFor Review and Match: the personRef and recordId of each candidate, who reviewed it, the decision, and the clearance id if one was recorded.

We keep each search and its results for 14 days. In that window it appears in the dashboard’s search history, a clearance can cite it, and POST /v1/report can turn it into a timestamped PDF for your file. After 14 days the stored results are erased, so keep in your own records whatever your retention policy requires.

Visitor screening speed and registry coverage

A visitor is waiting at the counter, so the check has to finish there. A search is designed to be sub-second, and every response carries elapsedMs so you can measure it in your own flow. Each search covers 58 registries: the 50 states, DC and the US territories. The date of birth narrows the answer to the right person, which also keeps the staff screen short.

Visitor screening and the FCRA

A visitor check decides who is escorted today. Results from POST /v1/search are not a consumer report and may not be used for FCRA-covered decisions. When a result will help decide whether to hire or engage someone, such as a substitute teacher, a coach or a contracted caregiver, that check is a consumer report and runs as an FCRA order through FCRA Partner Access. See the FCRA API.

Visitor screening integration code in Node and Python

A complete minimal integration: search at sign-in, map the answer to Clear, Review or Match, and record a reviewer’s decision as a clearance. It points at the sandbox; set OFFENDERSEARCH_BASE and OFFENDERSEARCH_KEY to go live.

// desk-screening.mjs — Node 18+ (fetch is built in)
const BASE = process.env.OFFENDERSEARCH_BASE ?? 'https://sandbox.offendersearch.app';
const KEY = process.env.OFFENDERSEARCH_KEY;

// A Match is a record whose full date of birth matched the visitor's ID and
// whose first and last name are both on file: matchState "dob_match" scores 0.90+.
const MATCH_MIN_CONFIDENCE = 0.90;

async function call(path, body) {
  const res = await fetch(BASE + path, {
    method: 'POST',
    headers: { 'X-API-Key': KEY, 'Content-Type': 'application/json' },
    body: JSON.stringify(body),
  });
  return res.json();
}

/** Clear | Review | Match for one search response. */
export function deskState(result) {
  if (result.status === 'complete' && result.counts.records === 0) return 'clear';
  const strong = result.records.some(
    (r) => r.matchState === 'dob_match' && r.matchConfidence >= MATCH_MIN_CONFIDENCE,
  );
  return strong ? 'match' : 'review';

Test visitor screening in the API sandbox

Run each of these against https://sandbox.offendersearch.app with a test key and check that your app reaches the desk state shown. Nothing on the sandbox is billed. Test keys, the criminal test names and examples are on Sandbox & test keys.

SendWhat comes backDesk state
firstName: Clear · lastName: VisitorClear visitorClear
firstName: Exact · lastName: Match · dob: 1980-01-01Exact match, date of birth verifiedMatch
firstName: Exact · lastName: MatchExact match, no date of birth sentReview
firstName: Exact · lastName: Match · dob: 1990-05-05Exact match name, different date of birthClear
firstName: Name · lastName: OnlyName only — possible matchesReview
firstName: Alias · lastName: MatchMatched through an aliasReview
firstName: Out · lastName: State · state: TXRegistered in another stateReview
firstName: Multi · lastName: StateOne person on two registriesReview
firstName: Homeless · lastName: RegistrantHomeless registrantReview

Download the sandbox Postman collection

Product overview