Fetch an async search's status + results

Returns the job's current state. While pending/running the body carries empty records/sourceStatus and a zeroed counts; on error it carries an error string; when finished it is the full SearchResponse.

get/v1/searches/{searchId}

Request

  • Base URL: https://api.offendersearch.app
  • URL: https://api.offendersearch.app/v1/searches/{searchId}
  • Auth: API key in header X-API-Key

Path parameters

searchIdstring required

Response

Search status + results

searchIdstring
status'complete' | 'partial' | 'running' | 'pending' | 'error'

complete=every registry was searched to the end; partial=at least one was NOT (deadline or candidate-cap truncation) or a source errored; pending/running=async job in flight; error=async job failed. ★ partial is NEVER about how old the data is — a snapshot's age does not affect status. When status is partial, read counts.sourcesIncomplete before treating an empty records as an answer (API-CONTRACT.md §5.0).

elapsedMsinteger

Server processing time for the search, in ms.

warningsstring[]

Plain-language notices about anything that SHORTENED this answer — one sentence per condition, naming the registries. ALWAYS present; [] means nothing did, so warnings == [] is a valid completeness check and the cheapest one available. The loudest entry, always first when present, begins "INCOMPLETE SEARCH:" and means one or more registries could not be searched to the end. Freshness never produces a warning.

pageinteger

The page you asked for, echoed back. 1 when the caller did not paginate. ★ CHANGED 2026-08-05: a query.page past totalPages now returns an EMPTY records array instead of being CLAMPED to the last page. The clamp re-served the last page under every page number you sent (pages 5, 6 and 50 of a 4-page result all carried page 4's records), so the canonical while records: page += 1 client NEVER TERMINATED — and /v1/search meters one billable call per request, so every spin was charged. counts.records and totalPages are unchanged, and walking 1..totalPages returns exactly the same records it always did.

perPageinteger

Page size actually applied. NOTE: when the caller did NOT paginate this equals the TOTAL record count (the whole set was returned in one page), not the 20 default.

totalPagesinteger

Total pages at this perPage. 1 when the caller did not paginate.

errorstring nullable

Set only when an async job failed (status=error).

cappedboolean

TRUE when the search matched more records than one response may carry and the list was cut to cappedLimit. Always present, false on virtually every search. Branch on this field, not on prose and not by comparing counts. When it is true, counts.records is still the true number of people who matched — you are holding the first cappedLimit of them. A capped response is not an error and not a partial: every record in it is a real match, and the registries were all searched to the end. To see the rest, narrow the search (see cappedReason).

cappedReason'responseLimit' nullable

Why the response was capped; null when capped is false. responseLimit is the fixed ceiling on how many records one response may carry. It exists so that a whole-registry sweep cannot be issued as a single call; it is not a limit on how much of the corpus you may reach, only on how much of it arrives at once.

cappedLimitinteger nullable

The ceiling that was applied (currently 4000), or null when capped is false. Read it from the response rather than hard-coding it.

cappedOmittedSourcesstring[]

The registries whose records are ENTIRELY absent from this response because of the cap — sorted, and [] on every uncapped search (always present). capped / cappedLimit tell you HOW MANY records did not fit; this tells you WHOSE. Records are ordered by match confidence rather than by registry, so a registry's whole contribution can fall below the line together. ★ NO PAGE OF THIS RESPONSE REACHES A REGISTRY NAMED HERE: totalPages counts only the cappedLimit records carried, so walking every page does not recover them. Their sourceStatus[].matched counts are still exact. The recovery is to NARROW the search until the answer fits — add state, city, zipCode, firstName or dob/age, or name registries directly with jurisdictions. Example: {"lastName":"Smith"} matches 11,338 people nationally and this list names the registries holding the rest; the same query with "state": "GA" returns 894 records with capped: false.

omittedFieldsstring[]

The record FIELDS this response does not carry because you did not request them — ["stateData"] on a default search, [] when include (or compat mode=extensive) was sent. Always present. ★ READ IT BEFORE TREATING A NULL NESTED BLOCK AS A FACT ABOUT THE REGISTRANT. stateData: null is byte-identical for "you did not ask" and "this registry publishes none"; this key is the only thing that separates them. images and offenses are never omitted, so [] there is always a fact about the registrant.

Example response

{
  "searchId": "srch_9f2c1a7b",
  "usage": {
    "liveChecks": 1,
    "liveChargeUsd": 0.02,
    "display": "1 live checks · $0.02"
  },
  "meterEvents": [
    {
      "kind": "live_source",
      "quantity": 1,
      "chargeCeilingUsd": 2
    }
  ],
  "sourceStatus": [
    {
      "source": "IL"
    }
  ],
  "records": [
    {
      "recordId": "rec_ab12cd34ef56gh78ij90",
      "uuid": "IL:X24A0926",
      "matchDetail": {
        "strategies": {
          "lastName": "exact",
          "firstName": "nickname"
        },
        "fieldsPresent": [
          "lastName",
          "firstName",
          "dob"
        ]
      },
      "nicknames": [
        "john",
        "johnny",
        "jack"
      ],
      "birthYear": 1976,
      "marks": "Tattooed Arm, left upper; Scar on Chest; Pierced ear, left",
      "addresses": [
        {
          "type": "residence"
        }
      ],
      "registrationState": "FL",
      "addressStates": [
        "FL",
        "GA"
      ],
      "stateData": {
        "shoeSize": "10½",
        "build": "Large",
        "shoeWidth": "D"
      },
      "source": {
        "jurisdiction": "IL"
      },
      "sources": [
        {
          "jurisdiction": "IL"
        }
      ]
    }
  ]
}

Changes

No recorded changes to this operation across all 1 revision of this API.