# Regular & Live modes, and source coverage

> Regular is the continuously-updated answer; Live re-verifies named jurisdictions in-request at $0.02 a source. Every record reports its own lastCheckedAt.

- **HTML:** https://offendersearch.app/docs/freshness
- **Base URL:** https://api.offendersearch.app
- **Authentication:** `X-API-Key` request header
- **OpenAPI:** https://offendersearch.app/openapi.json · https://offendersearch.app/openapi.yaml
- **All documentation as markdown:** https://offendersearch.app/docs.md

## Two modes: Regular, and Live verification

There is one search endpoint, and which mode you get is decided by the request body. Omit the `live` block and you get the **Regular** answer — the continuously-updated data we hold, close to live, returned in one round trip. Send a `live` block and the API re-verifies the named jurisdiction(s) in real time. Both modes run every result through the **same** match logic and return the **same** response envelope, so the choice is about how the answer is obtained, not about a different product.

|  | Regular (default) | Live verification |
| --- | --- | --- |
| How you ask | Omit `live` | Send a `live` block |
| What you get | The continuously-updated answer, ranked in one round trip — close to live. | Re-verifies the named jurisdiction(s) in-request, then merges with the Regular answer. |
| Speed | One round trip | ≤2 sources may run inline; more → asynchronous |
| Price | Base per-call rate only | Base rate plus $0.02 per completed live source, to a $2.00 ceiling |

Base is $0.15 per call, dropping to $0.11 after 2,000 calls a month. A Regular search is billed as one call at that base rate. A live check adds $0.02 for each source that completes, and the live charge for one search never exceeds $2.00 — a price ceiling, not a source limit. A live check that fails is not billed.

## Regular — the default

The Regular answer reads the continuously-updated data we hold, assembled into one ranked result, and returns in a single round trip. It is close to live — current enough for the great majority of screening decisions — and it is the mode you get when you say nothing. Every record still carries its own `lastCheckedAt`, so currency is a value you read off the answer rather than an assumption about the service.

## Live verification

A live check re-verifies at the named jurisdiction(s) as part of your request, then merges the fresh results with the Regular answer. Send a `live` block with `jurisdictions: [codes]` (an explicit list) or `scope: "matched"` (re-verify every jurisdiction that produced a Regular hit). A live-verified record carries a fresh `lastCheckedAt`, and a live result supersedes the Regular copy of the same person; nothing is lost from either side.

```json
{
  "query": { "firstName": "John", "lastName": "Doe", "dob": "1980-04-12" },
  "live": { "jurisdictions": ["TX"], "scope": "matched" }
}
```

A live check of **≤2 sources** runs synchronously — one `200`, results inline. **More than two sources** runs asynchronously: a `202` with a `searchId` and a `poll` URL. Both paths run the same live checks and return the same envelope. An empty live result is not, on its own, evidence of absence: check `counts.sourcesIncomplete == 0` before treating an empty `records` array as "not found".

## Currency is answered per request

Two fields carry it, and both are on every response. `sources[].lastCheckedAt` is when that jurisdiction’s copy of the record was last confirmed, and `sourceStatus[].lastCheckedAt` is the same question at the jurisdiction level for every jurisdiction the search touched — including the ones that returned no match. Read them rather than a general statement about the dataset: they are contract fields, and they answer the question for the exact record and the exact request in front of you.

```json
{
  "source": {
    "jurisdiction": "NJ",
    "registryName": "State Sex Offender Registry",
    "recordUrl": "https://…",
    "scrapedAt": "2026-08-13T04:12:00Z",
    "lastCheckedAt": "2026-08-13T04:12:00Z",
    "sourceUpdatedAt": "2026-08-11T00:00:00Z"
  },
  "sources": []
}
```

`sourceUpdatedAt` is the date the jurisdiction itself states it last changed the record; it is `null` where a jurisdiction publishes no such date.

## GET /v1/sources — List sources

The coverage catalog: every jurisdiction we cover, by code, name, and live health.

**Authentication:** `X-API-Key` header.

The live coverage catalog: every jurisdiction the API covers, with its code, display name, and health signals such as typical latency and last successful refresh. Use it to render your own coverage UI or to decide which `jurisdictions` to name.

### What you can do

- **Coverage.** Every jurisdiction with its code and name.
- **Health.** Typical latency and last successful refresh timestamps, per jurisdiction.

### Request

**cURL**

```bash
curl https://api.offendersearch.app/v1/sources \
  -H "X-API-Key: $OFFENDERSEARCH_KEY"
```

**Node**

```javascript
const res = await fetch("https://api.offendersearch.app/v1/sources", {
  headers: { "X-API-Key": process.env.OFFENDERSEARCH_KEY },
});
const sources = await res.json();
for (const s of sources) console.log(s.id, s.scope, s.covers.join(","));
```

**Python**

```python
import os, requests

resp = requests.get(
    "https://api.offendersearch.app/v1/sources",
    headers={"X-API-Key": os.environ["OFFENDERSEARCH_KEY"]},
)
for s in resp.json():
    print(s["id"], s["scope"], ",".join(s["covers"]))
```

### Response

```json
[
  {
    "id": "NJ",
    "name": "State Sex Offender Registry",
    "covers": ["NJ"],
    "health": { "lastSuccessAt": "2026-07-25T09:14:00Z", "typicalLatencyMs": 380 }
  },
  {
    "id": "CA",
    "name": "State Sex Offender Registry",
    "covers": ["CA"],
    "health": { "lastSuccessAt": "2026-07-25T09:14:00Z", "typicalLatencyMs": 410 }
  }
]
```

---

## Related

- Previous: [Pagination & response size](https://offendersearch.app/docs/pagination.md)
- Next: [Jurisdictions & codes](https://offendersearch.app/docs/jurisdictions.md)
- Index: [Offendersearch API documentation](https://offendersearch.app/docs.md)
