# FCRA adverse action API: from pre-adverse notice to decision

> For engineers at approved FCRA partners: what each adverse-action endpoint does, from the complete order through the pre-adverse notice, the waiting period, disputes, the post-adverse notice and the recorded decision. The process itself is for your counsel.

- **HTML:** https://offendersearch.app/docs/learn/fcra-adverse-action-api
- **Base URL (live):** https://api.offendersearch.app — live keys (`os_live_…`)
- **Base URL (sandbox):** https://sandbox.offendersearch.app — test keys (`os_test_…`), never billed
- **Authentication:** `X-API-Key` request header
- **OpenAPI:** https://offendersearch.app/openapi.json · https://offendersearch.app/openapi.yaml
- **Docs index for agents:** https://offendersearch.app/llms.txt

## What the FCRA adverse action API does, and what it leaves to counsel

When an end user is considering a decision against a consumer because of a report, the [FCRA Partner API](/fcra-api) can run the two-step adverse-action process as the consumer reporting agency for `direct` and `platform` partners. You name the records the decision rests on; the API sends the consumer the pre-adverse notice with the report access code, waits the period the use location requires, and sends the post-adverse notice. The end user still makes the decision; the API never decides for it.

This page describes **what each endpoint does**: request, response, state machine, error codes. When adverse action is required, what a notice must contain and how long to wait are questions for your counsel; the [adverse action process](/adverse-action-process) page and the [FCRA](/fcra) overview are written for that conversation. A `cra` partner runs notices in its own process and uses only the order, dispute and adjudication endpoints here.

An FCRA order is a consumer report, which is why it requires an approved partner, a registered end user with permissible purposes, a consent attestation and a use location. Python and Node.js SDKs ship with the first registry release; see [SDKs](/sdk). Until then the raw HTTP calls below are the integration, and the [sandbox](/sandbox) runs the same flows on an `os_test_` key, 60 calls a minute and 2,000 a day, with nothing billed.

## Before adverse action: the FCRA order lifecycle

Adverse action hangs off a **complete order**, so the lifecycle comes first. Register the end user once with `POST /v1/fcra/end-users` and its `permissiblePurposes`. Then place the order with `POST /v1/fcra/orders` and an `Idempotency-Key` header: `endUserId`, `permissiblePurpose`, `consent { obtainedAt, method, documentRef }`, `subject { firstName, lastName, dob, aliases[], addresses[] }`, `useLocation { state }`, `liveScope`, `searches[]`, `clientReference` and `callbackUrl`. The answer is `202` at `status: "received"`; poll `GET /v1/fcra/orders/{order_id}` or wait for `order.completed` until `status: "complete"`. The [creating orders guide](/docs/guides/fcra/creating-orders) walks every field.

The `Idempotency-Key` (200 characters or fewer) matters because the order is billed: a replay with the same body within 24 hours returns the original order with `Idempotent-Replayed: true`; a different body is `409 idempotency_conflict`.

Every order carries `consumerPortal { url, accessCode }`. With the code, their last name and date of birth, the consumer can see exactly what was furnished and dispute any item without contacting you first; the pre-adverse notice carries the code so they can do that before the post-adverse notice goes. The [consumer portal reference](/docs/fcra/consumer-portal) describes what they see.

The notices also need an address. An order from the hosted consent flow carries the consumer's e-mail; for an order placed through the API, set it with `PATCH /v1/fcra/orders/{order_id}/consumer` (`email`, `phone`, `copyRequested`, `locale`) before you start, or the start is `422 consumer_contact_required`.

## Read the adverse items, then start the pre-adverse action notice

`GET /v1/fcra/orders/{order_id}/adverse-items` returns every record the current report furnishes as an `items[]` entry with `id`, `type` (`sex_offender`, `criminal`, `healthcare`), `searchId` and a plain-language `text`, together with the `rule` the use location adds, or `null` when it adds none. The `rule` object carries `state`, `city`, `purpose`, `version`, `waitingBusinessDays`, `individualizedAssessment`, `citation`, `note` and `alsoApplies[]`. `individualizedAssessment: true` on the rule means the start will be refused without one.

