# Quickstart

> Make your first nationwide sex-offender search in cURL, Node or Python, and learn the four response fields to read on every call: status, counts, matchState.

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

## Authenticate

Every request carries your secret key in the `X-API-Key` header. Keys are created, rotated and revoked from the dashboard, and a key's secret is shown in full only once, at creation.

```bash
export OFFENDERSEARCH_KEY="os_live_9f2a…"
```

## Your first search

One authenticated call covers every jurisdiction. Passing `jurisdictions: null` — or omitting it — searches the full dataset, and the response is one scored, de-duplicated, source-tagged result set.

**cURL**

```bash
curl https://api.offendersearch.app/v1/search \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "firstName": "John",
      "lastName": "Doe",
      "dob": "1980-04-12"
    },
    "jurisdictions": null,
    "freshness": "daily",
    "match": "balanced",
    "include": ["stateData"]
  }'
```

**Node**

```javascript
const res = await fetch("https://api.offendersearch.app/v1/search", {
  method: "POST",
  headers: {
    "X-API-Key": process.env.OFFENDERSEARCH_KEY,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    query: { firstName: "John", lastName: "Doe", dob: "1980-04-12" },
    jurisdictions: null,     // null = search the full dataset
    freshness: "daily",
    match: "balanced",
    include: ["stateData"],  // extended per-state detail
  }),
});

const { records, sourceStatus, counts, status } = await res.json();
// status is "complete", or "partial" when a deadlineMs bound is set
for (const r of records) {
  console.log(r.matchConfidence, r.name.full, r.source.jurisdiction);
}
```

**Python**

```python
import os, requests

resp = requests.post(
    "https://api.offendersearch.app/v1/search",
    headers={"X-API-Key": os.environ["OFFENDERSEARCH_KEY"]},
    json={
        "query": {"firstName": "John", "lastName": "Doe", "dob": "1980-04-12"},
        "jurisdictions": None,   # None = search the full dataset
        "freshness": "daily",
        "match": "balanced",
        "include": ["stateData"],
    },
    timeout=130,
)
data = resp.json()
print(data["status"], data["counts"])
for r in data["records"]:
    print(r["matchConfidence"], r["name"]["full"], r["source"]["jurisdiction"])
```

## Read four things off every response

1. **`status`** — `"complete"` or `"partial"`. `"partial"` means a `deadlineMs` bound you set was reached before every source completed.
2. **`counts.sourcesComplete` vs `counts.sourcesQueried`** — how much of the search finished. An equal pair is a closed answer.
3. **`counts.records`** — the total number of matches *before* any page slice, so it does not change as you page.
4. **`matchState` and `matchedName`, per record** — the labelled strength of the identity match, and whether the registered name or an alias produced the hit.

Every response also carries `sourceStatus[]`, one entry per jurisdiction the request touched, with that jurisdiction’s own status and freshness — so an incomplete search is always labelled as one and never returned as a silently short list.

```json
{
  "status": "complete",
  "counts": {
    "records": 3,
    "sourcesQueried": 58,
    "sourcesComplete": 58,
    "sourcesIncomplete": 0,
    "sourcesSkippedByScope": 0
  },
  "records": [
    {
      "matchState": "dob_match",
      "matchedName": { "value": "John A. Doe", "type": "legal" },
      "matchConfidence": 1.0
    }
  ]
}
```

## Where to go next

- [POST /v1/search](https://offendersearch.app/docs/search.md) — every parameter, and nine worked query shapes.
- [The Record object](https://offendersearch.app/docs/record-object.md) — one normalized schema across all 58 jurisdictions.
- [Result completeness](https://offendersearch.app/docs/result-completeness.md) — how counts, sourceStatus and incompleteReason fit together.
- [Async & webhooks](https://offendersearch.app/docs/async-and-webhooks.md) and [Batch & CSV](https://offendersearch.app/docs/batch.md) — for volume work.
- [OpenAPI JSON](https://offendersearch.app/openapi.json) / [YAML](https://offendersearch.app/openapi.yaml) — generate a typed client rather than hand-writing one.

---

## Related

- Next: [Authentication, keys & security](https://offendersearch.app/docs/authentication.md)
- Index: [Offendersearch API documentation](https://offendersearch.app/docs.md)
