# Monitoring billing and limitations

> Flat, per monitor, per cycle, counted when the invoice is prepared. Everything else about cost and coverage follows from that sentence.

- **HTML:** https://offendersearch.app/docs/guides/monitoring/billing-and-limits
- **Base URL (live):** https://api.offendersearch.app — live keys (`os_live_…`)
- **Base URL (sandbox):** https://sandbox.offendersearch.app — test keys (`os_test_…`), never billed
- **Authentication:** `X-API-Key` request header
- **OpenAPI:** https://offendersearch.app/openapi.json · https://offendersearch.app/openapi.yaml
- **Docs index for agents:** https://offendersearch.app/llms.txt

## What you’ll build

An accurate forecast of your Monitoring invoice line from the API itself, and a clear picture of what a monitor can and cannot see. The billing rule is simple — every **active** monitor at the moment an invoice is prepared is charged its full price: **$3** for a person monitor, **$10** for a location monitor, whichever product it watches — and the limits are the ones that decide whether a monitor is the right tool for a given watch.

## Before you start

- Monitors are charged on the same monthly invoice as your other usage, totalled on one Monitoring line, broken down by `product:type` with a count, a rate and an amount.
- Creating a monitor bills nothing at that moment; cancelling bills nothing either. There is no monitor usage event — the charge is a **count at invoice time**.
- A free-trial account can create monitors on the live host; an account with no active subscription and no active trial gets `402`. The sandbox never bills.

> **No proration, in either direction.** A monitor created the day before the invoice is charged in full; a monitor cancelled the day before the invoice is not charged at all. Time active within the cycle does not matter — `status` at invoice time does.

## Step by step

1. **List active monitors.** `GET /v1/monitors` (default: active only). Each row is one charge.
2. **Price each row.** `type: "person"` → $3; `type: "location"` → $10. `product` does not change the price, but it does separate the breakdown lines (`sex-offender:person`, `criminal:person`, `sex-offender:location`).
3. **Retire before the invoice.** `DELETE /v1/monitors/{monitorId}` for anything you no longer need; it leaves the count immediately.
4. **Check the invoice.** The Monitoring line in [billing](/dashboard/billing) should equal your forecast. Personal-subscription alerts on the same account are excluded from it.

**cURL**
```bash
curl "https://sandbox.offendersearch.app/v1/monitors" \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY"
```

**Python**
```python
import os, requests
from collections import Counter

RATE_CENTS = {"person": 300, "location": 1000}

res = requests.get("https://sandbox.offendersearch.app/v1/monitors",
                   headers={"X-API-Key": os.environ["OFFENDERSEARCH_TEST_KEY"]}, timeout=30)
res.raise_for_status()
counts = Counter((m["product"], m["type"]) for m in res.json()["monitors"])   # active only

total = 0
for (product, type_), n in sorted(counts.items()):
    cents = n * RATE_CENTS[type_]
    total += cents
    print(product + ":" + type_, n, "x", RATE_CENTS[type_] / 100, "=", cents / 100)
print("forecast Monitoring line: $" + str(total / 100))
```

**JavaScript**
```javascript
const RATE_CENTS = { person: 300, location: 1000 };

const res = await fetch("https://sandbox.offendersearch.app/v1/monitors", {
  headers: { "X-API-Key": process.env.OFFENDERSEARCH_TEST_KEY },
});
const { monitors } = await res.json();                     // active only

const counts = new Map();
for (const m of monitors) {
  const key = m.product + ":" + m.type;
  counts.set(key, (counts.get(key) ?? 0) + 1);
}
let total = 0;
for (const [key, n] of [...counts].sort()) {
  const cents = n * RATE_CENTS[key.split(":")[1]];
  total += cents;
  console.log(key, n, "x", RATE_CENTS[key.split(":")[1]] / 100, "=", cents / 100);
}
console.log("forecast Monitoring line: $" + (total / 100).toFixed(2));
```

