FCRA Partner API · v1.0.0 · approved partners

Search scope

Where an order searches and why: the plan, the policy, the gaps, and the identity layer.

Livehttps://api.offendersearch.appSandboxhttps://sandbox.offendersearch.app

How the scope is built

Every order carries a search plan: the counties, states and national tier it searches, why each is there, and the sources behind each. The plan is computed from the subject’s address history and the order’s use location before anything runs, stored on the order, shown in the report, and it decides which sources the live discovery checks.

  1. Each address inside the lookback window is placed in a county — by the county you name, else by the ZIP, else by the city (a city that straddles a county line lists every county it touches).
  2. The counties around each residence county come in: those whose internal point lies within radiusMiles, nearest first, up to maxNeighborCounties. County lines and state lines do not stop it.
  3. The use location’s county and state come in; the states of every in-scope county come in; the national tier is always in.
  4. Each jurisdiction is matched to the sources we hold for it — county, city and regional criminal sources by county, statewide sources by state, the federal tier, and the sex offender registry of each state. A source added to the catalog with its geography appears in every later plan with no change on your side.
  5. A county with no county-level source is reported as a gap, with the statewide sources that still cover it. The result is unchanged unless the policy sets requireCountyCoverage.

Preview before you order

POST /v1/fcra/search-plans returns the plan for an address history, a use location and the searches you would order, without placing an order: nothing is stored, nothing is metered, no evidence row is written. Use it to show a reviewer where a search will go, or to decide whether to widen it.

POST /v1/fcra/search-plans
curl -X POST "https://api.offendersearch.app/v1/fcra/search-plans" \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "endUserId": "eu_0b44acbeefeaa7d7",
    "addresses": [
      { "state": "OH", "city": "Columbus", "postalCode": "43215", "from": "2019-01" },
      { "state": "TX", "postalCode": "76102", "from": "2012-01", "to": "2017-06" }
    ],
    "useLocation": { "state": "OH", "city": "Dublin" },
    "searches": [{ "type": "sex_offender" }, { "type": "criminal" }],
    "scopePolicy": { "maxNeighborCounties": 3 }
  }'

Preview an FCRA search plan — POST /v1/fcra/search-plans request and response

POST/v1/fcra/search-plansAuth: X-API-Key

Where an order would search for an address history and a use location: the counties, states and national tier in scope, why, the sources behind each, and the gaps. Nothing is stored or metered.

Request

Body · application/json
FieldTypeDescription
endUserId
optional
string or nullAn end user whose scope policy is the base for this plan.
addresses
optional
array of SubjectAddress or nullThe subject's address history, up to 20.
useLocation
optional
UseLocation or nullWhere the job, the housing or the transaction is.
searches
required
array of SearchSpecThe searches the order would carry, one to three.
scopePolicy
optional
ScopePolicy or nullOverrides on top of the end user's policy.

Response

200 · application/json

The plan.

The body is the SearchPlan object. The plan: every address as it was read, every jurisdiction in scope with its reasons and sources, the gaps, and the totals.

Errors
StatusCodeWhen
401—No valid X-API-Key (the body is { "detail": "…" }, a string).
403fcra_not_enabledThis account has no active FCRA partner agreement.
403fcra_not_on_trialFCRA orders are not available on a trial account.
404unknown_end_userNo such end user on this account.
422—A field failed validation (the standard validation body), including an address source other than declared.

The SearchPlan object — where an order searches and why

The plan: every address as it was read, every jurisdiction in scope with its reasons and sources, the gaps, and the totals.

Returned or accepted by POST /v1/fcra/search-plans, GET /v1/fcra/orders/{order_id}/scope.

