Managing and cancelling monitors
A monitor has two states and one lifecycle call. This guide covers listing, inspecting, retiring and what each answer means for your records and your bill.
What you’ll build
The housekeeping side of monitoring: a job that lists what your account is watching, matches each mon_ id to the person or place it is for, retires watches that are no longer needed before the next invoice, and keeps the alert history of retired monitors readable. There is no update call — a monitor’s criteria and channels are fixed at creation — so "change" means create a replacement and cancel the old one.
Before you start
- Four calls:
GET /v1/monitors(list, newest first, active only unlessincludeCanceled=true),GET /v1/monitors/{monitorId}(one monitor withrecentAlerts),DELETE /v1/monitors/{monitorId}(cancel),GET /v1/monitors/{monitorId}/alerts(history, also for canceled monitors). - A monitor’s
statusisactiveorcanceled;canceledAtisnulluntil you cancel. - The list has no pagination and no filter other than
includeCanceled; uselabelto carry your own reference.
What cancel means, precisely. Evaluation stops now. The monitor is not charged on any invoice prepared after the cancel — including the invoice for the month you cancel in — and there is no proration for the time it was active in that cycle. The monitor and every alert it filed stay readable.
Step by step
- List what you are paying for.
GET /v1/monitorsreturns every active monitor. Each is one charge at the next invoice ($3 person, $10 location). - Match each monitor to your record. Use
label(set it to your own id at creation) andperson/locationto find the subject. Anything you cannot match is a candidate for cancelling. - Cancel.
DELETE /v1/monitors/{monitorId}. A200withstatus: "canceled"confirms it; cancelling again is harmless (another200). - Keep the history. The alerts of a canceled monitor remain at
GET /v1/monitors/{monitorId}/alerts;GET /v1/monitors?includeCanceled=truelists the monitor itself withcanceledAtset.
List active monitors
curl "https://sandbox.offendersearch.app/v1/monitors" \
-H "X-API-Key: $OFFENDERSEARCH_TEST_KEY"{
"monitors": [
{
"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=…" },
"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 }
},
{
"id": "mon_b8f40d7afb9dc0d8",
"product": "sex-offender",
"type": "person",
"minConfidence": null,
"label": "roster-00412",
"status": "active",
"channels": { "emails": ["alerts@your-app.example.com"] },
"createdAt": "2026-10-25T14:02:11.418223+00:00",
"canceledAt": null,
"person": { "firstName": "Exact", "lastName": "Match", "dob": "1980-01-01" }
}
],
"sandbox": true
}Cancel one
curl -X DELETE https://sandbox.offendersearch.app/v1/monitors/mon_b8f40d7afb9dc0d8 \
-H "X-API-Key: $OFFENDERSEARCH_TEST_KEY"{
"status": "canceled",
"id": "mon_b8f40d7afb9dc0d8",
"note": "Evaluation stops now. The monitor is not charged on any invoice prepared after this cancel; its alerts stay readable.",
"sandbox": true
}{
"detail": {
"code": "personal_alert",
"message": "This alert is billed by its own subscription. Cancel it from Alerts in your personal dashboard."
}
}See it afterwards
curl "https://sandbox.offendersearch.app/v1/monitors?includeCanceled=true" \
-H "X-API-Key: $OFFENDERSEARCH_TEST_KEY"
# … "status": "canceled", "canceledAt": "2026-10-25T16:03:19.511270+00:00" …
curl "https://sandbox.offendersearch.app/v1/monitors/mon_b8f40d7afb9dc0d8/alerts" \
-H "X-API-Key: $OFFENDERSEARCH_TEST_KEY"
# the alert history is unchangedReading the result
| Answer | Values | What it means |
|---|---|---|
GET /v1/monitors → monitors[].status | active | Being evaluated daily and will be charged at the next invoice. |
?includeCanceled=true → status, canceledAt | canceled, date-time | Not evaluated, not charged on invoices prepared after canceledAt; readable. |
DELETE → 200 | {status: "canceled", id, note} | Done. note is a fixed sentence restating the semantics. |
DELETE → 409 | detail.code: "personal_alert" | The monitor belongs to a personal subscription on the same account; it is canceled from the personal dashboard, not the API. |
DELETE / GET → 404 | {"detail": "Monitor not found."} | Unknown id, or another account’s monitor — the API never distinguishes. |
401 / 402 | string detail | Bad key, or no active subscription and no active trial. |
Edge cases and pitfalls
- There is no PATCH. To change a radius, a
dob, a channel or a label, create a new monitor and cancel the old one. Its history stays on the oldid; link the two in your own store. - Cancelling twice returns `200` again and overwrites
canceledAt. Harmless, but do not usecanceledAtas "first cancelled at" if your job may re-run. - The list is everything, unpaginated. With thousands of monitors, cache it and work from
label; there is no server-side search. - Personal alerts appear in the list when a personal subscription shares the account; they are excluded from your invoice and answer
409onDELETE. - Cancel before the invoice is prepared to avoid the charge for that cycle; a monitor active at invoice time is charged in full. The cancel itself is instant.
- A canceled monitor never becomes active again. Re-watching the same person is a new monitor and a new
id.
Production checklist
- Set
labelto your own reference at creation; it is the only filter you will have. - Run a reconciliation that lists active monitors and cancels anything unmatched before each billing cycle.
- Handle
409 personal_alertand404without retrying. - Keep canceled
mon_ids: their alert history is part of your audit trail.