Offendersearch
API Reference · v1.0.0

Batch & CSV search

Up to 1000 lookups per call, JSON or CSV, row in and row out, with per-row fault isolation.

Base URL https://api.offendersearch.appThis page as Markdown/docs/batch.md

One row in, one result out

The batch endpoint is the shape to reach for when you already hold a list: a roster to screen, a CSV export to reconcile, a nightly re-check of a population. Each row runs through the same engine as POST /v1/search, so every result carries the same envelope — counts, sourceStatus, labelled matchState — and the same Record schema.

  • Three accepted body shapes. A bare JSON array of Query objects, a JSON envelope { "queries": [...], ...options } whose non-queries keys apply to every row, or a text/csv body whose header row names Query fields.
  • Input order is preserved. Each result carries its 0-based index, so you can zip results back onto your own rows without matching on identity.
  • Rows are fault-isolated. A row that fails is reported with status: "error" in place and never aborts the rest of the batch.
  • 1000 rows per call. A larger batch returns 413.
Billing is per search, not per request. A batch is one HTTP call, but each search in it is metered as one search — a batch of 100 rows is 100 metered searches, each at the graduated per-call rate plus its own +$0.01 daily-freshness surcharge when that row is daily (the default). Set freshness: "weekly" on a row, or batch-wide, to meter that search with no surcharge.
POST/v1/batchAuth: X-API-Key

Batch / CSV search

Run many lookups in one call — row in, row out. Send a JSON array of queries, a JSON envelope with batch-wide options, or a text/csv body. Each row runs through the same engine as /v1/search and results come back in input order.

The batch endpoint provides legacy CSV-batch parity: one row = one call in, one result out. Post a JSON array of Query objects, a JSON envelope { "queries": [...], ...options } where the non-queries keys (jurisdictions, freshness, match, recordTypes, locationScoped, include) apply to every row, or a text/csv body whose header row names Query fields (firstName,lastName,state,dob,city,zipcode,address,age,q).

Rows run with bounded server-side concurrency and results return in the same order you sent them. A row that fails is reported with status: "error" in place — it never aborts the rest of the batch. The maximum is 1000 rows per call (a larger batch returns 413).

Billing is per search, not per request. A batch is one HTTP call but each search in it is billed as one search — a batch of 100 rows = 100 metered searches, each at the graduated per-call rate plus its own +$0.01 daily-freshness surcharge when that row is daily (the default). Set freshness: "weekly" on a row (or batch-wide) to bill that search with no surcharge.

Every row is served at the freshness tier it asks for. weekly is the natural fit here — bulk and periodic re-screening work is exactly what it is for, and it carries no surcharge; set daily on the rows where currency decides the outcome. For very large or long-running workloads, the asynchronous endpoint is the other batch-shaped option: submit and collect, with no request-timeout ceiling.

What you can do
  • JSON or CSV. Post a JSON array, a JSON envelope with batch-wide options, or a text/csv body — whatever your pipeline already produces.
  • Row-in / row-out. Results return in input order, each tagged with its 0-based index.
  • Fault-isolated. A row that errors is reported with status:"error" in place and never aborts the batch.
  • Up to 1000 rows. One call handles up to 1000 queries; larger batches return 413.
Body

Send one of: a JSON array of Query objects, a JSON envelope, or a text/csv body.

FieldTypeDescription
queries
required
Query[]The per-row queries (envelope form). Or post a bare JSON array, or a CSV body.
jurisdictions / freshness / match / recordTypes / locationScoped / include
optional
variousBatch-wide options in the envelope form — applied to every row.
Request
# JSON envelope: batch-wide options apply to every row
curl https://api.offendersearch.app/v1/batch \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "queries": [
      { "firstName": "John", "lastName": "Smith", "state": "FL" },
      { "firstName": "Jane", "lastName": "Doe",   "state": "TX" }
    ],
    "freshness": "daily",
    "match": "balanced"
  }'

# …or upload a CSV whose header row names Query fields
curl https://api.offendersearch.app/v1/batch \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary $'firstName,lastName,state\nJohn,Smith,FL\nJane,Doe,TX'
Response
200 OK
{
  "count": 2,
  "results": [
    { "index": 0, "status": "complete",
      "counts": { "records": 1, "sourcesQueried": 3 },
      "records": [ /* … scored, de-duplicated records … */ ] },
    { "index": 1, "status": "complete",
      "counts": { "records": 0, "sourcesQueried": 3 }, "records": [] }
  ]
}

Results are in input order. A failed row is { "index": n, "status": "error", "error": "…" } in place. Returns 413 if the batch exceeds 1000 rows.

The CSV form

Post a text/csv body whose header row names Query fields — firstName, lastName, state, dob, city, zipcode, address, age, q. Every row is a query, and the response is the same JSON envelope as the JSON form, so a pipeline that already produces a CSV needs no transformation step.

Request
text/csv body
curl https://api.offendersearch.app/v1/batch \
  -H "X-API-Key: $OFFENDERSEARCH_KEY" \
  -H "Content-Type: text/csv" \
  --data-binary @roster.csv

# roster.csv
# firstName,lastName,state,dob
# John,Smith,FL,1980-04-12
# Jane,Doe,TX,
Response
200 OK
{
  "count": 2,
  "results": [
    { "index": 0, "status": "complete",
      "counts": { "records": 1, "sourcesQueried": 58,
                  "sourcesComplete": 58, "sourcesIncomplete": 0 },
      "records": [ /* … */ ] },
    { "index": 1, "status": "partial",
      "counts": { "records": 0, "sourcesQueried": 58,
                  "sourcesComplete": 57, "sourcesIncomplete": 1 },
      "records": [] }
  ]
}
Read each row’s own counts. Row-level completeness is reported per row, exactly as it is on a single search — row 1 above returned no records, but one jurisdiction did not complete, so it is not determined rather than no match. See Result completeness.

Batch or async?

Both are volume shapes and they compose. Batch is synchronous and row-oriented: you hold the connection, and you get every row back in one response, in order. Async (POST /v1/searches) is one search submitted and collected later, with no request-timeout ceiling — the right shape for a single very broad search, or when you would rather be called back than wait.

BatchAsync
UnitMany queries, one callOne query, collected later
DeliveryIn the response, in input orderPoll or signed webhook
Ceiling1000 rows per callNo request-timeout ceiling
Use it forRosters, CSV reconciliation, scheduled re-screensVery broad single searches, unattended jobs