# Arrest records API example: look up a jail booking by name

> One request to the criminal records API, one jail_booking record back: what an arrest record carries, how to tell a current booking from an old one, how to follow it to court, and how to be told when a new booking appears.

- **HTML:** https://offendersearch.app/docs/learn/arrest-records-api-example
- **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

## An arrest record is not a conviction: what the arrest records API returns

A jail booking is an **arrest-stage** record. It says that a person was booked into a county jail on a date, by an agency, on one or more charges. It does not say that a court found them guilty, and most of the time no court has looked at it yet. The [criminal records API](/criminal-records-api) returns a booking as a record whose `recordType` is `jail_booking`, with the charge at booking in `cases[].charges[]` and the `disposition` of that charge empty. An empty disposition is the whole point: nothing has been decided. A conviction is a different record kind, `court_case`, with a filled `disposition`. Say this in your product copy, in your reviewer UI and in your logs, because the single most common misuse of arrest data is reading a booking as a verdict.

This page walks through one booking end to end on the [sandbox](/sandbox), where the "Arrest Only" person with date of birth 1992-04-20 returns exactly one `jail_booking` record. The same request on `https://api.offendersearch.app` with an `os_live_` key searches the production dataset at $0.15 a call, $0.11 past 2,000 calls in a month, 25 free calls, no card. Python and Node.js SDKs ship with the first registry release; see [SDKs](/sdk). Until then the raw HTTP calls below are the integration.

| Record kind | `recordType` | What it tells you | Decides a conviction? |
| --- | --- | --- | --- |
| Jail booking | `jail_booking` | Arrested and booked; a charge at booking; `booking.bookingDate`, `booking.arrestingAgency` | No. `disposition` is `""` |
| Court case | `court_case` | Charges with `disposition` and `dispositionDate`; a `sentence` when there is one | Yes, per charge |
| Prison or DOC record | `doc_inmate` | Custody status, `custodyFacility`, release dates | Implies a sentence; read the case for the charge |
| Warrant | `warrant` | `currentStatus` `active_warrant`; the warrant facts | No |

## Look up arrest records with the API: the request

The endpoint is `POST /v1/criminal/search`. `query.lastName` is required; without it the API answers `422` with `error.code` `guard_unbounded_query` rather than return a lottery of namesakes. Add `query.firstName`, and `query.dob` as `YYYY-MM-DD` when you have it: a full date of birth is what turns a name match into an identification. In production you will almost always add `query.state`, and `query.county` when the roster you care about is one county. `page` and `perPage` (1 to 200) sit at the **top level** of the body, not inside `query`. The call is cached by default, which is what you want for an arrest lookup; the [cached vs live](/docs/guides/criminal/cached-vs-live) guide covers when to add a `live` block.

**curl**
```bash
# Arrest records by name, on the sandbox (free). lastName is required.
curl -X POST https://sandbox.offendersearch.app/v1/criminal/search \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" \
  -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Arrest", "lastName": "Only", "dob": "1992-04-20"}, "page": 1, "perPage": 50}'
```

**Python**
```python
# pip install requests
import os, requests

KEY = os.environ["OFFENDERSEARCH_TEST_KEY"]      # os_test_… from the dashboard
HOST = "https://sandbox.offendersearch.app"

def arrest_search(first, last, dob=None, state=None, county=None):
    query = {"firstName": first, "lastName": last}   # lastName is required (422 guard_unbounded_query without it)
    if dob:
        query["dob"] = dob                            # YYYY-MM-DD
    if state:
        query["state"] = state                        # USPS code, e.g. "TX"
    if county:
        query["county"] = county
    r = requests.post(f"{HOST}/v1/criminal/search", headers={"X-API-Key": KEY},
                      json={"query": query, "page": 1, "perPage": 50}, timeout=130)
    if r.status_code == 422:
        raise ValueError(r.json()["error"]["code"])   # guard_unbounded_query, invalid_dob, ambiguous_location …
    r.raise_for_status()
    return r.json()

body = arrest_search("Arrest", "Only", dob="1992-04-20")
print(body["status"], body["counts"]["records"])
for rec in body["records"]:
    if rec["recordType"] != "jail_booking":
        continue
    b = rec["booking"]
    print(rec["matchState"], rec["currentStatus"], b["bookingDate"], b["arrestingAgency"])
    for case in rec["cases"]:
        for ch in case["charges"]:
            # at the arrest stage disposition is "" — nothing has been decided yet
            print("  charge:", ch["description"], ch["severity"], "disposition:", repr(ch["disposition"]))
    print("  current:", rec["lifecycle"]["presentInLastSweep"], "dropped:", rec["lifecycle"]["droppedOut"])
print(body["legal"]["notice"])
```

