# Agents & tool calling

> A tool definition per question, structured results the agent reads field by field, and async jobs that fit an agent loop.

- **HTML:** https://offendersearch.app/docs/ai/agents
- **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

## Tool definitions

```json
[
  {
    "name": "registry_search",
    "description": "Search all 58 US sex offender registries for a person. Returns candidates, never a confirmed identity. Read status and sourceStatus before records.",
    "input_schema": {
      "type": "object",
      "properties": {
        "firstName": { "type": "string" },
        "lastName":  { "type": "string" },
        "dob":       { "type": "string", "description": "YYYY-MM-DD, when known" },
        "state":     { "type": "string", "description": "Two-letter code to scope the search; omit for national" }
      },
      "required": ["lastName"]
    }
  },
  {
    "name": "criminal_search",
    "description": "Search criminal records (bookings, inmates, court cases, warrants). A charge is not a conviction: read disposition and currentStatus.",
    "input_schema": {
      "type": "object",
      "properties": {
        "firstName": { "type": "string" },
        "lastName":  { "type": "string" },
        "dob":       { "type": "string" },
        "state":     { "type": "string" }
      },
      "required": ["lastName"]
    }
  }
]
```

Each tool's handler is a plain call to `POST /v1/search` or `POST /v1/criminal/search` with the `X-API-Key` header and the documented retry policy; see the [recipes](/docs/ai/recipes).

## Working with structured results

Return the envelope to the agent reduced to what it needs, with the completeness signals kept:

```json
{
  "status": "complete",
  "searched": 58, "incomplete": [],
  "candidates": [
    { "name": "Jordan Rivera", "matchConfidence": 0.96, "matchState": "dob_match",
      "registrationState": "TX", "recordUrl": "https://…" }
  ],
  "note": "Candidates are possible matches, not confirmed identities."
}
```

- Keep `status` and the incomplete-source list in every tool result; an agent that only sees `candidates: []` will say "clear" when it should say "could not complete".
- Pass through `matchConfidence` and `matchState`; let the agent tier, not decide.
- For criminal results, include `recordType`, `currentStatus` and each charge's `disposition`; strip nothing the agent needs to avoid calling an arrest a conviction.

## Async jobs in an agent loop

1. When a tool call answers with a pending job, return the `searchId` and the poll path to the agent as a pending result. The key that carries the path differs per entry point: `resultsUrl` on `POST /v1/searches` (`202`), `poll` on a Sex Offender live search that went async (a `200` whose `status` is `pending`) and on a criminal `202`, `pollUrl` on a `live.evidence` `202`.
2. Offer a second tool, `poll_search(path)`, that returns `pending` while the job runs and the finished envelope when done (for a criminal job the envelope is under `result`; the poll answers `202` while pending). The agent calls it every few seconds; cap the attempts.
3. Persist the `searchId` outside the conversation: a dropped agent run must not resubmit (every accepted job is billed).

## Interpretation rules in the system prompt

```plain
When reporting OffenderSearch results:
- Say what was searched and whether it completed (status, incomplete sources).
- Describe candidates as possible matches with their confidence and how they matched. Never state identity.
- For criminal records, say what the record is (booking, custody, court case, warrant) and the charge's disposition.
  Never call an arrest or a charge a conviction.
- If records is empty and status is complete, say no record was found in the sources searched — not that the person has no record.
- Do not use these results for employment, housing, credit or insurance decisions.
```

## Related

- [MCP server](https://offendersearch.app/docs/ai/mcp)
- [Recipes](https://offendersearch.app/docs/ai/recipes)
- [Data model rules](https://offendersearch.app/docs/platform/data-model)
