How to switch from offenders.io in four steps. Each step is reversible; compat stays up the whole time.
- 01
Swap host and key
Point at the compat endpoint and run your existing test suite unchanged.
POST /v1/compat/sexoffender - 02
Shadow-read native
Send the same query to native search and diff the record sets on each official registry link and jurisdiction.
POST /v1/search - 03
Adopt the labels
Branch on matchState for your auto-accept rule and on the incomplete-source count to separate no match from not determined.
counts.sourcesIncomplete - 04
Cut over
Move production traffic to native search; keep compat for any integration you have not migrated.
both stay supported
How offenders.io request parameters map to OffenderSearch
The compatibility endpoint accepts these as they are. The native contract has a home for each one.
| offenders.io parameter | OffenderSearch /v1/search | Notes |
|---|---|---|
| firstName / lastName | query.firstName / query.lastName | Same fields |
| dob | query.dob | YYYY-MM-DD; raises matchConfidence |
| city / state / zipcode | query.city / query.state / query.zipcode | Location filters |
| address | query.address | Street match |
| lat / lng / radius | query.lat / query.lng / query.radiusMiles | Radius in miles, up to 100 |
| q | query.q | Free text across name, aliases, city, ZIP and address |
| fuzzy: true | match: "balanced" | A mode, applied the same way across registries |
| prefixMatch | query.prefixMatch | Also accepts "both"; matches aliases too |
| mode: "extensive" | include: ["stateData"] | Full offenses, photos, vehicles |
| uuid / personUuid | GET /v1/records/{recordId} | Direct record lookup |
| page | page / perPage | Native search can return the whole set in one response |
| createdAt / updatedAt filters | query.createdAtStart/End, query.updatedAtStart/End | Inclusive date ranges |
| faceId (facial search) | — | Not part of the current contract |
Every field, with types and edge cases, is in the migration guide.
Moving from the compatibility endpoint to the native API
Most teams stay on compat for the first week, then shadow-read native /v1/search alongside it and diff the results. Key the diff on each record’s official registry link and jurisdiction, not on recordId, which is derived from the merge for a given query scope.
The main reason to finish the move is completeness. The legacy envelope can only signal a problem with a single error: 503, and {"offenders": [], "error": 503} means nothing was established — not that nobody matched. The native response marks a search partial and names each registry that did not complete and why in sourceStatus.
Once you branch on matchState and the incomplete-source count, you can auto-accept strong matches, route weak ones to a reviewer and retry partial searches. Native search also returns the whole de-duplicated set in one response up to a defined cap, so most pagination loops go away. Read matching and result completeness before you cut over.
A four-week offenders.io cut-over plan
A schedule most teams can run without a freeze. Each stage is reversible, because compat stays up the whole time.
| Week | What you change | What you check before moving on |
|---|---|---|
| 1 | Base URL and key on staging, then on a slice of production | Your existing tests pass unchanged; error handling treats a 503 as not determined |
| 2 | Shadow-read native /v1/search beside every compat call | Record sets agree on registry link and jurisdiction for the names you care about |
| 3 | Read matchState, matchConfidence and the incomplete-source count | Your auto-accept and review thresholds route a sample of real matches correctly |
| 4 | Production traffic on native search; turn on webhooks, batch or monitoring if you use them | Partial searches retry; reviewers see the official registry link on each match |
Smaller integrations often do weeks one and two in a single afternoon. Nothing forces the native move.
What to check before you cut over from offenders.io
- The same names. Run the queries your product actually sends, not a demo list, through both.
- Record sets, not counts. A count can match while the people differ; diff on registry link and jurisdiction.
- Partial answers. Confirm your code treats a 503 on compat, and
partialon native, as not determined. - Match handling. Decide which matchState values auto-clear, which go to review and which block.
- Record depth. Check the fields you rely on — photographs, aliases, typed addresses, offenses — on real records.
- Total cost. Price your month with add-ons included, as on the comparison page.
offenders.io alternative FAQ: migrating your integration
Is OffenderSearch a drop-in replacement for offenders.io?
POST /v1/compat/sexoffender accepts their parameters and returns their { offenders, page, totalPages } envelope. Switching means changing the base URL and the API key; your parsing code stays as it is.How long does a migration take?
Do I have to migrate off the compatibility endpoint?
What behaves differently on compat?
error: 503. {"offenders": [], "error": 503} means nothing was established — not that nobody matched. On native search the same condition is status: "partial" with a sourceStatus entry naming each registry and why.Is facial search supported?
Can our operations team use it before engineering integrates?
Does the same key cover criminal records?
Will our record IDs change?
recordId is derived from the merge for a given query scope, so store the official registry link and jurisdiction as your durable key, and use GET /v1/records/{recordId} for direct lookups within a session.How is pricing different after the switch?
Is there a free evaluation?
What else runs on the same OffenderSearch API key
Run both APIs and read the difference: test any sex offender search alternative against OffenderSearch on the same names.
25 free calls, a compat endpoint that speaks their shape, and the full contract waiting when you are ready.