**Node**
```javascript
// Node 18+: fetch is built in
const KEY = process.env.OFFENDERSEARCH_TEST_KEY;   // os_test_… from the dashboard
const HOST = "https://sandbox.offendersearch.app";

async function arrestSearch({ firstName, lastName, dob, state, county }) {
  const query = { firstName, lastName };            // lastName is required (422 guard_unbounded_query without it)
  if (dob) query.dob = dob;                         // YYYY-MM-DD
  if (state) query.state = state;                   // USPS code, e.g. "TX"
  if (county) query.county = county;
  const res = await fetch(`${HOST}/v1/criminal/search`, {
    method: "POST",
    headers: { "X-API-Key": KEY, "Content-Type": "application/json" },
    body: JSON.stringify({ query, page: 1, perPage: 50 }),
  });
  if (res.status === 422) throw new Error((await res.json()).error.code);   // guard_unbounded_query, invalid_dob …
  if (!res.ok) throw new Error(`${res.status} ${await res.text()}`);
  return res.json();
}

const body = await arrestSearch({ firstName: "Arrest", lastName: "Only", dob: "1992-04-20" });
console.log(body.status, body.counts.records);
for (const rec of body.records) {
  if (rec.recordType !== "jail_booking") continue;
  const b = rec.booking;
  console.log(rec.matchState, rec.currentStatus, b.bookingDate, b.arrestingAgency);
  for (const c of rec.cases) for (const ch of c.charges)
    console.log("  charge:", ch.description, ch.severity, "disposition:", JSON.stringify(ch.disposition)); // "" at arrest stage
  console.log("  current:", rec.lifecycle.presentInLastSweep, "dropped:", rec.lifecycle.droppedOut);
}
console.log(body.legal.notice);
```

The response is the standard criminal envelope: `status` ("complete" or "partial"), `counts` (`records`, `recordsReturned`, `sourcesQueried`, `sourcesComplete`, `sourcesIncomplete`, `sourcesOnFile`, `sourcesPending`), `warnings[]`, `page`, `perPage`, `totalPages`, `sourceStatus[]`, `legal.notice`, `searchId` and `records[]`. Read `status` first: a "partial" answer with `sourcesIncomplete` above zero is a lower bound, not a clear, and the [partial results](/docs/guides/criminal/partial-results) guide says what to do with it. The `422` with a coded `error` object is the one error you will meet while developing; `/docs/criminal/errors` lists the vocabulary.

## Reading a jail_booking arrest record: the keys that matter

Every criminal record carries the same keys, and every key is always present ("" / `null` / `[]` when the source publishes nothing), so a booking and a court case are read by the same code. The [criminal record object](/docs/criminal/record-object) reference lists them all; the subset an arrest lookup reads is below, in the shape the sandbox returns for "Arrest Only".

