Pagination
The list-returning endpoints — search, category, and the paid change feed — page with an opaque cursor. The single-title lookup returns one object and never paginates.
The envelope
Section titled “The envelope”Every paged response wraps its rows in data and carries a pagination block:
{ "data": [ /* SearchResult[] or Verdict[] or ChangeRecord[] */ ], "pagination": { "nextCursor": "eyJ2IjoyfQ", "hasMore": true }}nextCursor— an opaque token. Pass it back as?cursor=to get the next page. It isnullon the last page. Never parse or construct it; its format can change.hasMore—truewhen another page exists.
Page size
Section titled “Page size”Use limit (default 50, max 100). Larger limit means fewer round trips; the cap keeps
any single response bounded.
curl "https://api.doesitarm.com/v1/categories/productivity/verdicts?limit=100"A full loop
Section titled “A full loop”Walk every page until nextCursor is null:
async function* allVerdicts(category) { let cursor = null do { const url = new URL( `https://api.doesitarm.com/v1/categories/${category}/verdicts` ) url.searchParams.set("limit", "100") if (cursor) url.searchParams.set("cursor", cursor)
const { data, pagination } = await (await fetch(url)).json() yield* data cursor = pagination.nextCursor } while (cursor !== null)}
for await (const verdict of allVerdicts("productivity")) { console.log(verdict.slug, verdict.status)}Each page still carries the rate-limit headers, so honor
RateLimit-Remaining while looping. For keeping a large local copy current, prefer the
change feed over re-walking every page.