Skip to main content
Eight endpoints page, in two different models. Offset paging walks a fixed window with limit and offset and stops when hasMore is false. Use it for a ranked or ordered list that is not changing while you read it. Cursor paging walks an append-only log by change id, forwards with sinceId or backwards with beforeId. Use it for change history, where new rows land while you page and an offset would skip or repeat them. The last three take a limit and nothing else. They return the top N by relevance, and there is no page two, so raise limit to its maximum if you want the whole set. GET /watches/{id}/deliveries behaves the same way, with a limit of 50 by default and 200 at most.

Search paging

POST /us/statutes/search is the endpoint most integrations page. Two request fields control the window, and one response flag tells you when to stop.

Response fields

Each search response includes these top-level fields:
  • results: the array of result objects for this page.
  • count: the number of results returned on this page, not the corpus-wide match count.
  • total: a deprecated alias for count. It is still returned for back-compatibility. Read count in new code.
  • offset: the offset that was applied.
  • hasMore: true when more results are reachable beyond this page.
  • creditsConsumed: what this call actually cost. Read it rather than assuming the list price.
count is a per-page count, not a corpus total. There is no field that reports how many sections in the whole corpus match your query. Use hasMore to drive your loop, not arithmetic on count.
hasMore is always false on the last reachable page, because the ranked candidate pool is capped at 120 results (offset 70 + limit 50). It means “more results are reachable within that pool”, not “more matches exist in the corpus”. Narrow the query rather than paging deeper.

How deep you can page

The deepest reachable result is offset + limit. The full candidate pool is ranked once per query and cached, so every page, including deep ones, is a free slice of that ranking with no extra search or rerank. With the largest page size, offset up to 70 reaches the entire reranked pool:
If you need to survey a jurisdiction more broadly, scope with corpusType and state and run several narrower queries rather than trying to page past the window. Tighter scope also gives more predictable pages.

Page until hasMore is false

This loop walks every reachable page, stopping when hasMore is false or the offset cap is reached.

Offset paging on the list endpoints

GET /boards and GET /watches both take limit and offset and return their page inside data. They differ in what they tell you about the rest. GET /boards returns data.total and data.hasMore, so you can page it properly. GET /watches returns neither: page it by advancing offset until a page comes back shorter than limit. In practice one page is always enough there, because the watch cap is 3 outside Business and 100 on Business, and limit defaults to 100. GET /boards is the one that bites. Its limit defaults to 500 and 500 is also the maximum, so a client that never passes offset silently truncates the moment the registry passes 500 boards. There are about 216 boards today, so the default still covers the whole registry, but that is a fact about today rather than a guarantee. Read total and page on offset instead of trusting one call.
Python
GET /boards and GET /watches are free, so paging them costs nothing but a request against your rate limit.

Cursor paging on change history

GET /watches/{id}/changes and GET /us/statutes/section/{actId}/changes page by change id rather than by offset. Change ids are monotonic across the whole corpus, so an id is a stable position in the log even as new rows arrive behind you. The two endpoints return the cursor in different places, which is the one thing to get right:
  • GET /watches/{id}/changes returns it as meta.cursor, alongside meta.hasMore.
  • GET /us/statutes/section/{actId}/changes returns it as a top-level cursor, alongside a top-level hasMore.
Either way the cursor is the highest id on the page, and it is null on an empty page, so an idle poll must keep the cursor it already holds rather than overwriting it with null. Polling forwards is the normal case: save the cursor, and pass it as sinceId with order=asc on the next call. Walking forward means a page that fills to limit leaves the gap at a known end, so you never lose a row between polls. Walking backwards is what beforeId is for: pass the lowest id you have seen, with the default order=desc, to fetch the page of history that sits behind it. Repeat until a page comes back short of limit.
Python
hasMore on both change endpoints means “this page filled to limit”, not “more rows definitely exist”. A page that happens to land exactly on limit reports true and the next page comes back empty. Treat an empty page as the end.

Law Change Alerts

Subscribe to a board, then poll its changes with a cursor.

Rate limits

Stay under per-minute limits while paging in a loop.

Best practices

Scope queries for tighter, more predictable pages.
Last modified on September 6, 2026