*The jail_booking record, from the sandbox (trimmed)*
```json
// The jail_booking record the sandbox returns for Arrest Only (captured 2026-11-03 from the
// sandbox code), trimmed to the keys this page reads. Every key is always present in the full
// response; "" / null / [] when the source publishes nothing.
{
  "recordType": "jail_booking",
  "name": {
    "first": "Arrest",
    "middle": "",
    "last": "Only",
    "suffix": ""
  },
  "dob": {
    "date": "1992-04-20",
    "birthYear": 1992,
    "birthMonth": 4,
    "age": null,
    "ageAsOf": null,
    "dobPrecision": "full"
  },
  "state": "FL",
  "county": "Sample",
  "currentStatus": "released",
  "currentlyIncarcerated": false,
  "matchState": "dob_match",
  "matchConfidence": 0.99,
  "unverified": false,
  "booking": {
    "bookingNumber": "SBX-B-2026-0101",
    "bookingDate": "2026-09-30",
    "arrestDate": "2026-09-30",
    "arrestingAgency": "Sample County Sheriff's Office",
    "heldForAgency": "Sampleville Police Department",
    "housing": "Pod B-12",
    "bond": {
      "amount": 2500.0,
      "text": "$2,500.00",
      "type": "Cash or surety",
      "company": null
    },
    "holds": [
      "Sample County probation hold"
    ]
  },
  "cases": [
    {
      "caseNumber": "SBX-BK-2026-004512",
      "court": "",
      "county": "Sample",
      "state": "FL",
      "level": "county",
      "filedDate": "2026-08-03",
      "status": "Booked",
      "charges": [
        {
          "statute": "SAMPLE 316.193",
          "description": "Driving under the influence",
          "severity": "misdemeanor",
          "disposition": "",
          "dispositionDate": null,
          "offenseDate": "2026-08-03",
          "plea": "",
          "counts": 1
        }
      ]
    }
  ],
  "sources": [
    {
      "code": "FL-SAMPLE",
      "level": "county",
      "recordUrl": "https://offendersearch.app/docs/sandbox",
      "lastCheckedAt": "2026-10-01T00:00:00+00:00",
      "live": false
    }
  ],
  "lifecycle": {
    "firstSeen": "2026-08-03T00:00:00+00:00",
    "lastSeen": "2026-10-01T00:00:00+00:00",
    "presentInLastSweep": true,
    "droppedOut": false,
    "dropInterpretation": "unknown"
  },
  "mugshotUrl": "https://sandbox.offendersearch.app/v1/sandbox/photos/8.svg",
  "physical": {
    "height": "5'10"",
    "weight": "180 lbs",
    "hairColor": "Brown",
    "eyeColor": "Blue",
    "build": "Medium",
    "marks": "Tattoo, left forearm (sample)",
    "heightInches": 70,
    "weightPounds": 180
  },
  "identifiers": {}
}
```

| Key | Read it for |
| --- | --- |
| `recordType` | `jail_booking`. Branch on it before anything else; a court case for the same person is a separate record. |
| `name`, `dob` | Who the source says this is. `dob.date` is the full date when published; `dob.dobPrecision` says how precise it is. |
| `state`, `county` | Where the booking happened. The county is the jail, not the person's address. |
| `currentStatus` | Open text, the source's own status passed through: usually `in_custody`, `booked` or `released` on a booking, `on_probation`, `active_warrant` or `excluded` on other kinds, and `unknown` when the source published none. Branch on `lifecycle`, not on this string. |
| `booking` | `bookingNumber`, `bookingDate`, `arrestDate`, `arrestingAgency`. The facts of the arrest itself. |
| `cases[].charges[]` | The charge at booking: `statute`, `description`, `severity`, `offenseDate`, `counts`. `disposition` is `""` and `dispositionDate` is `null`: nothing decided. |
| `sources[]` | `code` (the jurisdiction, e.g. a county code), `level` `"county"` for a jail roster, `recordUrl` (the official page to cite), `lastCheckedAt`. |
| `lifecycle` | `firstSeen`, `lastSeen`, `presentInLastSweep`, `droppedOut`: whether the booking is current. Next section. |
| `mugshotUrl` | The booking photo when the source publishes one. Never show it as proof of identity (see below). |
| `physical`, `identifiers` | Height, weight, hair and eye colour; `stateId` and `fbiNumber` when published. Useful to a reviewer comparing two records, never a match on their own. |
| `matchState`, `matchConfidence`, `unverified` | Whether the record is the person you asked about. `dob_match` is the only automatic identification. |

### Current or historical: reading lifecycle

A booking that is still on the jail roster and a booking from three years ago look alike in `cases[]`: both have a charge and no disposition. `lifecycle` is what separates them. `presentInLastSweep` true and `droppedOut` false means the person was on the roster the last time the source was checked, and `lastSeen` tells you when that was. `droppedOut` true means they were on it once (`firstSeen`) and are not any more; for a jail roster the most likely reason is release, and `lastSeen` is the last date they were listed. A dropped-out booking is still returned, flagged, because a person who was arrested in the past is still an arrest record. Show the dates, not just a label: "booked 2026-03-02, last on the roster 2026-03-09" is what a reviewer can act on.

## From arrest to court case: following the charge to a disposition