```bash
curl https://sandbox.offendersearch.app/v1/fcra/orders/fo_ee0b34c5b52fd35b/adverse-items \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY"

# 200
{
  "orderId": "fo_ee0b34c5b52fd35b",
  "items": [
    {
      "id": "crim:IL-SANGAMON:2021CR004417",
      "type": "criminal",
      "searchId": "fs_9c03d7e1a4b6f258",
      "text": "Sangamon County Circuit Court, case 2021CR004417: one misdemeanor count, disposition guilty, 2021."
    }
  ],
  "rule": {
    "state": "IL",
    "city": null,
    "purpose": "employment",
    "version": "…",
    "waitingBusinessDays": 5,
    "individualizedAssessment": true,
    "citation": "…",
    "note": "…",
    "alsoApplies": []
  }
}
```

`POST /v1/fcra/orders/{order_id}/adverse-actions` starts the action. `adverseItemIds` (1–100 ids from the items list) is the only required key. `reasonText` (up to 4,000 characters) is your stated reason; `waitingDays` is 7 to 60 calendar days, default 7; `individualizedAssessment` is a boolean and `assessmentText` (up to 8,000 characters) is the written assessment where the rule requires one; `medium` is an optional map of delivery mediums.

**curl**
```bash
curl -X POST https://sandbox.offendersearch.app/v1/fcra/orders/fo_ee0b34c5b52fd35b/adverse-actions \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "adverseItemIds": ["crim:IL-SANGAMON:2021CR004417"],
    "reasonText": "The role requires unsupervised access to client funds.",
    "waitingDays": 7,
    "individualizedAssessment": true,
    "assessmentText": "Nature of the offense, time elapsed and the duties of the role were considered: …"
  }'
```

**Python**
```python
import os, requests

BASE = "https://sandbox.offendersearch.app"
H = {"X-API-Key": os.environ["OFFENDERSEARCH_API_KEY"]}      # os_test_… from the dashboard
order_id = "fo_ee0b34c5b52fd35b"                             # an order at status "complete"

# 1. What could the decision rest on? Every furnished record, plus the use location's rule.
items = requests.get(f"{BASE}/v1/fcra/orders/{order_id}/adverse-items", headers=H, timeout=30).json()
chosen = [i["id"] for i in items["items"] if i["type"] == "criminal"]
rule = items["rule"]                                         # None when the location adds no rule

# 2. Start the adverse action: the pre-adverse notice goes now; the post-adverse notice is scheduled.
body = {
    "adverseItemIds": chosen,
    "reasonText": "The role requires unsupervised access to client funds.",
    "waitingDays": 7,
    "individualizedAssessment": bool(rule and rule["individualizedAssessment"]),
    "assessmentText": "Nature of the offense, time elapsed and the duties of the role were considered: …",
}
r = requests.post(f"{BASE}/v1/fcra/orders/{order_id}/adverse-actions", headers=H, json=body, timeout=30)

if r.status_code == 201:
    aa = r.json()
    print(aa["id"], aa["status"], aa["preNoticeSentAt"], aa["postNoticeScheduledAt"])
else:
    detail = r.json().get("detail")
    if isinstance(detail, dict):                             # a coded FCRA refusal
        print(r.status_code, detail["code"], detail["message"])
    else:                                                    # a field-validation list, or a string
        print(r.status_code, detail)
```

**Node**
```javascript
// Node 18+ — fetch is built in
const BASE = "https://sandbox.offendersearch.app";
const H = { "X-API-Key": process.env.OFFENDERSEARCH_API_KEY, "Content-Type": "application/json" };
const orderId = "fo_ee0b34c5b52fd35b";                        // an order at status "complete"

// 1. What could the decision rest on? Every furnished record, plus the use location's rule.
const items = await (await fetch(`${BASE}/v1/fcra/orders/${orderId}/adverse-items`, { headers: H })).json();
const chosen = items.items.filter((i) => i.type === "criminal").map((i) => i.id);
const rule = items.rule;                                      // null when the location adds no rule

// 2. Start the adverse action: the pre-adverse notice goes now; the post-adverse notice is scheduled.
const res = await fetch(`${BASE}/v1/fcra/orders/${orderId}/adverse-actions`, {
  method: "POST",
  headers: H,
  body: JSON.stringify({
    adverseItemIds: chosen,
    reasonText: "The role requires unsupervised access to client funds.",
    waitingDays: 7,
    individualizedAssessment: Boolean(rule?.individualizedAssessment),
    assessmentText: "Nature of the offense, time elapsed and the duties of the role were considered: …",
  }),
});

if (res.status === 201) {
  const aa = await res.json();
  console.log(aa.id, aa.status, aa.preNoticeSentAt, aa.postNoticeScheduledAt);
} else {
  const { detail } = await res.json();
  if (detail && typeof detail === "object" && !Array.isArray(detail)) {
    console.error(res.status, detail.code, detail.message); // a coded FCRA refusal
  } else {
    console.error(res.status, detail);                      // a field-validation list, or a string
  }
}
```