FieldTypeDescription
version
stringThe scope engine version the plan was computed with.
Example "scope-2026-10-26.1"
policy
ScopePolicyThe effective policy, every field filled in.
computedAt
string (date)The day the plan was computed (the lookback window counts from it).
searchTypes
array of string
one of "sex_offender", "criminal", "healthcare"
The searches the plan is for.
addresses
array of ScopeAddressEvery address from the request, as read.
jurisdictions
array of ScopeJurisdictionCounties first, then states, then the national tier.
gaps
array of ScopeGapCounties in scope with no county-level source.
excluded
array of stringJurisdiction ids your policy removed.
summary
objectThe totals.
summary.counties
integerCounties in scope.
summary.states
integerStates in scope.
summary.sources
objectSources behind the plan.
summary.sources.live
integerSources searchable today.
summary.sources.pending
integerSources mapped and coming soon.
summary.gaps
integerCounties in scope with no county-level source.
summary.unresolvedAddresses
integerAddresses that could not be placed in a county (no usable ZIP, city or county).
healthcare
objectPresent when the plan includes a healthcare search: every healthcare source in every state (not tied to the consumer's addresses).
healthcare.scope
string
one of "all_states"
Always all_states.
healthcare.sources
array of objectEach source: id, name, state, scope, status (live / pending), recordKinds, live, identifiers, matchOn, freshness.
healthcare.statesCovered
array of stringStates with at least one live healthcare source.
healthcare.statesWithoutSource
array of stringStates with none yet — disclosed, like a county gap.
healthcare.sourceCounts
objectTotals.
healthcare.sourceCounts.live
integerSources searchable today.
healthcare.sourceCounts.pending
integerSources mapped and coming soon.

The ScopeAddress object — one address as the plan read it

One address from the request, with how it was placed in a county and whether it fell inside the lookback window.

Returned or accepted by POST /v1/fcra/search-plans, GET /v1/fcra/orders/{order_id}/scope.

FieldTypeDescription
index
integerIts position in the request's addresses.
state
string or nullThe state, upper-cased.
city
string or nullAs sent.
postalCode
string or nullAs sent.
county
string or nullThe county it resolved to, or the county name as sent.
from
string or nullAs sent.
to
string or nullAs sent.
source
string
one of "declared", "document", "traced"
Where the address came from. declared today; document and traced are reserved for the identity layer.
inLookback
booleanWhether the address counts under the policy's lookbackYears.
resolvedBy
string
one of "county", "zip", "city", "unresolved"
What placed it in a county: a named county, the ZIP, the city, or nothing (unresolved).
candidateCounties
array of objectEvery county the address may be in (a ZIP that crosses a county line, a city in several), up to six.
candidateCounties[].fips
stringCounty FIPS code.
candidateCounties[].state
stringState.
candidateCounties[].name
stringCounty name.
candidateCounties[].id
stringThe jurisdiction id (OH-FRANKLIN).

The ScopeJurisdiction object — one county, state or the national tier

One jurisdiction in the plan with why it is there and the sources behind it.

Returned or accepted by GET /v1/fcra/orders/{order_id}/report, POST /v1/fcra/search-plans, GET /v1/fcra/orders/{order_id}/scope.

FieldTypeDescription
id
string<ST>-<COUNTY> for a county, the state code for a state, US for the national tier.
Example "OH-FRANKLIN"
level
string
one of "county", "state", "national"
county for a county court or booking source, state for a statewide repository, national for a source that is not tied to one place.
state
string or nullThe state; null for the national tier.
county
string or nullThe county name; null unless level is county.
fips
string or nullThe county FIPS code; null unless level is county.
reasons
array of ScopeReasonWhy it is in scope.
sources
array of ScopeSourceThe sources the plan searches for it.
sourceCounts
objectHow many of this jurisdiction's sources answer live today (live) and how many are known but not yet searched live (pending).
sourceCounts.live
integerLive sources.
sourceCounts.pending
integerPending sources.
gap
booleanPresent and true on a county with no live county-level source; its statewide sources still apply (see gaps).

The ScopeReason object — why a jurisdiction is in scope

One reason a county or state is in the plan. Only the keys that apply to the reason are present.

Returned or accepted by GET /v1/fcra/orders/{order_id}/report, POST /v1/fcra/search-plans, GET /v1/fcra/orders/{order_id}/scope.

FieldTypeDescription
kind
string
one of "residence", "neighbor", "use_location", "client_added", "always"
residence (the subject lived there), neighbor (near a residence county), use_location, client_added (your policy), always (the national tier).
address
integer or nullThe address index this reason comes from.
source
string or nullThe address's source.
from
string or nullThe address's from.
to
string or nullThe address's to.
resolvedBy
string or nullHow the address was placed (county, zip, city).
of
string or nullFor neighbor: the residence jurisdiction it neighbours.
miles
number or nullFor neighbor: miles between the two counties' internal points.

The ScopeSource object — a source behind a jurisdiction

One source the plan will search for a jurisdiction: a criminal source from the source registry, or a sex offender registry.

Returned or accepted by GET /v1/fcra/orders/{order_id}/report, POST /v1/fcra/search-plans, GET /v1/fcra/orders/{order_id}/scope.

FieldTypeDescription
product
string
one of "criminal", "sex_offender", "healthcare"
Which search uses it.
id
stringThe public source code, the same value as sources[].code on a record.
Example "OH-FRANKLIN"
name
string or nullThe jurisdiction label.
scope
stringcounty, city, regional, statewide, federal, state or national.
status
string
one of "live", "pending"
live (searchable today) or pending (mapped, coming soon).
recordKinds
array of stringThe kinds of record the source holds (criminal sources).
live
booleanWhether a live check can run against it.
yearsBack
integer or nullHow many years of records the source states it holds; null when unknown.

The ScopeGap object — a county with no county-level source

A county in scope for which we hold no county-level source, and what still covers it.

Returned or accepted by GET /v1/fcra/orders/{order_id}/report, POST /v1/fcra/search-plans, GET /v1/fcra/orders/{order_id}/scope.

FieldTypeDescription
id
stringThe jurisdiction id.
county
stringThe county name.
state
stringThe state.
coveredBy
array of stringThe live statewide criminal sources that still cover the county.
pendingSources
array of stringCounty-level sources mapped for it and coming soon.

The scope policy

A scope policy says how far beyond the subject’s own counties an order searches. Set it once on an end user (scopePolicy on POST /v1/fcra/end-users or PATCH); every order for that end user uses it, and an order may override any field with its own scopePolicy. Every field is optional.

FieldDefaultWhat it does
lookbackYears7An address whose to date is older is out of scope. An address with no dates always counts.
radiusMiles25How far around each residence county to look for neighbouring counties.
neighborCountiestrueWhether neighbouring counties come in at all.
maxNeighborCounties6At most this many neighbours per residence, nearest first.
includeUseLocationtrueWhether the use location’s county and state come in.
statewidetrueWhether the statewide sources of every in-scope state are searched.
nationaltrueWhether the national tier is searched.
requireCountyCoveragefalseWhen true, a county with no county-level source makes the search incomplete rather than a reported gap.
extraJurisdictions—Counties or whole states to add.
excludeJurisdictions—Counties or whole states to leave out. A county the subject lived in can never be removed.

The ScopePolicy object — where an order searches

Where an order searches beyond the subject's own counties. Set it once on an end user; override any field on an order. Every field is optional and the defaults are in each description.

Returned or accepted by POST /v1/fcra/end-users, GET /v1/fcra/end-users, GET /v1/fcra/end-users/{end_user_id}, PATCH /v1/fcra/end-users/{end_user_id}, POST /v1/fcra/orders, GET /v1/fcra/orders/{order_id}/report, POST /v1/fcra/search-plans, GET /v1/fcra/orders/{order_id}/scope.

FieldTypeDescription
lookbackYears
optional
integer or nullHow many years back an address counts, 1 to 30. An address whose to date is older is out of scope. Default 7.
Example 7
radiusMiles
optional
number or nullHow far around each residence county to look for neighbouring counties, 0 to 100. Default 25.
Example 25
neighborCounties
optional
boolean or nullWhether neighbouring counties come into scope at all. Default true.
maxNeighborCounties
optional
integer or nullAt most this many neighbours per residence, nearest first, 0 to 20. Default 6.
Example 6
includeUseLocation
optional
boolean or nullWhether the use location's county and state come into scope. Default true.
statewide
optional
boolean or nullWhether the statewide sources of every in-scope state are searched. Default true.
national
optional
boolean or nullWhether the national tier (federal sources) is searched. Default true.
requireCountyCoverage
optional
boolean or nullWhen true, a county in scope with no county-level source makes the search incomplete instead of a reported gap. Default false.
extraJurisdictions
optional
array of ScopeTarget or nullCounties or whole states to add to the scope.
excludeJurisdictions
optional
array of ScopeTarget or nullCounties or whole states to leave out. A county the subject lived in can never be removed.

The ScopeTarget object — a county or state to add or remove

A county (by name) or a whole state, named in a scope policy's extraJurisdictions or excludeJurisdictions.

Returned or accepted by POST /v1/fcra/end-users, GET /v1/fcra/end-users, GET /v1/fcra/end-users/{end_user_id}, PATCH /v1/fcra/end-users/{end_user_id}, POST /v1/fcra/orders, GET /v1/fcra/orders/{order_id}/report, POST /v1/fcra/search-plans, GET /v1/fcra/orders/{order_id}/scope.

FieldTypeDescription
state
required
stringTwo-letter state code, any case.
Example "KY"
county
optional
string or nullThe county name in any common form (Jefferson, Jefferson County, Orleans Parish). Omit it to name the whole state.
Example "Jefferson"

The scope on an order and in the report

The order object carries scope: the totals and one line per jurisdiction. GET /v1/fcra/orders/{id}/scope returns the full plan, addresses included. The report carries the same plan without the addresses, so whoever reads a clear can see exactly which counties, states and sources it rests on.

Retrieve an FCRA order's search plan — GET /v1/fcra/orders/{order_id}/scope

GET/v1/fcra/orders/{order_id}/scopeAuth: X-API-Key

The full plan the order ran under: every county and state in scope, why, and the sources behind each.

Request

Path parameters
FieldTypeDescription
order_id
required
stringThe order id (fo_…).
Example "fo_ee0b34c5b52fd35b"

Response

200 · application/json

The plan.

FieldTypeDescription
orderId
stringThe order id.
scope
SearchPlanThe plan.
Errors
StatusCodeWhen
401—No valid X-API-Key (the body is { "detail": "…" }, a string).
403fcra_not_enabledThis account has no active FCRA partner agreement.
403fcra_not_on_trialFCRA orders are not available on a trial account.
404not_foundNo such order on this account.

The ScopeSummary object — an order's scope in brief

The scope on an order: the totals and one line per jurisdiction. The full plan is GET /v1/fcra/orders/{order_id}/scope.

Returned or accepted by POST /v1/fcra/orders, GET /v1/fcra/orders, GET /v1/fcra/orders/{order_id}, POST /v1/fcra/orders/{order_id}/cancel, POST /v1/fcra/orders/{order_id}/adjudication.

FieldTypeDescription
version
stringThe scope engine version.
Example "scope-2026-10-26.1"
counties
integerCounties in scope.
states
integerStates in scope.
sources
objectSources behind the plan.
sources.live
integerSources searchable today.
sources.pending
integerSources mapped and coming soon.
gaps
integerCounties in scope with no county-level source.
unresolvedAddresses
integerAddresses that could not be placed in a county (no usable ZIP, city or county).
jurisdictions
array of objectOne line per jurisdiction.
jurisdictions[].id
stringThe jurisdiction id.
jurisdictions[].level
string
one of "county", "state", "national"
county, state or national.
jurisdictions[].gap
booleantrue on a county with no live county-level source.

The ReportScope object — the scope as stated in a report

The plan the report ran under, without the addresses: jurisdictions, sources, gaps and totals. A reader holds a clear against exactly this.

Returned or accepted by GET /v1/fcra/orders/{order_id}/report.

FieldTypeDescription
version
stringThe scope engine version.
policy
ScopePolicyThe effective policy.
jurisdictions
array of ScopeJurisdictionEvery jurisdiction searched.
gaps
array of ScopeGapCounties with no county-level source.
excluded
array of stringJurisdiction ids the policy removed.
summary
objectThe totals.
summary.counties
integerCounties in scope.
summary.states
integerStates in scope.
summary.sources
objectSources behind the plan.
summary.sources.live
integerSources searchable today.
summary.sources.pending
integerSources mapped and coming soon.
summary.gaps
integerCounties in scope with no county-level source.
summary.unresolvedAddresses
integerAddresses that could not be placed in a county (no usable ZIP, city or county).

Gaps

A gap is a county in scope for which we hold no county-level source. It is listed with the live statewide sources that still cover the county (a state court index, a department of corrections roster, the exclusion lists) and any county-level source mapped for it and coming soon. A gap does not change the result; set requireCountyCoverage on the policy if your program needs it to.

The healthcare search in the plan

A healthcare search is not tied to the consumer’s counties: an exclusion in any state bars the person from that state’s program, so every healthcare source is searched for every consumer. The plan shows them in its own healthcare block — never mixed into jurisdictions, so asking for a healthcare search never widens where the criminal search looks.

FieldWhat it holds
scopeAlways all_states.
sourcesEvery healthcare source: id (the source code records carry), name, state, scope (federal or statewide), status (live or pending), recordKinds (exclusion, nurse-aide), live (whether a match is confirmed live at the source), identifiers (what its records carry), matchOn (the identifiers a match on it can rest on: full_dob, npi, license_number) and freshness (maxAgeDays, and when the publisher replaces the file).
statesCoveredStates with at least one live healthcare source.
statesWithoutSourceStates with none yet — disclosed, like a county gap. They do not change the result.
sourceCountslive and pending totals.

A source we add to the healthcare package appears in the next plan, the next healthcare search and its coverage with no change on your side.

The live scope inside the search plan

The plan decides which sources an order covers; liveScope decides which of them are asked live. With relevant and all, the plan’s sources in the relevant states are searched live and the rest in our data. With matches, every source in the plan is searched in our stored copy first; a source in the plan that we hold no copy of is searched live (copyStatus: "none", liveReason: "no_copy" on its coverage row), and a possible record found anywhere in the plan is confirmed with the agency that keeps it before it can be reported. The plan is the same whichever live scope the order runs.

The identity layer

Every order carries identity. Identity verification (a government ID and selfie check) and address-history trace are coming soon; until a provider is live, both report not_available and the note says what the order was matched on: the name, the full date of birth and the addresses provided. An address source other than declared is refused until then.

The Identity object — verification and trace status

The identity layer of an order. Identity verification and address-history trace are coming soon; until a provider is live both report not_available and orders are matched on the name, full date of birth and the addresses provided.

Returned or accepted by POST /v1/fcra/orders, GET /v1/fcra/orders, GET /v1/fcra/orders/{order_id}, POST /v1/fcra/orders/{order_id}/cancel, GET /v1/fcra/orders/{order_id}/report, POST /v1/fcra/orders/{order_id}/adjudication.

FieldTypeDescription
verification
objectDocument and selfie verification of the subject.
verification.status
string
one of "not_available", "pending", "verified", "failed"
not_available until a provider is live.
verification.provider
string or nullThe provider, once one is live.
trace
objectAddress-history trace.
trace.status
string
one of "not_available", "answered", "thin", "failed"
not_available until a provider is live.
trace.provider
string or nullThe provider, once one is live.
note
string or nullA plain statement of what the order was matched on while the layer is not available.

Test FCRA orders on the sandbox server

With a test key (os_test_…) every account is a sandbox FCRA partner on the sandbox server, with the same last-name rules. Register an end user, then place orders against it; nothing is billed. An order finishes right after the 202: read it once with GET /v1/fcra/orders/{orderId}.

Base URL
https://sandbox.offendersearch.app Not https://api.offendersearch.app, which takes live keys only.
Test key
In the dashboard, Developers → API keys → Create test key. Test keys begin os_test_; send yours in the X-API-Key header.
1. POST /v1/fcra/end-users
curl -X POST "https://sandbox.offendersearch.app/v1/fcra/end-users" \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "name": "Sample Staffing LLC",
      "permissiblePurposes": [
        "employment"
      ],
      "certification": {
        "text": "End user certifies it will use reports only for employment purposes.",
        "version": "v1",
        "certifiedAt": "2026-10-01T12:00:00Z"
      }
    }'