A booking tells you a charge was filed at arrest. What happened next lives in a `court_case` record for the same person, when the county court publishes it and the case has been filed. Search the same `query` (same name, same `dob`, the booking's `state` and `county`) and read the records whose `recordType` is `court_case`. Match a case to the booking by `cases[].caseNumber` when the booking carries one, otherwise by the charge `description` and `offenseDate`, which should agree with the booking's `arrestDate` or sit a few days before it.

Then read each charge, not the case. `cases[].charges[].disposition` decides it: "Guilty" (or a jurisdiction's equivalent) with a `dispositionDate` is a conviction on that charge; "Dismissed", "Nolle prosequi", "Acquitted" and the like are not; `""` means still pending. One case can carry both, as the sandbox's "Felony Record" person shows with a Guilty burglary and a Dismissed theft in the same `cases[0]`. The [arrests, charges and convictions](/docs/guides/criminal/arrests-charges-convictions) guide lists the disposition vocabulary you will meet across counties, and the [court records API tutorial](/docs/learn/court-records-api-tutorial) walks the court side of the same person. If no `court_case` comes back, say "no court record found in the sources on file", not "charges dropped": `counts.sourcesPending` tells you whether a court source for that county is not yet available.

## Scope an arrest records API search by county, sort it and check coverage

Arrest records are county records. `query.state` narrows a search to one state; `query.county` narrows it to one county within that state. Send the county as a name ("Bexar"), and send `state` with it, because the same county name exists in several states and the API refuses an ambiguous place with `422 ambiguous_location`. With a `state` in the query you can also ask for `include: ["coverage"]`, which adds `coverage.sources[]`: every source in scope for that state or county, each with `code`, `name`, `recordType`, `scope` ("county", "state" or "federal") and `status` ("live" or "pending"). That is how you show a reviewer which county jail rosters the answer actually covers. `sort: "offenseDate"` puts the record with the newest offense date first when the request also sends `filters` or `locations`; a plain search keeps relevance order.

*County scope, coverage and the public catalog*
```bash
# Scope to one county and ask which county sources were in scope (production shape; synthetic subject)
curl -X POST https://sandbox.offendersearch.app/v1/criminal/search \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" -H "Content-Type: application/json" \
  -d '{"query": {"firstName": "Jordan", "lastName": "Rivera", "dob": "1988-04-12", "state": "TX", "county": "Bexar"},
       "include": ["coverage"], "sort": "offenseDate", "page": 1, "perPage": 50}'

# 200 — complete, zero records for this synthetic name; coverage.sources[] lists every source in scope
{ "status": "complete",
  "counts": { "records": 0, "recordsReturned": 0, "sourcesOnFile": …, "sourcesPending": … },
  "coverage": { "sources": [ { "code": "TX-BEXAR", "name": "Bexar County, TX — County Jail Roster",
                               "recordType": "County jail roster", "scope": "county", "status": "live" } ] },
  "records": [] }

# The public catalog, no key: one row per jurisdiction with its scope, status and last-success time
curl https://api.offendersearch.app/v1/criminal/sources
# { "coverage": { "jurisdictions_total": … }, "sources": [ { "id": "TX-BEXAR", "scope": "county", "status": "live",
#   "health": { "lastSuccessAt": "…", "ageSeconds": … } } ], "count": … }
```

Before you build for a county, check it is covered: `GET /v1/criminal/sources` needs no key and lists one row per jurisdiction with `id`, `name`, `scope`, `status` and a `health` block with `lastSuccessAt`. The dataset is continuously updated; `health.ageSeconds` is the honest number for how current one jurisdiction is. The [coverage](/docs/criminal/coverage) reference covers the catalog and the place fields in full; the [jail roster](/jail-roster) and [arrest records](/arrest-records) pages describe what the roster sources carry.

## Be told of a new arrest: a criminal person monitor

Re-running a search on a schedule to notice a new booking is the wrong shape: it is a billed call every time and most of them find nothing. A **person monitor** on the criminal dataset watches a named person continuously and sends a `monitor.alert` when a new record appears, changes, or is removed. Create it with `POST /v1/monitors`, `product: "criminal"`, `type: "person"`, the person (`lastName` required, `firstName` and `dob` recommended) and at least one channel: `channels.webhookUrl`, `channels.email` or `channels.emails[]`. Set `minConfidence: "dob_match"` when you send a date of birth and only want identified hits. The response is `201` with the monitor and, when you gave a webhook, `channels.webhookSecret` shown once. $3 a month per person monitor.

*Create the monitor; the alert that arrives*
```bash
# A criminal person monitor: be told when a new booking, case or warrant appears for this person
curl -X POST https://sandbox.offendersearch.app/v1/monitors \
  -H "X-API-Key: $OFFENDERSEARCH_TEST_KEY" -H "Content-Type: application/json" \
  -d '{"product": "criminal", "type": "person", "label": "Arrest Only — booking watch",
       "person": {"firstName": "Arrest", "lastName": "Only", "dob": "1992-04-20"},
       "minConfidence": "dob_match",
       "channels": {"webhookUrl": "https://hooks.example.com/hooks/offendersearch"}}'

# 201 … "channels": {"webhookUrl": "…", "webhookSecret": "whsec_…"}   ← shown once; store it

# What arrives at the webhook when something new is found (monitor.alert)
{
  "type": "monitor.alert",
  "event": "new",
  "alertId": "hit_…",
  "monitorId": "mon_…",
  "monitorLabel": "Arrest Only — booking watch",
  "product": "criminal",
  "detail": { "name": "ARREST ONLY", "jurisdiction": "…", "locality": "…", "matchLabel": "dob_match",
              "matchConfidence": 0.99, "personRef": "prs_…", "recordId": "…" },
  "occurredAt": "…", "sentAt": "…"
}
# Header: X-Offendersearch-Signature: t=<unix>,v1=<hex HMAC-SHA256(secret, "<t>." + raw body)>
```

The alert carries `event` ("new", "changed" or "removed"), `alertId`, `monitorId`, `product` and a `detail` block with `name`, `jurisdiction`, `locality`, `matchLabel`, `matchConfidence`, `personRef` and `recordId`. Treat it as a notification, not a record: fetch the record with `GET /v1/criminal/records/{recordId}` before you show anything, and verify the `X-Offendersearch-Signature` header on the raw body first. The receiver code is on the [webhook example](/docs/learn/webhook-example) page; alert semantics are in the [monitoring API](/docs/monitoring) reference. Location monitors are not yet available for the criminal dataset: `product: "criminal"` with `type: "location"` answers `200 { "status": "not_yet_available" }` and creates nothing.

## Mugshots, name-only matches and arrest record identity

Two things an arrest lookup must never do. The first is to show a mugshot as if it confirmed identity. `mugshotUrl` is the photo the jail published with the booking; it proves that the booked person looked like that, not that the booked person is your applicant. Show it to a reviewer beside the record, labelled as the booking photo, and keep the identity decision on `matchState`.

The second is to treat a name-only match as a hit. The sandbox's "Possible Match" person returns three records in three states with no date of birth on any of them, `matchState` `no_dob_age_year` and `unverified` true on each. That is a lead for a human, and the right UI says "3 possible records, none verified". `dob_match` with `unverified` false is the only automatic identification; `year_match` and `age_match` are weaker agreements you should surface as such; a record whose date of birth contradicts yours is not returned at all, which is why "Arrest Only" with a different date of birth is a clean zero. The [false positives](/docs/guides/criminal/false-positives) guide goes deeper, and the [no-match and multiple-match results](/docs/learn/no-match-and-multiple-match-results) tutorial shows the UI states.

## How to check if someone was arrested with the API, step by step

1. **Mint a test key and point at the sandbox.** Create an `os_test_` key in the dashboard, export it as `OFFENDERSEARCH_TEST_KEY` and call `https://sandbox.offendersearch.app`. Nothing on the sandbox is billed; 60 calls a minute and 2,000 a day per key.
2. **Send lastName, firstName and dob.** `POST /v1/criminal/search` with `query.lastName` (required), `query.firstName` and `query.dob` as YYYY-MM-DD. In production add `query.state`, and `query.county` when you know it. Sandbox: "Arrest Only" with 1992-04-20.
3. **Keep only jail_booking records and check matchState.** Filter `records[]` on `recordType` "jail_booking". Act on `dob_match` with `unverified` false; treat `no_dob_age_year` as a lead for a reviewer, never as an identification.
4. **Read the booking and the charge at booking.** Read `booking.bookingDate`, `booking.arrestDate` and `booking.arrestingAgency`, then `cases[].charges[]`: `description`, `severity` and `offenseDate`. `disposition` is "" at this stage: an arrest, not a conviction.
5. **Read lifecycle to tell current from historical.** `presentInLastSweep` true and `droppedOut` false is a booking still on the roster; `droppedOut` true with `lastSeen` in the past is a historical one, most likely a release.
6. **Follow the case to court.** Search the same person again and read `court_case` records: `cases[].charges[].disposition` and `dispositionDate` decide whether anything became a conviction. Open `sources[].recordUrl` to confirm.
7. **Watch for a new booking.** `POST /v1/monitors` with `product` "criminal", `type` "person", the person and `channels.webhookUrl`. A `monitor.alert` with `event` "new" arrives when a new record appears. $3 a month per person monitor.

Every response carries `legal.notice`: *"Not a consumer report. This information may not be used for any purpose under the Fair Credit Reporting Act (15 U.S.C. § 1681 et seq.)."* Keep it beside the result. If the lookup feeds a hiring, tenant, credit or insurance decision, it does not run here: it runs as an FCRA order on the [FCRA Partner API](/fcra), which records consent, scopes the search to the use location and handles adverse action. Whether your use needs it is a question for your counsel.

## Arrest records API: questions developers ask

### Is an arrest record the same as a criminal record?

No. An arrest record (a `jail_booking`) says a person was booked on a charge; the charge's `disposition` is empty because no court has decided it. A conviction is a `court_case` record with a filled `disposition`. Both are returned by the same endpoint, and `recordType` tells them apart.

### How do I check if someone was arrested with the API?

`POST /v1/criminal/search` with `query.lastName`, `query.firstName`, `query.dob` and, in production, `query.state`. Read `records[]` for `recordType` "jail_booking", confirm `matchState` is `dob_match`, then read `booking.bookingDate` and the charge in `cases[].charges[]`.

### How do I know whether the booking is current?

Read `lifecycle`. `presentInLastSweep` true and `droppedOut` false means the person was on the roster at the last check, with `lastSeen` as the date. `droppedOut` true means they have left the roster since `firstSeen`, most likely released. `currentStatus` is the source's own text and is not always updated, so decide on `lifecycle`.

### Does a booking tell me whether the person was convicted?

Never. Search for the `court_case` record for the same person and read `cases[].charges[].disposition`. If there is no court record in the sources on file, say so; do not infer either way.

### Which counties does the arrest records API cover?

`GET /v1/criminal/sources` lists every jurisdiction in the catalog with its `scope`, `status` and `health.lastSuccessAt`, and needs no key. On a search, `include: ["coverage"]` with a `state` in the query returns `coverage.sources[]` for that scope, and `counts.sourcesPending` counts the sources for that place that are not yet available.

### Can I use arrest records for hiring or tenant screening?

Not from this endpoint: every response says `legal.notice` "Not a consumer report". A hiring, tenant, credit or insurance decision runs as an FCRA order through the FCRA Partner API, and how arrests may be weighed in such a decision is a question for your counsel.

## What this page does not cover

- Live re-verification against a county jail portal (`live` block, `202` and polling): see [cached vs live](/docs/guides/criminal/cached-vs-live).
- Prison and DOC custody records (`doc_inmate`, `custodyFacility`, release dates): the [build an inmate search tool](/docs/learn/build-an-inmate-search-tool) tutorial and the [inmate search API](/inmate-search-api) page.
- Warrants (`recordType` "warrant", `currentStatus` "active_warrant"): see the [warrant search](/warrant-search) page.
- Filtering and faceting a result (`filters`, `include: ["facets"]`): the criminal filtering reference.

## Related

- [Search criminal records by name and date of birth](https://offendersearch.app/docs/learn/search-criminal-records-by-name-and-dob)
- [Court records API tutorial](https://offendersearch.app/docs/learn/court-records-api-tutorial)
- [Build an inmate search tool](https://offendersearch.app/docs/learn/build-an-inmate-search-tool)
- [Monitor a person with webhooks](https://offendersearch.app/docs/learn/monitor-a-person-with-webhooks)
- [No-match and multiple-match results](https://offendersearch.app/docs/learn/no-match-and-multiple-match-results)
- [Arrest records](https://offendersearch.app/arrest-records)
- [Jail roster](https://offendersearch.app/jail-roster)
- [Criminal records API](https://offendersearch.app/criminal-records-api)
- [Inmate search API](https://offendersearch.app/inmate-search-api)
- [Criminal record object](https://offendersearch.app/docs/criminal/record-object)
- [Arrests, charges and convictions](https://offendersearch.app/docs/guides/criminal/arrests-charges-convictions)
- [Monitoring API](https://offendersearch.app/docs/monitoring)