The `201` is the adverse action at `status: "pending"`. `preNoticeSentAt` records the moment the pre-adverse notice was delivered; `postNoticeScheduledAt` is when the post-adverse notice will go; `postNoticeSentAt` is `null` until it does. The order's own `adjudication` becomes `pre_adverse_action` at the same time.

*201 Created*
```json
{
  "id": "fa_1b2c3d4e5f607182",
  "orderId": "fo_ee0b34c5b52fd35b",
  "status": "pending",
  "adverseItemIds": ["crim:IL-SANGAMON:2021CR004417"],
  "reasonText": "The role requires unsupervised access to client funds.",
  "waitingDays": 7,
  "rule": {
    "state": "IL", "city": null, "purpose": "employment", "version": "…",
    "waitingBusinessDays": 5, "individualizedAssessment": true,
    "citation": "…", "note": "…", "alsoApplies": []
  },
  "individualizedAssessment": true,
  "assessmentText": "Nature of the offense, time elapsed and the duties of the role were considered: …",
  "medium": {},
  "preNoticeSentAt": "2026-11-03T15:02:11Z",
  "postNoticeScheduledAt": "2026-11-10T15:02:11Z",
  "postNoticeSentAt": null,
  "pausedAt": null,
  "resumedAt": null,
  "cancelledAt": null,
  "cancelReason": null,
  "disputeId": null,
  "createdAt": "2026-11-03T15:02:10Z",
  "updatedAt": "2026-11-03T15:02:11Z"
}
```

A second start while an action is open is refused with `409 adverse_action_open`, so a retried request cannot send a second notice. The [adverse action reference](/docs/fcra/adverse-action) describes every field.

## The adverse action waiting period rule

The waiting period runs from the moment the pre-adverse notice is delivered, and its length is the **latest** of three things: your `waitingDays` (calendar days, default 7), the use location's rule in business days, and a floor of five business days that applies to every adverse action. A `waitingDays` below 7 is refused with `422 waiting_period_too_short` before anything is sent. You can lengthen the period, never shorten it.

`GET /v1/fcra/adverse-rules` lists every rule the engine applies, by state, city and purpose, with its `waitingBusinessDays`, whether a written `individualizedAssessment` is required, and a `citation`. Read it at build time to know which use locations need an assessment collected up front, and read the `rule` on each adverse-items answer at run time, because the rules are versioned.

```bash
curl https://sandbox.offendersearch.app/v1/fcra/adverse-rules \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY"

# 200
{
  "version": "…",
  "defaultWaitingDays": 7,
  "rules": [
    { "state": "IL", "city": null, "purpose": "employment", "waitingBusinessDays": 5,
      "individualizedAssessment": true, "citation": "…", "note": "…" },
    …
  ]
}
```

When the period has run, the post-adverse notice goes, `postNoticeSentAt` is set, `status` becomes `complete` and the order's `adjudication` becomes `post_adverse_action`; cancel before then and `adjudication` clears.

## Adverse action API error codes

Every refusal the FCRA API raises itself is `{ "detail": { "code", "message" } }`; a body that fails field validation returns the standard validation list instead, so guard the shape of `detail` as the code tabs above do. The [FCRA errors reference](/docs/fcra/errors) lists every code by endpoint.