*Output for two SO person, one criminal person and one SO location monitor*
```plain
criminal:person 1 x 3.0 = 3.0
sex-offender:location 1 x 10.0 = 10.0
sex-offender:person 2 x 3.0 = 6.0
forecast Monitoring line: $19.0
```

Watching one person in both datasets is the first and third lines together: two monitors, two `mon_` ids, $6. There is no combined monitor.

## Reading the result

| Watch | Request | Price per cycle | Status |
| --- | --- | --- | --- |
| Sex-offender person | `product: "sex-offender"`, `type: "person"` | $3 | Live |
| Criminal person | `product: "criminal"`, `type: "person"` | $3 | Live |
| Sex-offender location | `product: "sex-offender"`, `type: "location"` | $10 | Live |
| Criminal location | `product: "criminal"`, `type: "location"` | $10 when it ships | Coming soon — `200 not_yet_available`, nothing created, nothing billed |

Rates are the standard ones; a plan may carry its own per-monitor rates, which the invoice breakdown reflects. Enterprise terms are on the [pricing page](/pricing).

## Limitations

| Limit | Value | Consequence |
| --- | --- | --- |
| Evaluation cadence | Once a day | An alert reflects a change seen at the daily evaluation; there is no faster tier and no on-demand evaluation in production (the sandbox `test-event` route is sandbox only). |
| Sex-offender coverage | All 58 US registries, continuously updated | A person or radius is watched across the registry data the search endpoint uses. |
| Criminal coverage | The federal, state and county sources the platform holds | Person monitors only. Exclusion-list records alert only when conviction-based and when the entry publishes a date of birth. |
| Criminal location | Not available | `200 not_yet_available`. |
| `radiusMiles` | > 0 and ≤ 100, default 3 | `422` outside the range. |
| Alert history per call | `limit` 1–200, default 50 | No cursor; 200 most recent per request. `recentAlerts` on the monitor is 10. |
| `webhookUrl` / email | ≤ 2,000 chars, `https`, public host / ≤ 254 chars | `422` at creation otherwise. |
| Webhook delivery | One attempt per run, 10 s timeout, signed per monitor, redirects not followed | Retried next day if neither channel succeeded. |
| Alert detail | City only, never a street address | Open the record by `recordId` (sex-offender) for the listing. |
| Sandbox | 200 monitors per account, 200 alerts per monitor, 60 requests/min per key | Reset on redeploy; no email; nothing billed. |

## Edge cases and pitfalls

- **"I cancelled on the 28th and was still charged"** — the invoice was prepared before the cancel. Cancel earlier in the cycle; there is no proration to recover.
- **"Two charges for one person"** — one sex-offender monitor and one criminal monitor. That is the model; budget per dataset.
- **`402` on create** — the account has neither an active subscription nor an active trial. Monitoring has no separate entitlement; fix the subscription.
- **A `200 not_yet_available`** never appears on an invoice; nothing was created.
- **Many alerts do not cost more.** Price is per monitor, not per alert or per delivery; a noisy name-only monitor costs $3 like a quiet one.
- **Alerts are not a consumer report** and may not be used for employment, tenant, credit or insurance decisions; that is a condition of use, not a limit of the data.

## Production checklist

- Forecast the Monitoring line from `GET /v1/monitors` before each cycle.
- Cancel unneeded monitors as soon as the need ends; the saving is the whole next cycle.
- Plan person watches per dataset; plan location watches for the sex-offender product only, today.
- Design for a daily cadence — reconcile once a day after the run, not continuously.

## Related

- [Billing (reference)](https://offendersearch.app/docs/monitoring/billing)
- [Location monitoring](https://offendersearch.app/docs/monitoring/location-monitoring)
- [Pricing](https://offendersearch.app/pricing)
- [Monitoring sandbox](https://offendersearch.app/docs/monitoring/sandbox)
