Front-desk visitor screening integration
From the ID scan to the staff screen, the reviewer's verdict, and the roster you keep watching. A complete walkthrough against the sandbox.
What you will build
A visitor arrives at a school, a care facility or an office; a kiosk scans the ID; before the badge prints, the desk knows whether the name and date of birth match a registrant. You will build the four moving parts: the search at sign-in (keyed to your visitor and site ids), the desk state it maps to (Clear, Review, Match), the reviewer's verdict recorded as a clearance so a false positive is quiet next time, and a monitor on the people who visit regularly. The reference tables for this recipe — what to send from the ID, the staff screen, retention — live on Front-desk visitor screening; this guide is the build order.
Before you start
- A test key. Sandbox visitors: Clear Visitor (Clear), Exact Match with
dob1980-01-01(Match), Name Only (Review: three same-name candidates without birth dates). - Two ids from your own system on every search:
subjectId(your stable id for the visitor — a badge number, a CRM id) andclientId(the site, school or district when one key serves several). They are stored only as per-account hashes and they are what clearances key on. - The desk-state rule: Match = a record with
matchState: "dob_match"andmatchConfidence ≥ 0.90(an exact legal name with a confirmed full date scores 0.90 or more); Clear =status: "complete"andcounts.records: 0; everything else is Review. The thresholds come from the scorer's own arithmetic, not from tuning.
sources[].recordUrl. Nothing in this flow auto-denies.Step by step
- Search at sign-in. Send
firstName,lastNameanddobfrom the ID withsubjectIdandclientId. OnePOST /v1/search, typically well under a second. - Map to a desk state. Compute Clear / Review / Match from
status,counts.recordsand each record'smatchState+matchConfidence. StoresearchId, the state and the time. - Record the reviewer's verdict. When a reviewer decides a candidate is not the visitor,
POST /v1/clearanceswith the record'spersonRef, thesearchIdthat showed it, andverdict: "not_this_person". The next search for thatsubjectIdsuppresses the person and lists them incleared[]. - Watch the regulars. For visitors on a standing roster (contractors, volunteers, family members), create a person monitor so a new registration reaches you between visits.
Steps 1–3 — the desk module
"""desk.py — kiosk sign-in → Clear | Review | Match, and the reviewer's clearance."""
import os
from datetime import datetime, timezone
import requests
BASE = os.environ.get("OFFENDERSEARCH_BASE", "https://sandbox.offendersearch.app")
H = {"X-API-Key": os.environ["OFFENDERSEARCH_KEY"]}
MATCH_MIN_CONFIDENCE = 0.90
def _post(path, body):
r = requests.post(BASE + path, headers=H, json=body, timeout=60)
r.raise_for_status()
return r.json()
def desk_state(result):
if result["status"] != "complete":
return "review" # incomplete: a person must see it; never Clear
if result["counts"]["records"] == 0:
return "clear"
strong = any(r["matchState"] == "dob_match" and (r["matchConfidence"] or 0) >= MATCH_MIN_CONFIDENCE
for r in result["records"])
return "match" if strong else "review"
def screen_visitor(visitor_id, site_id, first_name, last_name, dob):{
"id": "clr_9c54032c347c4f1e",
"product": "sex_offender",
"verdict": "not_this_person",
"personRef": "prs_fec5ef71d9deef5bc8c4",
"recordId": "rec_fec5ef71d9deef5bc8c4",
"searchId": "srch_e891a43dfb5b8648131fde66",
"subjectId": "visitor-1044",
"clientId": "facility-07",
"reviewer": "front-desk",
"note": "Photo and age do not match the visitor.",
"createdAt": "2026-10-25T14:18:55.563989+00:00",
"revokedAt": null,
"supersedes": null,
"sandbox": true
}{
"status": "complete",
"counts": { "records": 2, "recordsReturned": 3, "sourcesQueried": 58, "sourcesComplete": 58, "sourcesIncomplete": 0, "sourcesSkippedByScope": 0, "clearedSuppressed": 1 },
"records": [
{ "recordId": "rec_ca91307a7124dca36777", "personRef": "prs_ca91307a7124dca36777", "registrationState": "GA", "matchState": "no_dob_age_year" },
{ "recordId": "rec_84881c51922112e393d2", "personRef": "prs_84881c51922112e393d2", "registrationState": "MI", "matchState": "no_dob_age_year" }
],
"cleared": [
{ "personRef": "prs_fec5ef71d9deef5bc8c4", "clearanceId": "clr_9c54032c347c4f1e", "verdict": "not_this_person",
"clearedAt": "2026-10-25T14:18:55.563989+00:00", "recordId": "rec_fec5ef71d9deef5bc8c4" }
]
}The cleared person has left records[], counts.records is 2, and cleared[] lets the staff screen say "1 previously cleared person hidden". Send clearanceMode: "annotate" instead to keep the record with a clearance object for a greyed-out row. A clearance matches by personRef only — never by name — because the person being cleared shares the visitor's name. If that registrant's record changes materially, the clearance goes stale and the person comes back for review.
Step 4 — monitor the roster
{
"product": "sex-offender",
"type": "person",
"label": "facility-07 · volunteer v-2201",
"person": { "firstName": "Jordan", "lastName": "Rivera", "dob": "1988-04-12" },
"channels": { "webhookUrl": "https://example.com/hooks/monitors?token=YOUR_SECRET" }
}One monitor per person per dataset (product is sex-offender with a hyphen here; a clearance's product is sex_offender with an underscore). Monitors are evaluated daily, alerts arrive by email or webhook, and a cancelled monitor stops at once. Billing is flat per active monitor (Monitoring billing). The build details are on Person monitoring.
Reading the result
| Desk state | Rule | What staff see | What is stored |
|---|---|---|---|
| Clear | status: "complete" and counts.records: 0 | Nothing — the badge prints. | searchId, state, checkedAt. |
| Review | Any records, none dob_match at ≥ 0.90; or status: "partial" | Candidate cards: name, aliases, photo, age, registration state, matchState, a link to the registry page. "Not this person" and "This is the person" buttons. | searchId, each candidate's personRef and recordId, the verdict, the reviewer. |
| Match | A record with matchState: "dob_match" and matchConfidence ≥ 0.90 | The same card, flagged, with your escalation procedure. | The above, plus a verification report requested within 7 days. |
cleared[] | Present when subjectId was sent | "N previously cleared people hidden", each linking to its clearance. | clearanceIds for the audit. |
Edge cases and pitfalls
- No `dob` from the ID means no Match state is reachable. A record found on name alone has
matchState: nulleven at 0.90. Treat name-only sign-ins as Review and ask for the date of birth. - `partial` is Review, not Clear. The sandbox never produces it; test the branch with a fixture. See Working with incomplete results.
- Multi-site platforms must send `clientId`. Without it, one site's clearances apply at every site the key serves. A verdict recorded with
clientIdis listed only with the sameclientId. - A clearance must cite the search that showed the person within the 14-day window. Citing another search answers
409 person_not_in_search; citing an erased one answers404 search_not_found. Clear from the result on screen, at the time. - `this_person` is also a verdict. Recording it annotates the record on later searches rather than hiding it, so the next desk sees that the match was confirmed before.
- Keep the kiosk honest about time.
checkedAtis yours;sources[].lastCheckedAtis when the registry was last read. Both belong in the log. - This is not a consumer report. Visitor screening is not an employment, housing, credit or insurance decision, and the API must not be used as the sole basis for one; that path is the FCRA Partner API.
Production checklist
- Staff-only display; the visitor never sees a result.
subjectIdandclientIdon every search; the same ids used for clearances and monitors.- Review and Match screens link
sources[].recordUrland record the reviewer's verdict as a clearance. - Match triggers a verification report within 7 days;
searchId,recordId,personRef,clearanceIdandX-Report-Idare stored together. - Standing visitors monitored; alerts routed to the same review queue.
- The remaining checks in the Go-live checklist.
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