| Status | Code | Meaning | What to do |
| --- | --- | --- | --- |
| `409` | `order_not_complete` | The order is not at `status: "complete"`. | Wait for `order.completed`, then start. |
| `409` | `adverse_action_open` | A `pending` or `paused` action already exists on this order. | Read it with `GET …/adverse-actions`; cancel it first if the decision changed. |
| `422` | `consumer_contact_required` | The order has no consumer e-mail to send the notice to. | `PATCH /v1/fcra/orders/{order_id}/consumer` with `email`, then start again. |
| `422` | `unknown_adverse_item` | An `adverseItemIds` entry is not in the current adverse-items list. | Re-read `GET …/adverse-items`; the report may have been revised. |
| `422` | `individualized_assessment_required` | The use location's rule requires a written assessment and the request sent none. | Send `individualizedAssessment: true` and `assessmentText`. |
| `422` | `waiting_period_too_short` | `waitingDays` is below 7. | Send 7 or more, or omit it for the default. |
| `502` | `pre_notice_not_delivered` | The mail provider did not accept the pre-adverse notice; no waiting period started and the action was cancelled with `cancelReason: "pre_notice_not_delivered"`. | Fix the address, then start again. `adverse_action.notice_not_delivered` also fires. |
| `409` | `not_pausable`, `not_paused`, `dispute_open`, `not_cancellable` | A pause, resume or cancel that the current state does not allow. | Read the action's `status` and `disputeId` first. |

Never retry a `4xx` as if it were transient; each code above names a fix. The `502` is not transient either: the engine has already cancelled the action, and the fix is the consumer contact.

## Disputes pause the adverse action clock: pause, resume, cancel

No waiting period runs while a dispute on the order is open. When a consumer disputes a furnished record, file it with `POST /v1/fcra/orders/{order_id}/disputes`: `recordIds` (1–100 furnished record ids), `reason` (`not_me`, `inaccurate`, `incomplete`, `outdated`, `other`), an optional `consumerStatement` and `receivedAt`. The `201` is the dispute at `status: "received"`; a dispute from the consumer portal is filed for you and reaches you as `dispute.created`. The [FCRA dispute API](/fcra-dispute-api) page and the [disputes reference](/docs/fcra/disputes) cover the reinvestigation.

A dispute opened while the action is `pending` pauses it with the time that was left, sets `pausedAt` and `disputeId`, and emits `adverse_action.paused`. An action started while a dispute is already open starts `paused`. Resuming is refused with `409 dispute_open` until every dispute on the order is resolved. When the last open dispute resolves `verified`, the action resumes on its own and `adverse_action.resumed` fires.

A dispute resolved `modified`, `deleted` or `unable_to_verify` changes what the report furnishes. The report is revised and the adverse action is **cancelled** with `cancelReason: "report_revised"`, because the notice no longer matches the report. If the decision still stands, read the adverse items again and start a new action.

```bash
# A dispute arrives from the consumer (or through the consumer portal, which files it for you)
curl -X POST https://sandbox.offendersearch.app/v1/fcra/orders/fo_ee0b34c5b52fd35b/disputes \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"recordIds": ["crim:IL-SANGAMON:2021CR004417"], "reason": "not_me",
       "consumerStatement": "This record belongs to a different person with the same name.",
       "receivedAt": "2026-11-04T09:12:00Z"}'
# 201 → the dispute, status "received"; the pending adverse action becomes "paused" with the time that was left

# Pause and resume by hand (resume is 409 dispute_open while any dispute on the order is open)
curl -X POST https://sandbox.offendersearch.app/v1/fcra/adverse-actions/fa_1b2c3d4e5f607182/pause \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY"
curl -X POST https://sandbox.offendersearch.app/v1/fcra/adverse-actions/fa_1b2c3d4e5f607182/resume \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY"

# Cancel: the decision changed, or the report was revised
curl -X POST https://sandbox.offendersearch.app/v1/fcra/adverse-actions/fa_1b2c3d4e5f607182/cancel \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"reason": "client_cancelled"}'
```

`POST /v1/fcra/adverse-actions/{aa_id}/pause` and `/resume` move the clock by hand, for a reason of your own; `/cancel` takes an optional `{ "reason" }` (default `client_cancelled`, up to 200 characters) and sets `cancelledAt` and `cancelReason`. Each answers the updated action, or the matching `409` when the state does not allow it.

## Notices, webhook events and adjudication after adverse action

