Offendersearch
API Reference · v1.0.0

Regular & Live modes, and source coverage

Two modes — Regular (continuously updated) and Live — per-record lastCheckedAt, and the live coverage catalog.

Base URL https://api.offendersearch.app

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 a Regular record and a Live record agree about identity and rank together — the choice is about how the answer is obtained, not about a different product.

Regular (default)Live verification
How you askOmit liveSend a live block
What you getThe continuously-updated answer, assembled into one ranked result. Close to live, and returned in one round trip.Re-verifies the named jurisdiction(s) in-request, then merges the fresh results with the Regular answer through the same matcher.
SpeedOne round trip≤2 sources may run inline; more → asynchronous
PriceBase per-call rate onlyBase rate plus $0.02 per completed live source, to a $2.00 ceiling per search

Regular — the default, and what almost every caller wants

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.

  • No live block, no usage block — a Regular search is billed as a single call at the base per-call rate.
  • status is "complete". Every jurisdiction the request touched still reports its own row in sourceStatus[], so an incomplete search is labelled, never returned as a silently short list.
  • Each record carries sources[].lastCheckedAt — when we last confirmed that person at that jurisdiction. There is no separate freshness flag; the timestamp is the answer.

Live verification — checked at the source, right now

A live check is interactive and real-time — the mode to reach for when a decision needs a current, moment-of-decision verification of a specific person. Send a live block and the API re-verifies at the named jurisdiction(s) as part of your request, then merges the fresh results with the Regular answer:

Ask for a live re-verification
{
  "query": { "firstName": "John", "lastName": "Doe", "dob": "1980-04-12" },
  "live": { "jurisdictions": ["TX"], "scope": "matched" }
}
  • jurisdictions: [codes] — an explicit list of jurisdiction codes to re-verify live.
  • scope: "matched" — re-verify every jurisdiction that produced a Regular hit for this query.

A live-verified record carries a fresh lastCheckedAt; the merge lets a live result supersede the Regular copy of the same person, and the whole set is re-ranked. A person only the live pull found is added; a person only the Regular answer held stays — nothing is lost.

An empty live result is not, on its own, evidence of absence. A live source is bounded to stay fast, so it can be a lower bound even when it answered. Before treating an empty records array as “not found”, check counts.sourcesIncomplete == 0 first — > 0 means at least one source could not be searched to the end. Full discipline in Result completeness.

Sync vs async — a latency rule, not a product difference

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 — the only difference is whether you hold the connection open or collect the result later. Regular searches and synchronous live searches never produce a job to poll. See Async search & webhooks for the poll handshake.

What a live check costs

  • +$0.02 per live source that completes. A Regular search has no such charge — it is the base per-call rate only.
  • The live charge for one search never exceeds $2.00. This is a price ceiling, not a source limit — you may request live checks across more sources than the ceiling covers and all of them run; past the ceiling the marginal live check is free.
  • A live check that fails is not billed. Only sources that complete are metered, and the usage block always reports the true count even when the charge is capped.
usage — reported on live searches
"usage": { "liveChecks": 2, "liveChargeUsd": 0.04, "capped": false,
           "display": "2 live checks · $0.04" }

Currency is answered per request, not per claim

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.

Provenance on a record
{
  "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": [ /* one entry per corroborating jurisdiction */ ]
}

sourceUpdatedAt is the date the jurisdiction itself states it last changed the record; it is null where a jurisdiction publishes no such date. Running at volume? POST /v1/searches has no request-timeout ceiling, so every named jurisdiction runs to completion.

GET/v1/sourcesAuth: X-API-Key

List sources

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

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 https://api.offendersearch.app/v1/sources \
  -H "X-API-Key: $OFFENDERSEARCH_KEY"
Response
200 OK
[
  {
    "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 }
  }
]