Creating screening orders
One consumer, one end user, one purpose, one consent attestation, one or two searches. The order is accepted at once and finishes in the background.
What you’ll build
The order call at the centre of the FCRA API: POST /v1/fcra/orders with the subject’s identifiers, the end user, the permissible purpose, your attestation of the consumer’s consent, and the searches to run (sex_offender, criminal, or both). You get a 202 with the order at status received, an ETA, and the consumer portal access code; the searches run in the background and report by webhook. On the sandbox the order finishes right after the 202, so one poll shows the complete result.
Before you start
- A test key against
https://sandbox.offendersearch.appand a registered end user (eu_…) with the purpose you will state. - Sandbox subjects are chosen by last name: Clear (every search
clear), Records (one furnished record per search), Withheld, Unavailable, Dispute, Excluded, Sanctioned. Any other last name behaves as Clear. UsePat Records,1980-01-01. - Required:
endUserId,permissiblePurpose,consent(obtainedAt,method∈esign,wet_signature,electronic_click,recorded_verbal,other; optionaldocumentRef),subject(firstName,lastName, a fulldob; optionalmiddleName, up to 5aliases, up to 20addresseswith a two-letterstate),searches[](1–2, eachtypeat most once). Optional:useLocation.state,annualSalary,clientReference,callbackUrl,liveScope(relevantdefault,all), and on a criminal searchexclusions(conviction_onlydefault,all,none). - Send an
Idempotency-Keyheader (≤ 200 characters). The same key and body within 24 hours returns the original order withIdempotent-Replayed: true; a different body is409 idempotency_conflict.
subject.addresses[].state and useLocation.state (plus any exact-date-of-birth candidate). With no addresses and no use state the order has no relevant jurisdictions and its result rests on the platform’s data, every coverage row mode: "snapshot". Send the history you hold.Step by step
- Assemble the order. Subject identifiers exactly as consented; the end user; the purpose that end user holds;
consent.obtainedAtandmethodfrom your authorization flow; the searches; yourclientReference. - Send it with an Idempotency-Key.
POST /v1/fcra/orders→202. Storeid(fo_…), thesearches[].ids (fs_…) andconsumerPortal.accessCode. - Wait for the webhook, or poll.
order.created, then onesearch.completedper search, thenorder.completed. Polling:GET /v1/fcra/orders/{order_id}untilstatusiscomplete,errororcancelled.eta.p50/eta.p90say when to look. - Cancel if you must.
POST /v1/fcra/orders/{order_id}/cancelwhile the order has not finished; queued searches becomecancelled, a running one finishes.409 not_cancellableonce it iscomplete,errororcancelled.
curl -X POST https://sandbox.offendersearch.app/v1/fcra/orders \
-H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
-H "Idempotency-Key: 8f1c2a7e-order-889" \
-H "Content-Type: application/json" \
-d '{
"endUserId": "eu_0b44acbeefeaa7d7",
"permissiblePurpose": "employment",
"consent": { "obtainedAt": "2026-10-25T13:55:00Z", "method": "esign", "documentRef": "auth-55120" },
"subject": {
"firstName": "Pat", "lastName": "Records", "dob": "1980-01-01",
"addresses": [{ "state": "IL", "city": "Springfield", "from": "2019-01", "to": null }]
},
"useLocation": { "state": "IL" },
"searches": [{ "type": "sex_offender" }, { "type": "criminal" }],
"clientReference": "cra-order-889"
}'{
"id": "fo_ee0b34c5b52fd35b",
"status": "received",
"createdAt": "2026-10-25T14:01:07.318402+00:00",
"updatedAt": "2026-10-25T14:01:07.318402+00:00",
"completedAt": null,
"cancelledAt": null,
"clientReference": "cra-order-889",
"endUserId": "eu_0b44acbeefeaa7d7",
"permissiblePurpose": "employment",
"useLocation": { "state": "IL" },
"controlVersion": "fcra-controls-2026-10-19.1",
"liveScope": "relevant",
"eta": { "p50": "2026-10-25T14:02:37+00:00", "p90": "2026-10-25T14:08:07+00:00" },
"sandbox": true,
"searches": [
{ "id": "fs_452884eecde9a1f2", "type": "sex_offender", "status": "queued", "result": null,
"recordCount": 0, "withheld": {}, "createdAt": "2026-10-25T14:01:07.318402+00:00",
"startedAt": null, "completedAt": null, "supersededBy": null, "coverage": [], "incompleteReasons": [] },
{ "id": "fs_9c03d7e1a4b6f258", "type": "criminal", "status": "queued", "result": null,
"recordCount": 0, "withheld": {}, "createdAt": "2026-10-25T14:01:07.318402+00:00",
"startedAt": null, "completedAt": null, "supersededBy": null, "coverage": [], "incompleteReasons": [] }
],
"consumerPortal": { "url": "https://offendersearch.app/consumer/report", "accessCode": "K7QF-2MXP-9RDA" }
}Three things to store from the 202: the order id, each search id, and consumerPortal.accessCode — the code is shown only to the ordering account and belongs in the consumer’s copy of the report. controlVersion names the rule set this order runs under.
Reading the result
| Field | Values | What it decides |
|---|---|---|
| HTTP status | 202 | Accepted. Anything else means no order exists — read detail.code. |
status | received → in_progress → partial → complete or error; cancelled | partial means some searches finished and others are still running. Only complete, error and cancelled are final. |
eta.p50, eta.p90 | date-times or null | When to expect completion, from recent completions of each search type. null once nothing is pending. |
searches[].status | queued, in_progress, complete, unavailable, error, cancelled, superseded | Per-search progress; records are not on the order object — read the search or the report. |
consumerPortal | url, accessCode | What the consumer needs to see what was furnished and dispute it. |
Idempotent-Replayed header | true | This 202 is the original order for a repeated key, not a new order. |
Edge cases and pitfalls
- `404 unknown_end_user` — the
endUserIdis not one of yours; `422 end_user_suspended` — it is, but suspended. - `403 fcra_purpose_not_allowed` — the purpose is not held by the end user or not covered by your agreement. This refusal is written to the evidence record with its reason.
- `422 webhook_endpoint_required` — you sent a
callbackUrlwithout a configured webhook endpoint; the endpoint’s secret is what signs events sent to the callback. - `422` array — a partial or future
dob, aconsent.obtainedAtin the future, the same search type twice, acallbackUrlthat is nothttps://on a public host, an unknown field. Each entry hasloc,msg,type. - `409 idempotency_conflict` — the key was reused with a different body within 24 hours. Mint a new key per distinct order; reuse a key only to retry the same body.
- A running search is not cancelled by
/cancel; it finishes and is furnished. Cancel early or not at all. - Sandbox orders finish immediately; production orders take the ETA. Build the webhook path first and treat polling as the fallback.
Production checklist
- Always send
Idempotency-Key; retry a timeout with the same key and body. - Send the address history and
useLocation.state; sendannualSalarywhen the purpose isemploymentand you hold it. - Persist
fo_,fs_ids and the access code; put the code and portal URL in the consumer’s copy of the report. - Consume
order.completedandsearch.completedwebhooks; poll only as a fallback. - The consent attestation records what you obtained and how; obtaining disclosure, authorization and consent from the consumer stays with you.