OffenderSearch
API Reference · v1.0.0

Sex offender API reference

Every endpoint and every object, each linked to the one place its fields are defined.

Base URL https://api.offendersearch.app

How the sex offender API reference is organised

Every endpoint documents what it is sent and what it returns in two tables, next to the guide that explains it: POST /v1/search on Search, the async endpoints on Async & webhooks, and so on. Shared objects — the Record a search returns, the per-registry SourceStatus, the Error body — are defined once and linked from every table that carries them. The same tables are in the markdown twin of each page and in openapi.json.

Sex offender API endpoints — request and response fields

Each endpoint links to its reference block: a Request table (headers, path, query and body fields, with type, required and default) and a Response table (every field, with type and when it is null).

EndpointWhat it does
POST /v1/searchSearch. Search every US sex offender registry in one call. Send a SearchRequest; get back one de-duplicated, scored result set with a status for every registry searched.
POST /v1/searchesAsync search. Submit a search and collect the result later, by polling or by webhook. Accepts every SearchRequest field plus webhookUrl, including a live block (the named registries are re-verified live inside the job and billed as on POST /v1/search), and returns 202 with a job id immediately.
GET /v1/searches/{searchId}Get a search. Read an async job's status and, once it has finished, its full result.
GET /v1/searches/{searchId}/resultsStored search results. Re-open a past search on this account and get back exactly the records it returned. The search is not run again and the call is not billed.
POST /v1/evidence/searchesStart a live evidence search. On the Live Evidence host — https://evidence.offendersearch.app, not the main API's base URL; same key, same header. Send the same body as POST /v1/search; the registries named in live.jurisdictions (up to 20) are the ones printed. The live check runs as usual, so the records come back exactly as POST /v1/search returns them, and each named registry's own public website is printed to one PDF behind a cover page. Always answers 202; collect the result from GET /v1/evidence/searches/{searchId}.
GET /v1/evidence/searches/{searchId}Poll a live evidence search. On the Live Evidence host — https://evidence.offendersearch.app. Read the job's status and, once it has finished, the full search response plus evidence[], one entry per registry named in live.jurisdictions.
GET /v1/evidence/searches/{searchId}/{jurisdiction}.pdfDownload an evidence document. On the Live Evidence host — https://evidence.offendersearch.app. Download one registry's live evidence document — the registry's own public website, printed as shown behind a cover page. The path is the downloadUrl of the matching evidence[] entry. Not billed: the document was billed when it was produced.
POST /v1/batchBatch search. Run up to 1000 searches in one call, one result per input row, in input order. Each row is billed as one search.
POST /v1/reportVerification report. One consolidated PDF for a search you already ran, with a source citation on every record. The search's recorded criteria are run again when you request it.
POST /v1/searches/{searchId}/proofProof documents. Render per-registry proof documents for a search you already ran. Needs billing on the account.
GET /v1/proof-docs/{token}Fetch a proof document. Download one rendered proof document by the token in its documents[].url.
GET /v1/records/{recordId}Get a record. Fetch one Record by the recordId or the registry uuid a search returned. A recordId resolves while the search that returned it is retained (14 days).
GET /v1/sourcesList sources. The coverage catalog: every registry the API searches, by code, with its coverage and health. No API key required.
POST /v1/compat/sexoffenderCompatibility search. The drop-in endpoint: legacy parameter names in, the legacy {offenders, page, totalPages} envelope out. Switch an existing integration by changing the base URL and key.
GET /v1/compat/sexoffenderCompatibility search (GET). The same search as the POST form, with the parameters in the query string.
POST /v1/clearancesRecord a clearance. Record that the person behind a personRef is (this_person) or is not (not_this_person) the subject identified by your subjectId. The verdict must cite the search that showed the person. A second verdict for the same subject, client and person replaces the first. Included in the search price; nothing here is metered.
GET /v1/clearancesList a subject's clearances. Every clearance recorded for one subjectId (and clientId, when you send one), newest first. Active clearances only unless includeRevoked=true. The ids you send are hashed before the lookup, so a listing needs the same subjectId and clientId the verdicts were recorded under.
GET /v1/clearances/{clearanceId}Retrieve a clearance. One clearance by id — the clearanceId a search response's cleared[] or clearance carries. subjectId and clientId are null on this route: only a per-account hash of them is stored.
DELETE /v1/clearances/{clearanceId}Revoke a clearance. Sets revokedAt. The verdict stops applying at once: the next search for that subject shows the person again. The clearance stays readable with includeRevoked=true, so the audit trail is not lost.

