Monitoring API · v1.0.0

Person monitoring

A person monitor follows an identity, not a listing. Get the criteria right and the alert stream stays quiet until something real happens.

What you’ll build

A watch on one named person in one dataset — the sex-offender registries or the criminal records the platform holds — that alerts when a matching record appears (new), when a fact about a matched person changes (changed), or when the person is no longer listed in that registry or source (removed). You will create the monitor with a confidence floor, read the alerts it files, and learn which field to key your own records on.

Typical operators: a compliance team watching a roster of caregivers, a housing platform watching admitted residents, a marketplace watching its providers. Each person on the list is one monitor per dataset.

Before you start

  • A test key against https://sandbox.offendersearch.app. Sandbox people for this guide: Exact Match (1980-01-01, one dob_match alert in OH), Multi State (1972-03-14, two alerts — FL and GA), Name Only (three name_match alerts), Felony Record (1975-06-15, a criminal monitor alert with externalId SBX-CR-2015-000123).
  • The person object: lastName is required; firstName and dob (YYYY-MM-DD) are optional but a dob is what keeps the monitor precise.
  • Decide which dataset you are watching. product: "sex-offender" and product: "criminal" are two monitors, even for the same person.
The date of birth is checked for shape only. person.dob must match YYYY-MM-DD or the call is a 422. It is not checked as a calendar date, so 1988-13-45 is accepted and the monitor then runs as if no date had been sent. Validate the date yourself before you send it.

Step by step

  1. Pick the floor. minConfidence is the weakest match the monitor alerts on: dob_match (full date agrees), year_match (birth year agrees — the default with a dob), age_match (a published age is consistent), name_match (name alone — the default without a dob). any means "the default".
  2. Create the monitor. POST /v1/monitors with product, type: "person", person, minConfidence and channels.
  3. Read the alerts. GET /v1/monitors/{monitorId}/alerts. Each alert is one person in one registry; record.personRef is the key.
  4. Store the key. Save record.personRef with record.jurisdiction against your own person record. Treat record.recordId as a pointer to the listing at that moment, not as the person’s identity.

Create a person monitor with a dob_match floor

curl -X POST https://sandbox.offendersearch.app/v1/monitors \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "product": "sex-offender",
    "type": "person",
    "person": { "firstName": "Multi", "lastName": "State", "dob": "1972-03-14" },
    "minConfidence": "dob_match",
    "label": "roster-00412",
    "channels": { "email": "alerts@your-app.example.com" }
  }'
GET /v1/monitors/{monitorId}/alerts — one alert per registry that lists the person
{
  "alerts": [
    {
      "id": "hit_0d3e9b7c51a2f684",
      "eventType": "new",
      "matchLabel": "dob_match",
      "record": {
        "name": "MULTI STATE", "jurisdiction": "GA", "locality": "Sampleville",
        "matchLabel": "dob_match", "matchConfidence": 1.0,
        "personRef": "prs_9a1b2c3d4e5f60718293", "recordId": "rec_7f8e9d0c1b2a39485766"
      },
      "firstSeenAt": "2026-10-25T14:10:02.118930+00:00",
      "occurredAt": "2026-10-25T14:10:02.118930+00:00",
      "notifiedAt": null
    },
    {
      "id": "hit_6c2a8f1e94d7b035",
      "eventType": "new",
      "matchLabel": "dob_match",
      "record": {
        "name": "MULTI STATE", "jurisdiction": "FL", "locality": "Sampleville",
        "matchLabel": "dob_match", "matchConfidence": 1.0,
        "personRef": "prs_4e5f60718293a1b2c3d4", "recordId": "rec_1b2a39485766f7e8d9c0"
      },
      "firstSeenAt": "2026-10-25T14:10:02.118930+00:00",
      "occurredAt": "2026-10-25T14:10:02.118930+00:00",
      "notifiedAt": null
    }
  ],
  "sandbox": true
}

Two alerts, two ids, two personRefs: the person is listed in two registries, and each registry is its own thread. notifiedAt is null here because the sandbox sends no email and this monitor has no webhook — the alerts are still filed and readable.

A criminal person monitor

Same request with product: "criminal". The alert record then carries externalId (the record’s own id at its source, the same value the Criminal Records API returns), recordType (court_case, doc_inmate, jail_booking, healthcare_exclusion, …) and, for an exclusion-list record, an exclusion block; recordId is null.

Criminal alert record (sandbox Felony Record, 1975-06-15)
{
  "name": "FELONY RECORD",
  "jurisdiction": "OH",
  "locality": "Sampleville",
  "matchLabel": "dob_match",
  "matchConfidence": 0.98,
  "personRef": "prs_6d0c2a9e4b1f7e3a8c5d",
  "recordId": null,
  "recordType": "court_case",
  "externalId": "SBX-CR-2015-000123"
}

Reading the result

FieldValuesWhat it means for a person watch
eventTypenewThe person newly matches: they were not matching at the previous evaluation. Everyone who already matches is new on the first evaluation.
eventTypechangedA fact about an already-matched person changed — an address, an offense field, a status, or on a criminal monitor a new booking. A registry republishing the listing under a new number is not a change.
eventTyperemovedThe person is no longer listed in that registry or source. Reported once; matchLabel is null and record carries only event, jurisdiction and personRef.
matchLabeldob_match, year_match, age_match, name_matchWhich strength actually fired. Under any floor you can tell a full-date confirmation from a year-only or age-only one.
record.personRefprs_…The person within the publishing registry or source — the same value the matching record carries in search results. Stable across renumbering. Your reconciliation key.
record.recordIdrec_… or nullSex-offender monitors only: the matching record at the time of the event, for GET /v1/records/{recordId}. null on a criminal monitor.
record.externalIdsource id or nullCriminal monitors: the record’s id at its source. null for registry records.

Edge cases and pitfalls

  • Common names without a `dob` flood the stream: every same-named record alerts at name_match. Supply a dob; if you truly cannot, plan to triage the volume.
  • A contradicting `dob` is silent — the monitor is created and never alerts on that person. Check the dob you hold before blaming coverage.
  • `minConfidence: "dob_match"` hides year-only records. A registry that publishes only a birth year can never reach dob_match; use year_match (the default) if that matters to you.
  • One person, several registries is several alerts with different personRefs. Reconcile on (jurisdiction, personRef), not on the name.
  • A renumbered listing is not an event. No new, no changed, no removed; the same alert id and personRef continue. Do not key on recordId.
  • Exclusion lists on a criminal monitor: only conviction-based exclusions alert, and list entries that publish no date of birth (most SAM.gov and state entries) never alert, because a person monitor is keyed on identity.
  • `minConfidence: "any"` is stored and echoed but behaves as "unset"; an unknown value is a 422.

Production checklist

  • Validate dob as a real calendar date before sending; always send it when you have it.
  • Create one monitor per dataset you need — sex-offender, criminal, or both — and store each mon_ id.
  • Persist record.personRef + record.jurisdiction per alert; look up recordId only to show the listing.
  • Route removed events to a human: disappearance is data, and the alert record is deliberately sparse.
  • Alerts are not a consumer report and may not be used for employment, tenant, credit or insurance decisions.

Product overview