# Async live jobs & polling

> A live search of more than two sources fans out to an async job: a 202 with a searchId, then poll GET /v1/criminal/searches/{searchId} until it returns 200.

- **HTML:** https://offendersearch.app/docs/criminal/async
- **Base URL:** https://api.offendersearch.app
- **Authentication:** `X-API-Key` request header
- **OpenAPI:** https://offendersearch.app/openapi.json · https://offendersearch.app/openapi.yaml
- **Criminal reference as markdown:** https://offendersearch.app/docs/criminal.md

## When a live search goes async

A live check of **≤2 sync-eligible sources** runs synchronously — one `200`, results inline. **More than two sources, or any source that needs heavier handling**, runs asynchronously: a `202` with a `searchId` and a `poll` URL. Both paths run the same live checks and return the same envelope — the only difference is whether you hold the connection open or collect the result later.

Cached searches and synchronous live searches never produce a job to poll.

```json
// The 202 body
{
  "searchId": "crs_9a3b71c0e28d4f6a5b12",
  "status": "pending",
  "mode": "async",
  "liveSourcesRequested": 4,
  "poll": "/v1/criminal/searches/crs_9a3b71c0e28d4f6a5b12",
  "reason": "4 live sources (> 2 or not sync-eligible) — running asynchronously"
}
```

## GET /v1/criminal/searches/{searchId}

**Authentication:** `X-API-Key`. A search is only visible to the account that created it — another account's `searchId` returns `404`. The poll returns the stored job record; the shape depends on job state.

| `status` | Meaning | Action | HTTP |
| --- | --- | --- | --- |
| `pending` | Queued / running | Poll again | `202` |
| `complete` | Done | Read `result` | `200` |

When it completes, `result` carries the **full response envelope** — read `result.counts.sourcesIncomplete` and `result.warnings` exactly as you would for a synchronous live search. There is no separate `error` job state: a live source that fails is reported *inside* the completed `result` as an incomplete `sourceStatus[]` row.

```bash
# submit, then poll until 200
SID=$(curl -s -X POST "https://api.offendersearch.app/v1/criminal/search" \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" -H "Content-Type: application/json" \
  -d '{"query":{"lastName":"Hamilton","state":"TX"},
       "live":{"jurisdictions":["TX-DALLAS","TX-HARRIS","TX-DOC"]}}' | jq -r '.searchId')

while :; do
  code=$(curl -s -o /tmp/job.json -w '%{http_code}' \
    "https://api.offendersearch.app/v1/criminal/searches/$SID" -H "X-API-Key: $OFFENDERSEARCH_KEY")
  [ "$code" = "200" ] && break
  sleep 2
done
jq '.result.counts' /tmp/job.json
```

---

## Related

- Previous: [Resolve a website to a code](https://offendersearch.app/docs/criminal/resolve.md)
- Next: [Batch & CSV search](https://offendersearch.app/docs/criminal/batch.md)
- Index: [Criminal Search API reference](https://offendersearch.app/docs/criminal.md)
