Data methodology and provenance
Where the records come from, how current they are, how a match is judged, and what each jurisdiction publishes. Written for the person who has to defend the decision to embed this data inside their own product.
Every record names the registry it came from
Records originate from the official sex-offender registry maintained by each state, territory and federal authority. There is no second class of data: nothing is derived from another aggregator, from private files, or from any source a jurisdiction did not itself publish.
The citation travels with the record
Every record carries source.jurisdiction, source.registryName and source.recordUrl — the registry’s own address for that registrant on its own site. A claim in a response can be taken back to the authority that made it.
One person, every registry they appear on
A registrant listed by more than one authority returns as a single record whose sources[] names each registry with its own link. Someone registered in South Carolina who works in Texas is one person, cited three ways, not three rows to reconcile.
Nothing is manufactured to fill a field
Where a registry publishes a year and not a date, we return the year. Where it publishes an age, we return the age. A birth year is never inferred from an age, and a day and month are never chosen to complete a partial date — an estimate served as a fact is the one error that cannot be audited later.
58 registries, and what each of them publishes
A nationwide request covers 58 registries: all 50 states, the District of Columbia, the five US territories, and the federal NSOPW index. Jurisdictions differ in what they release, because each legislature decided that separately. The API states which case applies on every record rather than flattening them.
Date of birth
| What the registry publishes | Jurisdictions | What the API returns |
|---|---|---|
| A full calendar date of birth | The majority of registries, including California, which publishes an exact date for effectively every registrant | dob carries an ISO-8601 date; dobPrecision is "exact" |
| A birth year only | Massachusetts, Georgia, North Dakota, Nevada and Pennsylvania — and Kansas on part of its registry | dob is null, birthYear carries the year, dobPrecision is "year" |
| An age, and no date at all | Arizona, Maryland and Wisconsin, on every surface those registries render | dob and birthYear are null, age carries the value, dobPrecision is "unknown" |
A record without a full date is still reachable by a name-and-date search. The widening happens when your query is matched, not when the record is stored, and the result says which kind of check was performed — see match strength.
Physical description
Maine is the statutory case: its registry publishes no physical description at all — no height, weight, eye or hair colour, and no scars, marks or tattoos — because state law releases those details only against a written request naming the requester. Twelve registries publish no scars-marks-and-tattoos field on any page they render: Kentucky, Nebraska, West Virginia, Idaho, South Dakota, Alaska, Texas, Illinois, Virginia, Georgia, Massachusetts and Maine. Around 28 others do, each at its own rate.
Addresses
The District of Columbia publishes block-level addresses by design (“2100 BLOCK OF NEW HAMPSHIRE AVENUE NW”) and no house numbers on any surface. Maine publishes a residence street only for registrants inside its tier scheme and the town alone for everyone else. Residence, employment and school addresses are published independently by a registry and need not agree, so addresses[] is an array with a type on each entry.
Risk classification
offense.riskLevel and offense.tier are the jurisdiction’s own assessment in the jurisdiction’s own vocabulary (“Sexually Violent Predator”, “Level 3”, “Tier II”). There is no cross-state scale and the API does not invent one. Florida publishes a registration status on every registrant and no risk level at all, which is why status lives in its own field rather than being folded in.
Which registry holds a person
registrationState is the registering authority; addressStates is where their published addresses are. They disagree constantly — a meaningful minority of registrants held by a state registry have no address in that state — so the API keeps them as two fields rather than one ambiguous “state”. Tribal registries are their own registering authority and are not mapped onto the state they sit inside.
Fields a single jurisdiction publishes
Some fields exist in one registry and nowhere else. They are named here rather than presented as national coverage, so nobody builds a feature on one of them by accident.
| Field | Published by |
|---|---|
offense.federal — federal vs state conviction | Oklahoma |
stateData.sentenceCompletionDate | Oklahoma |
stateData.professionalLicenses | Utah |
stateData.birthCity / birthState — place of birth | Wyoming |
stateData.shoeSize | Texas |
stateData.build | Minnesota |
stateData.district / psa / quadrant | District of Columbia |
stateData.skinTone, residencyRestriction, employmentRestriction, exclusionZones | Iowa |
Per-jurisdiction fill rates move as registries republish, so they live in the field reference rather than on this page. See the API documentation and coverage by jurisdiction.
Cadence is a contract term, not a claim
Records are refreshed against their issuing registry on a continuous nightly cycle. Rather than asserting a general currency figure, every response carries the timestamps and the per-source outcome for the request you actually made.
Per-record timestamps
lastCheckedAt is when that specific record was last confirmed against its source registry. sourceUpdatedAt is the registry’s own last-updated date and is null on most registries, because few publish one — a property of the registry, and stated rather than filled in.
Freshness chosen per request
Each request selects its tier: daily — the default, and the freshest data we hold — or weekly, the same dataset a step behind, at the base rate. It is a per-request parameter, not a property of your key, so batch work and live screening can run on one integration.
Every response states its own scope
counts.sourcesQueried, sourcesComplete, sourcesIncomplete and sourcesSkippedByScope report how many registries the request reached and how each resolved, and sourceStatus[] names them individually. An integrator branches on the scope of the answer rather than assuming it.
You set the confidence threshold, not us
A match is labelled with the evidence that produced it, so the decision about what counts as a hit stays inside your product. matchState is the field to branch on: it says how the record was checked against the date of birth or age you supplied, in terms of what the registry actually published.
matchState | What it means |
|---|---|
dob_match | The registry holds a full date of birth and it equals the date you queried. The strongest identity confirmation this API can return. |
year_match | The registry publishes only a birth year and that year equals your date’s year. The day and month were never checked, because the jurisdiction never published them. |
age_match | The registry publishes an age rather than a date, and that age agrees with the age your date implies, within a year for birthday drift. |
no_dob_age_year | The registry publishes no date, year or age for this registrant. The record is returned because the name matched, and it is flagged unverified so it can never be read as an identification. |
age_mismatch | The name matched, the registry publishes an age, and that age contradicts your date. Returned only when you ask for it with onAgeMismatch: "flag" — "omitted" and "checked, and it disagrees" are different facts, and you decide which you want. |
null | You supplied no date of birth and no age, so there was nothing to verify against. Not a failed check. |
Why not a single score
matchConfidence is published too, but it measures how much identity evidence is held — not the probability that this is your person. Two records at the same score can rest on different evidence, which is why the labelled state is the field to filter on and the score is the field to rank with.
Why a record was returned
matchBasis gives the qualified reason — lastName:exact, firstName:nickname, alias:prefix — and matchedName says whether the registered name or an alias matched. An alias hit and a legal-name hit are distinguishable at a glance.
Recall is a request parameter
match selects strict (exact names only), balanced (the default: nicknames, variants, initials, single typos) or broad (prefix and typo tolerance on the surname too). The same rules apply uniformly across all 58 registries.
Differences are returned, not smoothed over
Fifty-eight authorities publish to fifty-eight standards. The contract’s job is to make that difference legible in code rather than leave an integrator to infer it from an empty string.
Precision is a field, not an inference
dobPrecision states what the registry published — exact, year or unknown — so a year the registry printed is distinguishable from a year read off a full date. Every date inside offense and stateData carries its own datePrecision, and datesAsPublished preserves the registry’s literal text alongside the normalised ISO-8601 value.
Three-state flags, so absence is not a false
flags.absconder and flags.predator are true, false or null. null means the registry publishes no such designation, which is a different fact from a registry affirmatively reporting that the person does not carry it. A falsy check would merge the two.
The durable key is the registry’s own
sources[].recordUrl, with its jurisdiction, is the registry’s permanent address for that registrant. It is the value to store and to diff on, because it is the identifier the issuing authority will still honour next year — and it is what makes a record we return traceable by anyone who wants to check it.
Status and risk are separate questions
Whether someone is currently confined, absconded, deported, deceased or living in the community is stateData.status, in the jurisdiction’s own words. It is not a risk level, and the two are never merged — Florida’s nine status values and a New York tier answer different questions.
Where this data may and may not be used
Disputing or correcting a record
A registry entry is maintained by the authority that issued it, and that authority is the only party that can amend the official record. Both routes below are open to anyone, with no account and no charge.
If a record is about you
Ask the issuing jurisdiction to correct its entry — that is the change that lasts — and tell us at support@offendersearch.app. Include the name as published, the jurisdiction, and the link to the entry if you have it. We acknowledge within one business day, re-verify against what the registry currently publishes, and bring our copy into line with it. Full detail on the about page.
If you are integrating
Send the recordId, the source.recordUrl and the query that produced the response to support. Those three make the result reproducible on our side, which is what turns a report into a fix.
Every field, defined
The API reference documents each field, each enum value and each response guarantee, with per-jurisdiction detail.