Every consumer e-mail is recorded. `GET /v1/fcra/adverse-actions/{aa_id}/notices` lists the notices for one action and `GET /v1/fcra/orders/{order_id}/notices` every notice for the order: each has an `id`, a `kind` (`pre_adverse`, `post_adverse`, `report_copy`, `dispute_received`, `dispute_resolved`, …), `to`, `subject`, `status` (`queued`, `sent`, `failed`, `skipped`), `sentAt`, `error`, `templateVersion` and `createdAt`.

```bash
curl https://sandbox.offendersearch.app/v1/fcra/adverse-actions/fa_1b2c3d4e5f607182/notices \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY"

# 200
{
  "notices": [
    { "id": "fn_…", "kind": "pre_adverse", "to": "consumer@example.com", "subject": "…",
      "status": "sent", "sentAt": "2026-11-03T15:02:11Z", "error": null,
      "templateVersion": "…", "createdAt": "2026-11-03T15:02:10Z" }
  ]
}

# Record the decision once the action is complete (or on an order with no adverse action)
curl -X POST https://sandbox.offendersearch.app/v1/fcra/orders/fo_ee0b34c5b52fd35b/adjudication \
  -H "X-API-Key: $OFFENDERSEARCH_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"decision": "cleared"}'
# 200 → the order, with "adjudication" set; 409 order_not_complete | adverse_action_open
```

The adverse-action events arrive on the endpoint you registered with `PUT /v1/fcra/webhook-endpoint`, with `data` carrying `adverseActionId`, `orderId`, `status` and, where it applies, `reason`: `adverse_action.created`, `adverse_action.completed`, `adverse_action.paused`, `adverse_action.resumed`, `adverse_action.cancelled` and `adverse_action.notice_not_delivered`. `GET /v1/fcra/events` lists every event with its delivery state. The [webhook example](/docs/learn/webhook-example) has the receiver; the [FCRA webhooks reference](/docs/fcra/webhooks) has the catalogue.

*Routing the adverse-action events*
```javascript
// One receiver for every FCRA event — see /docs/learn/webhook-example for the verifier
switch (event.type) {
  case "order.completed":                   enqueueReadAdverseItems(event.data.orderId); break;
  case "adverse_action.created":            markPreNoticeSent(event.data.adverseActionId); break;
  case "adverse_action.paused":             holdDecision(event.data.adverseActionId, event.data.reason); break;
  case "adverse_action.resumed":            releaseHold(event.data.adverseActionId); break;
  case "adverse_action.completed":          markPostNoticeSent(event.data.adverseActionId); break;
  case "adverse_action.cancelled":          reopenDecision(event.data.orderId, event.data.reason); break;
  case "adverse_action.notice_not_delivered": pageCompliance(event.data.adverseActionId, event.data.reason); break;
  case "dispute.created":
  case "dispute.updated":                   enqueueFetchDispute(event.data.disputeId); break;
}
```

Finally, record the decision. `POST /v1/fcra/orders/{order_id}/adjudication` with `{ "decision": "engaged" }` or `{ "decision": "cleared" }` answers the order with `adjudication` set, and is refused with `409 order_not_complete` or `409 adverse_action_open` while an action is still pending or paused. Adjudication records the end user's decision with the order; it sends nothing to the consumer.

## Step by step: rehearse the adverse action flow on the sandbox

On the sandbox every test key is already a `direct` partner for every permissible purpose, and the subject's **last name** picks the outcome: **Clear**, **Records** (one furnished record per search), **Withheld**, **Unavailable**, **Dispute** (one furnished record whose dispute resolves `deleted` sixty seconds after you file it), **Excluded**, **Sanctioned** and **Pending**; any other name is Clear. Use `Pat Records`, `1980-01-01`, for an order with something to take adverse action on, and `Pat Dispute` to watch a dispute cancel the action. The sandbox sends no e-mail; the notices list shows what would have gone. The [FCRA sandbox reference](/docs/fcra/sandbox) lists every subject.

