Offendersearch
API Reference · v1.0.0

Pagination & response size

The full result set in one response by default — plus page, perPage and totalPages when you want slices.

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

Pagination

POST /v1/search returns the full, de-duplicated result set in a single response unless you ask otherwise. Set query.page and/or query.perPage to paginate; set neither and you get everything at once. Results are totally and stably ordered, so paging never reshuffles.

You do not have to page to get a complete answer. There is no page-until-empty loop to write and no cursor to carry: send the query, read records, and that is the whole result set. On a response you did not paginate, perPage comes back equal to counts.records and totalPages is 1 — that is the shape confirming you have everything. Pagination exists for your convenience — a UI that shows twenty rows at a time, or a smaller payload — not as a condition of completeness.
ParameterDefaultConstraint
query.page1>= 10 returns 422. A page past the end is clamped to the last page.
query.perPage20 when paginating>= 1

The search envelope returns page, perPage and totalPages directly — you do not need to compute the page count. Read page back rather than assuming it: a page past the end is clamped to the last one. Separately, counts.records is the total matched before the page slice, which is not records.length.

Paging a result set
async function page(n) {
  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: { lastName: "Doe", state: "NJ", page: n, perPage: 20 },
    }),
  });
  return res.json();
}

const first = await page(1);
// counts.records is the TOTAL before the slice; totalPages is authoritative.
for (let n = 2; n <= first.totalPages; n++) {
  const next = await page(n);       // page(n).page echoes the page you got
}

Response size

An unpaginated search for a common surname can reach roughly 20 MB of JSON (gzip is applied above 1 KB). Paginate for anything interactive. An unpaginated response is returned up to a defined 4,000-record cap and sets capped: true when that cap applies; send perPage and every matched record is reachable, with capped false. Branch on counts.records against counts.recordsReturned: if they differ, there is more to fetch.

  • Broad geographic searches. A wide lat/lng + radiusMiles query can match many registrants. Narrow the radius or add name filters, or run it as an async search, which has no request-timeout ceiling. Radius matching selects on published coordinates, so use a name search when you need selection that is independent of address coordinates.
  • Compatibility endpoint. The compat endpoint preserves the legacy paged envelope — pass page and read page / totalPages from the response exactly as before. Regular searches page 20 per page there; a GIS (lat + lng) search pages 50.
  • A cap is not a page. The per-jurisdiction candidate limit described in Result completeness is reported through incompleteReason: "truncated", and it is not something paging can walk through — narrow the query instead.