Monitoring API · v1.0.0

Location monitoring

A location monitor stands watch over a place. Give it a centre and a radius, and it reports who moves in, changes, or moves out.

What you’ll build

A sex-offender location monitor: a centre (lat/lng) and a radiusMiles, evaluated once a day against the registered addresses the registries publish. Alerts carry matchLabel: "in_radius" and arrive when a record enters the radius or is newly listed inside it (new), when a record inside it changes (changed), or when it leaves or comes off a registry (removed).

This is the watch for a school, a facility, a property or a route hub — anywhere the question is "who is registered near here" rather than "is this person listed".

Before you start

  • A test key against https://sandbox.offendersearch.app. The sandbox point is 39.9612, -82.9988 (Sampleville, OH): a 1-mile radius there files one in_radius alert for the sample registrant Exact Match. 34.0522, -118.2437 with any radius files none.
  • Coordinates for your centre. On its own, a ZIP code or "City, ST" in address is placed at its centre; a street address must come with lat and lng or the call is a 422.
  • radiusMiles defaults to 3 and accepts any value above 0 up to 100.
Criminal location monitoring is not available yet. product: "criminal" with type: "location" answers `200` with status: "not_yet_available". No monitor is created and nothing is billed. It is a 200, not a 4xx, so check status before assuming you hold an id.

Step by step

  1. Resolve your centre. Geocode the street address yourself and send lat + lng. Keep address too — it is stored as sent and names the monitor on alert emails when you set no label.
  2. Pick the radius. Start tight (1–2 miles) for a single site; widen for a district. Every record inside the radius alerts on the first evaluation, so the radius sets the first day’s volume.
  3. Create the monitor. POST /v1/monitors with type: "location". minConfidence is ignored on a location monitor.
  4. Read the alerts. GET /v1/monitors/{monitorId}/alerts. Each is a person at a registered address inside the radius, labelled in_radius.

Create the monitor

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": "location",
    "location": {
      "address": "100 Sample St, Sampleville, OH 00000",
      "lat": 39.9612, "lng": -82.9988, "radiusMiles": 1
    },
    "label": "Sampleville campus — 1 mi",
    "channels": { "webhookUrl": "https://your-app.example.com/hooks/offendersearch?token=YOUR_SECRET" }
  }'
201 Created
{
  "id": "mon_4f2a9c7e1b8d6035",
  "product": "sex-offender",
  "type": "location",
  "minConfidence": null,
  "label": "Sampleville campus — 1 mi",
  "status": "active",
  "channels": { "emails": [], "webhookUrl": "https://your-app.example.com/hooks/offendersearch?token=YOUR_SECRET" },
  "createdAt": "2026-10-25T15:20:40.771302+00:00",
  "canceledAt": null,
  "location": { "address": "100 Sample St, Sampleville, OH 00000", "lat": 39.9612, "lng": -82.9988, "radiusMiles": 1 },
  "sandbox": true
}

A location monitor carries location and no person key. lat and lng are always filled in the response — when you sent only a ZIP or "City, ST", they are the centre the API placed it at.

The alert

POST to your webhookUrl
{
  "type": "monitor.alert",
  "event": "new",
  "alertId": "hit_a7c3e19f5b2d8046",
  "monitorId": "mon_4f2a9c7e1b8d6035",
  "monitorLabel": "Sampleville campus — 1 mi",
  "product": "sex-offender",
  "detail": {
    "name": "EXACT MATCH",
    "jurisdiction": "OH",
    "locality": "Sampleville",
    "matchLabel": "in_radius",
    "matchConfidence": null,
    "personRef": "prs_3c9e1f0a7b2d4e6f8a1b",
    "recordId": "rec_ab12cd34ef56ab78cd90"
  },
  "occurredAt": "2026-10-25T15:20:40.902211+00:00",
  "sentAt": "2026-10-25T15:20:41.336508+00:00"
}

The criminal + location acknowledgement

200 OK — nothing created
{
  "status": "not_yet_available",
  "message": "Location monitoring for criminal records is coming soon — it activates as location coverage across criminal records expands. Person monitoring is available for criminal records today.",
  "sandbox": true
}

Reading the result

FieldValuesWhat it means for a radius watch
matchLabelin_radiusThe only label a location monitor produces. There is no identity floor; matchConfidence is null.
eventTypenewA registered address inside the radius that was not there at the previous evaluation — a move in, or a new listing.
eventTypechangedA record inside the radius changed a fact (address within the radius, offense field, status).
eventTyperemovedThe person moved out of the radius or came off the registry. record carries event, jurisdiction and personRef only.
record.localitycityCity only — an alert never carries a street address. Open recordId with GET /v1/records/{recordId} for the record.
location.lat, location.lngnumbersThe centre actually evaluated. Check them after creation when you sent an address alone.

Edge cases and pitfalls

  • `422 location monitors need location.lat and location.lng` — you sent a street address without coordinates. Geocode first; the API only places ZIPs and "City, ST" on its own.
  • `422 location.radiusMiles must be between 0 and 100` — 0, negatives and anything over 100 are refused; omit it for the 3-mile default.
  • `lat`/`lng` are not range-checked — a swapped pair (lat: -82.99, lng: 39.96) creates a monitor over the wrong hemisphere and alerts never. Validate ranges yourself.
  • Day one is the loudest. Every registered address already inside the radius is a new alert on the first evaluation. Size the radius, or stage the rollout, with that in mind.
  • A registered address is not a current location. The radius is evaluated against addresses as the registries publish them; a removed event may mean a move or a lapse in listing.
  • Criminal + location returns 200 not_yet_available on both hosts and bills nothing. Hold the watch and re-check the docs rather than retrying in a loop.
  • Same place, two products is not possible today; a sex-offender location monitor costs $10 per cycle and is the only location product.

Production checklist

  • Geocode on your side; send lat, lng and the address string together.
  • Branch on HTTP 201 vs 200 not_yet_available; never store an id from a 200.
  • Expect the first day’s new burst; de-duplicate on alertId.
  • Show locality and the record via recordId; never infer a street address from an alert.
  • Alerts are not a consumer report; every alert email carries that notice.

Product overview