1. **Register an end user and place an order.** `POST /v1/fcra/end-users` with `permissiblePurposes: ["employment"]`, then `POST /v1/fcra/orders` with an `Idempotency-Key`, the subject Pat Records, 1980-01-01, `useLocation.state`, and `searches: [{ "type": "criminal" }]`. Poll `GET /v1/fcra/orders/{order_id}` until `status: "complete"`.
2. **Set the consumer contact.** `PATCH /v1/fcra/orders/{order_id}/consumer` with the consumer's `email`, so the notices have somewhere to go. Skipping this is `422 consumer_contact_required` at the next step.
3. **Read the adverse items and the rule.** `GET /v1/fcra/orders/{order_id}/adverse-items`. Keep the `id` of each record the decision rests on and read `rule.individualizedAssessment` and `rule.waitingBusinessDays`.
4. **Start the adverse action.** `POST /v1/fcra/orders/{order_id}/adverse-actions` with `adverseItemIds`, `reasonText`, `waitingDays` of at least 7, and `individualizedAssessment` plus `assessmentText` where the rule requires it. Store the `201` and its `postNoticeScheduledAt`.
5. **Handle the events.** Route the `adverse_action.*` events from your registered webhook endpoint and fetch the action by id for the detail.
6. **File a dispute and watch the clock.** `POST /v1/fcra/orders/{order_id}/disputes` on the furnished record; confirm the action is `paused`. On the subject Dispute, the resolution sixty seconds later revises the report and cancels the action with `cancelReason: "report_revised"`.
7. **Record the decision.** When the action is `complete` or `cancelled`, `POST /v1/fcra/orders/{order_id}/adjudication` with `decision: "engaged"` or `"cleared"`, and read `GET …/notices` for the record of what was sent.

## FCRA adverse action API questions engineers ask

### Does the API decide whether to take adverse action?

No. It sends the two notices, keeps the clock and records the decision you report; whether to act is the end user's decision, made with counsel.

### How is the waiting period calculated?

The latest of your `waitingDays` (7–60 calendar days), the use location's rule in business days from `GET /v1/fcra/adverse-rules`, and a five-business-day floor, counted from `preNoticeSentAt`. Below 7 is `422 waiting_period_too_short`.

### What is in the pre-adverse action notice?

The report access code for the consumer portal and the summary of rights, sent by the API as the consumer reporting agency, in the partner's branding for `platform` partners; the record is in `GET …/notices` with its `templateVersion`.

### What happens when the consumer disputes during the waiting period?

The action pauses with the time that was left and `adverse_action.paused` fires. It resumes on its own when the dispute resolves `verified`, and is cancelled with `cancelReason: "report_revised"` when the resolution changes the report.

### Can I run two adverse actions on one order?

Not at once: a second start while one is `pending` or `paused` is `409 adverse_action_open`. After a cancellation, for example on a revised report, you can start a new one.

## What this page does not cover

- Whether, when and how to take adverse action in your jurisdiction and for your purpose: your counsel, with the [adverse action process](/adverse-action-process) page as background.
- The hosted consent flow and invitations that collect the consumer's authorization and e-mail, and the partner application itself.
- Reinvestigation outcomes and report revisions in detail: the [disputes reference](/docs/fcra/disputes) and the [FCRA dispute API](/fcra-dispute-api).
- Continuous checks that re-run a consented order on an interval, and the standard non-FCRA search endpoints.

## Related

- [Employment screening API](https://offendersearch.app/employment-screening-api)
- [Background check API (the two-dataset hub)](https://offendersearch.app/background-check-api)
- [FCRA background checks (overview)](https://offendersearch.app/fcra)
- [FCRA Partner API](https://offendersearch.app/fcra-api)
- [Adverse action reference](https://offendersearch.app/docs/fcra/adverse-action)
- [Adverse action process](https://offendersearch.app/adverse-action-process)
- [FCRA dispute API](https://offendersearch.app/fcra-dispute-api)
- [Creating orders (guide)](https://offendersearch.app/docs/guides/fcra/creating-orders)
- [Disputes (guide)](https://offendersearch.app/docs/guides/fcra/disputes)
- [FCRA webhooks and events](https://offendersearch.app/docs/fcra/webhooks)
- [FCRA errors](https://offendersearch.app/docs/fcra/errors)
- [FCRA sandbox](https://offendersearch.app/docs/fcra/sandbox)
- [Webhook example: receive, verify and retry](https://offendersearch.app/docs/learn/webhook-example)
- [Learn APIs by example](https://offendersearch.app/docs/learn)
