Visitor management systems
An ID is scanned, a badge prints or a staff member is quietly notified. The check has to fit inside that second, never accuse a parent on a name coincidence, and leave a record a security director can stand behind.
How screening works in visitor management today
A visitor management system replaces the paper log with a kiosk or a front-desk screen. The visitor presents a government ID; the system reads the barcode to extract name, date of birth and photo; it checks the person against a sex offender registry and the organisation's own watch and banned lists; a cleared visitor gets a printed badge with photo, destination and time, and the host is notified; a flagged visitor is held at the desk while a designated staff member — the front-office lead, a school resource officer, hospital security — reviews the alert out of the visitor's sight. The same product runs in K-12 schools, hospitals and clinics, corporate lobbies, data centres and manufacturing sites; schools add custodial-order and volunteer-approval lists, hospitals add patient-requested restrictions, corporate sites add contractor credential expiry. The check runs on every sign-in, which means the integration is judged on two numbers: how quickly it answers and how rarely it flags the wrong person.
The rules are mostly state and institutional. Texas Education Code § 38.022 lets a district verify whether a visitor is a registered sex offender against the state database or another database it can access, requires its board to adopt a policy for a visitor so identified, and limits any electronic visitor log to security use. Louisiana R.S. 14:91.1 requires a registrant on school property to notify the principal's office on arrival and departure; many states bar registrants from school grounds without permission. Florida's Jessica Lunsford Act (§ 1012.465) requires Level 2 screening of contractors with access to students, which is why contractor credentialing often rides on the same kiosk. Districts that have deployed registry-checking kiosks have also seen the failure mode: at least one Ohio district dropped a program after it misidentified visitors, and vendor guidance now stresses minimal data retention — the name, date of birth and last four digits of the ID, not the ID image. For corporate and healthcare sites the duty is the organisation's own: a reasonable visitor-safety policy, applied consistently, with a review step before anyone is turned away.
Visitor screening is not an employment, housing, credit or insurance decision, so the FCRA does not govern it. State registry-use statutes still do: California Penal Code § 290.46(j), Nevada NRS 179B.270 and New Jersey N.J.S.A. 2C:7-16(c) prohibit using information from the state registry website for listed purposes, and California and Nevada include benefits, privileges or services provided by a business establishment; whether a facility-access policy falls inside those lists is for the organisation's counsel, and the state registry pages publish their own conditions of use.
Where the API fits
| Desk step | Endpoint | Fields that matter |
|---|---|---|
| ID scanned → one check | POST /v1/search | query.firstName, query.lastName, query.dob (YYYY-MM-DD) from the barcode; subjectId = a hash of issuer + ID number; clientId = the campus or building; deadlineMs for a bounded wait; read status, counts.sourcesIncomplete, records[].matchState, records[].matchDetail |
| Same visitor, same day | your cache keyed by subjectId | One search per visitor per day is enough; the snapshot is continuously updated and a monitor covers longer gaps |
| Staff review of a hit | GET /v1/records/{recordId}, images[], source.recordUrl | Photo, offenses[], registrationState, addressStates, matchDetail.strategies |
| A regular visitor who keeps matching someone else | POST /v1/clearances | product: "sex_offender", subjectId, clientId, personRef, recordId, searchId (within 14 days), verdict: "not_this_person", reviewer, note |
| Badge holders, contractors, frequent visitors | POST /v1/monitors (type: "person") | product: "sex-offender", person.firstName/lastName/dob, label with the site and badge id, channels.webhookUrl with a token |
| Awareness around the site | POST /v1/monitors (type: "location") | location.lat/lng, radiusMiles; alerts with matchLabel: "in_radius" |
| Incident review | GET /v1/searches/{searchId}/results, POST /v1/report | Stored results for 14 days; a PDF of what the desk saw for 7 days |
The Sex Offender Search API is the product for the desk. Criminal records are rarely part of a visitor policy and introduce review work the sign-in line cannot absorb; where a site screens contractors for work eligibility, that is an employment decision and belongs on the FCRA Partner API, not the kiosk.
Reference integration
The module is the whole desk: a stable subjectId derived from the ID without storing its number, a same-day cache, a bounded search, the four-state rule, and the clearance a reviewer records for a regular visitor who keeps matching someone else. Match requires matchState: "dob_match" and an exact legal-name match on both fields in matchDetail.strategies; a weaker name strategy (nickname, prefix, fuzzy), an alias hit or a missing date of birth is Review. Run it against the sandbox with an os_test_ key; the test names produce each state.
"""Kiosk visitor screening — OffenderSearch Sex Offender Search API, Python 3.11+, requests.
The ID scan gives you a name and a date of birth; the search answers in well under a second from the
current snapshot. The desk sees one of four states; the visitor sees nothing. Cache by subjectId so a
person who signs in twice today is searched once.
OFFENDERSEARCH_API_KEY=os_test_... OFFENDERSEARCH_BASE=https://sandbox.offendersearch.app python kiosk.py
"""
from __future__ import annotations
import hashlib
import os
import time
from dataclasses import dataclass, field
import requests
API_KEY = os.environ["OFFENDERSEARCH_API_KEY"]
BASE = os.environ.get("OFFENDERSEARCH_BASE", "https://sandbox.offendersearch.app")
HEADERS = {"X-API-Key": API_KEY, "Content-Type": "application/json"}
SITE = "site-lincoln-elementary" # your clientId: the campus, building or facility
CACHE_TTL_S = 12 * 3600 # one working day; a Monitoring watch covers anything longer
_cache: dict[str, tuple[float, "DeskResult"]] = {}
{
"searchId": "srch_9f2c1a7b",
"status": "complete",
"elapsedMs": 412,
"counts": { "records": 1, "recordsReturned": 1, "sourcesQueried": 58, "sourcesComplete": 58,
"sourcesIncomplete": 0, "sourcesSkippedByScope": 0, "clearedSuppressed": 0 },
"warnings": [],
"cleared": [],
"records": [
{ "recordId": "rec_6d066763131ebe8c77ea", "personRef": "prs_3c9e1f0a7b2d4e6f8a1b",
"name": { "first": "Exact", "middle": "Sample", "last": "Match", "full": "Exact Sample Match" },
"dob": "1980-01-01", "registrationState": "OH", "addressStates": ["OH"],
"matchConfidence": 1.0, "matchState": "dob_match",
"matchBasis": ["lastName:exact", "firstName:exact", "dob"],
"matchDetail": { "strategies": { "lastName": "exact", "firstName": "exact" },
"fieldsPresent": ["lastName", "firstName", "dob"], "matchedNameType": "legal",
"signals": ["matchedLegalName"], "verification": "dob_match" },
"offenses": [ { "crime": "Sexual battery", "tier": "Tier III", "convictionDate": "2004-05-12" } ],
"images": [ { "url": "https://sandbox.offendersearch.app/v1/sandbox/photos/1.svg" } ] }
]
}What staff see for Review or Match: the registry photo beside the ID photo, the record's name and aliases, dob or age, registrationState and addressStates, offenses[], and how the name matched. A Review that a reviewer resolves as the wrong person becomes a clearance; the next sign-in by the same ID lists that registrant in cleared[] and the desk state recomputes without them. A cleared person who re-surfaces more strongly comes back with clearance.stale: true for a fresh look. elapsedMs on every response is the server time to watch in your kiosk metrics; healthy searches complete in well under a second.
onDeadline: "partial" a search that cannot finish inside the kiosk's bound returns what completed and says so in counts.sourcesIncomplete and warnings[]. An empty records[] on such a response is a lower bound, not a clearance. Show "could not complete" and follow that procedure; the default behaviour keeps working past the deadline target precisely so that this is rare.Ongoing monitoring
People who hold a standing badge — contractors, vendors, volunteers, regular family visitors on a school's approved list — are checked once at credentialing and then walk in on the badge. A person monitor keeps that credential honest: product: "sex-offender", type: "person", the name and date of birth from the credentialing scan, a label the security office can route on, and a webhookUrl carrying a secret token (monitoring deliveries are not signed). An alert suspends the badge for review; it is not itself a denial. Cancel the monitor when the credential expires.
curl -X POST https://sandbox.offendersearch.app/v1/monitors \
-H "X-API-Key: $OFFENDERSEARCH_API_KEY" -H "Content-Type: application/json" \
-d '{
"product": "sex-offender",
"type": "person",
"person": { "firstName": "Jordan", "lastName": "Rivera", "dob": "1988-04-12" },
"minConfidence": "year_match",
"label": "site-lincoln-elementary / badge-0417 (HVAC contractor)",
"channels": { "webhookUrl": "https://your-vms.example/hooks/offendersearch?token=3c9e1f0a" }
}'A location monitor around the site (type: "location", the campus coordinates, a radius) gives the office the awareness many district policies call for, and the Offender Map API can render it. One dataset per monitor; alerts and history at GET /v1/monitors/{monitorId}/alerts. Person monitoring, Delivery.
When this is FCRA
Deciding whether a visitor may enter a building is not an employment, housing, credit or insurance decision, so visitor screening does not run on the FCRA Partner API and the standard API is the right product. Two adjacent jobs are different. If the same system decides whether a contractor or vendor employee is eligible to work on site — a credentialing decision an employer or the contractor's employer will act on — that is an employment purpose and belongs in an FCRA order placed by an approved partner, with the disclosure, authorization and adverse-action steps that go with it. And if a school uses the kiosk to screen volunteers for placement with students, the youth-organisations article in this section covers that flow and the counsel question around volunteers. In every case the standard API is not a consumer report and must not be the sole basis for such a decision.
Evidence and audit
- Per sign-in: the
searchId, the desk state,status,counts.sourcesCompleteofcounts.sourcesQueried, the staff member who handled a Review or Match, and the outcome under policy. Never the ID image or document number; the hashedsubjectIdrecognises a return visit. - Re-open what the desk saw with
GET /v1/searches/{searchId}/resultsfor 14 days (410afterwards), and render it withPOST /v1/reportwithin 7 days — a PDF of every matching record with a citation per record and the use-restriction notice, identified byX-Report-Id. - Clearances carry
reviewer,note,createdAt,revokedAtandsupersedes;GET /v1/clearances?subjectId=…&clientId=…&includeRevoked=truereproduces the history for one visitor. - Open the record a reviewer looked at with
GET /v1/records/{recordId}; a registry identifier that was later superseded still resolves to the person's current record, so the id you logged keeps working. - Monitor history at
GET /v1/monitors/{monitorId}/alertsties a badge suspension to the alert that caused it. - Data minimisation: the Texas statute and vendor practice agree — keep what the check needs and the result, and nothing else from the ID.
Rollout plan
- Policy. Write the procedure for each of the four desk states, who is notified, and what the visitor is told. Confirm with counsel how registry-use statutes in your states treat facility access.
- Sandbox. With an
os_test_key, run the module above throughClear Visitor,Exact Matchwith and without the date of birth,Name Only, andAlias Match; record a clearance and sign the same ID in again. - Kiosk flow. Derive
subjectIdfrom the ID without storing the number; setdeadlineMsto your line's tolerance; route Review and Match to a staff screen, never the kiosk display. - Review screen. Registry photo beside the ID photo,
offenses[],matchDetail,source.recordUrl; one-clicknot_this_personfor regular visitors. - Credentials and monitors. Create a person monitor on every standing badge; suspend on alert; cancel on expiry. Add a location monitor per site if policy wants it.
- Go live. Switch host and key; watch
elapsedMsand the Incomplete rate for the first week; confirm the first productionsearchIdopens onGET /v1/searches/{searchId}/results. Go-live checklist.