Errors, rate limits & retries
One error shape, a Retry-After you can trust, and a short list of what to retry.
The error body
Two shapes, by product. Sex Offender, Monitoring, Map, FCRA and every `401`/`402`: JSON with a detail. For a field validation failure it is a list, one entry per field, each with loc (the path to the field), msg and type; for a refused request an object with a code and a sentence; on a 429 an object with a code and retryAfterSeconds. Criminal Records refusals (`4xx`/`5xx` from the service): JSON with an error object carrying code and message. Branch on the code, never on the sentence.
// Sex Offender refusal
{ "detail": { "code": "query_too_broad",
"message": "Add a first name, a date of birth or a state." } }
// Criminal Records refusal
{ "error": { "code": "query_too_broad",
"message": "Add a first name, a date of birth or a state." } }Status codes
| Status | Meaning | Retry? |
|---|---|---|
200 | The answer. Read status (complete / partial) before records. | — |
202 | Accepted: an async job with an id to poll (POST /v1/searches, live.evidence, a criminal search with more than two live sources). A Sex Offender live search that goes async answers 200 with status: "pending" and a poll path instead. | — |
400 / 422 | The request was refused: a missing required field, an invalid value, a query too broad to answer honestly. detail (criminal: error.code) says which. | No — fix the request. |
401 | Authentication failed. | No — fix the key or the host. |
403 | Not entitled (Map API, FCRA partner, consumer account on a business route). | No — enable it. |
404 | Unknown id or path. | No. |
429 | Rate limited or at capacity; nothing was searched or billed. Retry-After carries the seconds to wait (1–120); on the Sex Offender API detail.retryAfterSeconds repeats it. A criminal 429 comes from the gateway after it has queued the request for up to 45 s and may carry no JSON body — read the header. | Yes — after Retry-After. |
5xx | The platform failed or a dependency timed out. Nothing was searched; nothing was billed. (504 also answers a Sex Offender search that hit its deadlineMs with onDeadline: "error", or that found nothing before some registries finished: incomplete_no_results.) | Yes — with backoff, for requests that are safe to repeat (below). |
What to retry
- Safe to retry: every
GET; a synchronous search without aliveblock;429afterRetry-After. - Retry with care: a synchronous search *with* a
liveblock (a repeat re-checks the sources again and bills again) — read the error first. - Never retry blindly:
POST /v1/searches(async). Every accepted request is a new, billed job and there is no idempotency header. Persist thesearchIdfrom the202and recover by polling it. See Async jobs & webhooks.
import time, requests
def call(method, url, **kw):
for attempt in range(4):
r = requests.request(method, url, timeout=60, **kw)
if r.status_code == 429:
time.sleep(int(r.headers.get("Retry-After", "5")))
continue
if 500 <= r.status_code < 600 and method == "GET":
time.sleep(2 ** attempt)
continue
return r
return rRate limits
Limits are per account and are enforced as 429 with Retry-After; there is no fixed public number, because the platform queues briefly before refusing and the headroom depends on your plan. A 429 is never a partial answer: nothing was searched and nothing was billed. Product pages note where a product queues or caps differently (Sex Offender errors, Criminal errors, Map limits).