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 onein_radiusalert 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"inaddressis placed at its centre; a street address must come withlatandlngor the call is a422. radiusMilesdefaults to 3 and accepts any value above 0 up to 100.
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
- Resolve your centre. Geocode the street address yourself and send
lat+lng. Keepaddresstoo — it is stored as sent and names the monitor on alert emails when you set nolabel. - 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.
- Create the monitor.
POST /v1/monitorswithtype: "location".minConfidenceis ignored on a location monitor. - Read the alerts.
GET /v1/monitors/{monitorId}/alerts. Each is a person at a registered address inside the radius, labelledin_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" }
}'{
"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
{
"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
{
"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
| Field | Values | What it means for a radius watch |
|---|---|---|
matchLabel | in_radius | The only label a location monitor produces. There is no identity floor; matchConfidence is null. |
eventType | new | A registered address inside the radius that was not there at the previous evaluation — a move in, or a new listing. |
eventType | changed | A record inside the radius changed a fact (address within the radius, offense field, status). |
eventType | removed | The person moved out of the radius or came off the registry. record carries event, jurisdiction and personRef only. |
record.locality | city | City only — an alert never carries a street address. Open recordId with GET /v1/records/{recordId} for the record. |
location.lat, location.lng | numbers | The 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
newalert 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
removedevent may mean a move or a lapse in listing. - Criminal + location returns
200 not_yet_availableon 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,lngand theaddressstring together. - Branch on HTTP
201vs200 not_yet_available; never store anidfrom a200. - Expect the first day’s
newburst; de-duplicate onalertId. - Show
localityand the record viarecordId; never infer a street address from an alert. - Alerts are not a consumer report; every alert email carries that notice.