2. POST /v1/fcra/orders
curl -X POST "https://sandbox.offendersearch.app/v1/fcra/orders" \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "endUserId": "{endUserId}",
      "permissiblePurpose": "employment",
      "consent": {
        "obtainedAt": "2026-10-01T12:00:00Z",
        "method": "esign"
      },
      "subject": {
        "firstName": "Pat",
        "lastName": "Records",
        "dob": "1980-01-01"
      },
      "searches": [
        {
          "type": "sex_offender"
        },
        {
          "type": "criminal"
        }
      ]
    }'

Try it here

Pick a scenario, edit the request, and run it against the sandbox from this page. The explorer accepts os_test_ keys only and never reaches the live host, so nothing here is billed; the “production” code it copies is for your own backend with a live key.

Create a test key under Dashboard → API keys. Live keys are refused here.
Copy this request as code

Sandbox code uses your test key. Production code calls the live host and is billed per call; run it from your backend with an os_live_… key.

curl -X POST https://sandbox.offendersearch.app/v1/fcra/end-users \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{ "name": "Sample Staffing LLC", "permissiblePurposes": [ "employment" ], "certification": { "text": "End user certifies it will use reports only for employment purposes.", "version": "v1", "certifiedAt": "2026-10-01T12:00:00Z" } }'

Product overview