Managing disputes
A dispute is an endpoint. Open it with the records and the date it reached you, follow it by webhook, and apply the outcome everywhere you hold the record.
What you’ll build
The dispute flow from your side: when a consumer disputes a record you received from us, open a reinvestigation on the order naming the records, the reason and when the dispute reached you; add the consumer’s new information if it arrives; receive the outcome by dispute.updated; and react to record.corrected, which reaches every partner that received the record. You will run it on the sandbox subject Dispute, whose dispute resolves deleted sixty seconds after you file it.
Before you start
- A completed sandbox order for Pat Dispute (1980-01-01) with a criminal search; it furnishes one record with id
sandbox:criminal:dispute-1980-01-01. - Required:
recordIds[](1–100, each furnished in this order — including by a superseded search) andreason∈not_me,inaccurate,incomplete,outdated,other. Optional:consumerStatement(≤ 8,000),receivedAt(a past date-time). - Statuses:
received→investigating→verified|modified|deleted|unable_to_verify.overdueis true for an open dispute pastdueAt.
Send `receivedAt`. The 30-day clock runs from when the dispute reached you (15 U.S.C. § 1681i(a)(1)); without
receivedAt it runs from the call. dueAt is receivedAt + 30 days. If the consumer sends new information during the investigation, POST /v1/fcra/disputes/{dispute_id}/information moves dueAt by 15 days — once (15 U.S.C. § 1681i(a)(1)(B)).Step by step
- Open the dispute.
POST /v1/fcra/orders/{order_id}/disputes→201atreceivedwithdueAt. You also receivedispute.created. - Add information if it comes.
POST /v1/fcra/disputes/{dispute_id}/informationwith{ "statement": "…" }→200withdueAtmoved andextendedAtset;409 dispute_not_extendableif resolved, already extended, or past due. - Follow it.
dispute.updatedon each change;GET /v1/fcra/disputes/{dispute_id}for the current state;GET /v1/fcra/disputes?status=&orderId=lists every dispute on your orders, earliest due first. - Apply the outcome.
verified: nothing changes.modified:changessays what; the record carriescorrections[]in later orders.deletedandunable_to_verify: the record is suppressed from every later FCRA order. All three sendrecord.correctedto every partner that received the record.
curl -X POST https://sandbox.offendersearch.app/v1/fcra/orders/fo_ee0b34c5b52fd35b/disputes \
-H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
-H "Content-Type: application/json" \
-d '{
"recordIds": ["sandbox:criminal:dispute-1980-01-01"],
"reason": "not_me",
"consumerStatement": "I have never lived in that county.",
"receivedAt": "2026-10-25T09:12:00Z"
}'{
"id": "fd_acae08b7d57ccc18",
"orderId": "fo_ee0b34c5b52fd35b",
"origin": "partner",
"recordIds": ["sandbox:criminal:dispute-1980-01-01"],
"reason": "not_me",
"consumerStatement": "I have never lived in that county.",
"receivedAt": "2026-10-25T09:12:00+00:00",
"dueAt": "2026-11-24T09:12:00+00:00",
"extendedAt": null,
"status": "received",
"notes": null,
"changes": null,
"resolvedAt": null,
"overdue": false
}{
"id": "fd_acae08b7d57ccc18",
"orderId": "fo_ee0b34c5b52fd35b",
"origin": "partner",
"recordIds": ["sandbox:criminal:dispute-1980-01-01"],
"reason": "not_me",
"status": "deleted",
"receivedAt": "2026-10-25T09:12:00+00:00",
"dueAt": "2026-11-24T09:12:00+00:00",
"extendedAt": null,
"notes": null,
"changes": null,
"resolvedAt": "2026-10-25T14:31:19.004118+00:00",
"overdue": false
}{
"id": "fev_9c1d3e5f7a2b4068",
"type": "record.corrected",
"createdAt": "2026-10-25T14:31:19.021776+00:00",
"data": { "recordId": "sandbox:criminal:dispute-1980-01-01", "disputeId": "fd_acae08b7d57ccc18", "outcome": "deleted" }
}Reading the result
| Field | Values | What it decides |
|---|---|---|
status | received, investigating | Open. The clock is running; overdue flips when dueAt passes. |
status | verified | Confirmed as furnished. Nothing changes. |
status | modified | Corrected. changes lists what; later orders carry corrections[] on the record. |
status | deleted | Removed, or found not to be the consumer’s. Suppressed from every later FCRA order (disputed_suppressed). |
status | unable_to_verify | Could not be verified. Treated as deleted: suppressed the same way. |
dueAt, extendedAt | date-times | receivedAt + 30 days; + 15 days once when information is added, with extendedAt recording when. |
origin | partner, consumer_portal | Who opened it. Portal disputes are the same object and reach you by dispute.created. |
Edge cases and pitfalls
- `422 unknown_record` — a
recordIdsentry was not furnished in this order (the message names it). Use therecordIdexactly as the search returned it. - `receivedAt` in the future is a
422; areceivedAtlong in the past makes the disputeoverdueat once. Send the real date. - `409 dispute_not_extendable` carries one of three messages: already resolved, already extended once, or the period has ended.
- Disputes without an access code reach us through the consumer request form, are reinvestigated by our team and are not listed among your disputes; when one deletes or corrects a record you received, you still get
record.corrected. - `record.corrected` can arrive for an old order and from another partner’s dispute. Key your handling on
recordId, not on your open disputes. - The list is unpaginated, ordered earliest due first; filter with
statusandorderId. - Sandbox auto-resolution applies only to the subject Dispute and only to
deleted; other outcomes need a production dispute.
Production checklist
- Open disputes with
receivedAt; storefd_ids against the consumer’s case. - Consume
dispute.created,dispute.updatedandrecord.corrected; suppress or correct the record in your own copies. - Watch
overdueon the list; escalate beforedueAt. - Notices to the consumer about the outcome, and any adverse-action steps, stay with you; the API reinvestigates and corrects the record.