Sex offender API objects — Record, SourceRef, SourceStatus and more

Each object is defined once, on the page linked here. Wherever an endpoint or another object carries it, the type links back to that one definition.

ObjectWhat it is
QueryWho or where to search for. Every field is optional; send what you know. A name, a date of birth or age, or a location narrows the search, and a query that narrows on nothing is refused with a 422 rather than answered with a partial list.
LiveBlockAsks for named registries to be re-checked at query time (Live verification). Omit it for the Regular answer. Send jurisdictions or scope, not both.
SourceStatusOne registry's outcome within a search. Each registry the search touched has one entry in sourceStatus[], so coverage is stated per response rather than assumed.
LiveUsageThe live-check charge summary. Present only on the result of a search that carried a live block — the inline response, or the polled result of a live job; its presence is the signal that the answer was re-verified live.
MeterEventOne billable unit behind a live result. Present only on a live result, in meterEvents[].
EvidenceLiveUsageThe usage block of a live evidence search: every LiveUsage field — the live check is billed exactly as on the main API — plus the evidence documents.
EvidenceDocumentOne registry's live evidence document: the registry's own public website, printed as shown to a PDF behind a cover page. One entry per registry named in live.jurisdictions, in evidence[]. A complete document carries the capture and download fields; an incomplete one carries incompleteReason and detail and is never billed.
ProofRequestAsks for per-registry proof documents: the registries to render and the format. Billing must be enabled on the account. Each rendered document is billed as one PDF, at the same rate as a verification report.
ProofBundleThe proof documents rendered for a search. {"status": "none"} when no proof was requested.
RecordOne registrant, normalized to the same shape across every US registry. Every key is present on every record; an empty value is "", [] or null as stated per field, never a missing key.
NameA person's name as the registry publishes it, split into parts. Each part is "" when the registry does not publish it.
AddressOne published address. A record carries every address the registry publishes, each typed.
OffenseOne offense as the registry publishes it. Every field is present; a field the registry does not publish is "" (or null for federal). Dates are ISO-8601, at the precision the registry published.
SourceRefWhere a record came from: the registry, its own link to the registrant, and three timestamps. source is the registry that supplied the record's top-level values; sources[] lists every registry that holds the person.
StateDataThe extended per-state detail. Returned only when the request sends include: ["stateData"]; otherwise stateData is null and omittedFields names it. Each field is "", [] or null when the registry does not publish it.
ErrorThe error body. detail is a sentence you can log or show; the HTTP status is the value to branch on.
RateLimitErrorThe 429 Too Many Requests body. The Retry-After header carries the same wait as detail.retryAfterSeconds.
OffendersIoRequestThe parameters of the compatibility endpoint, in the legacy offenders.io names. Sent as a JSON body to POST /v1/compat/sexoffender, or as query parameters to GET /v1/compat/sexoffender.
OffendersIoResponseThe legacy {offenders, page, totalPages} envelope, returned unchanged so an existing integration keeps working.
OffendersIoOffenderOne registrant in the legacy flat shape: every legacy key, plus personUuid, county, stateData, matchConfidence and matchState. Coordinates use -1 for missing, not null.
CompatErrorThe error body on the compatibility endpoint: {code, message} in the legacy shape, instead of {detail}.
ClearanceOne verdict: the person behind a personRef is, or is not, the subject being screened. Returned by every /v1/clearances route. subjectId and clientId are echoed only on the request that carried them (create and list); on GET /v1/clearances/{clearanceId} they are null, because only a per-account hash of them is stored.
ClearedEntryOne record a not_this_person clearance removed from records[] for the subjectId sent. cleared[] carries one of these per suppressed record, so an interface can say how many previously cleared people are hidden and link each to its clearance.
RecordClearanceThe clearance that names a record's person, attached as clearance when the request carried subjectId and the record was kept: in clearanceMode: "annotate", on a this_person verdict, or because a cleared person has re-surfaced (staleReason set). A record no clearance names carries no clearance key.

Product overview