Skip to main content
API
Available on Full Domination
2 min read

Pagination

Cursor-based pagination — params, response shape, and patterns.

Last updated May 12, 2026

Why cursors, not pages

The API uses cursor-based pagination throughout. Cursors don't shift when underlying data changes, so iterating a list while items are added or removed never re-shows or skips items.

We never used offset / limit pagination, so there's no migration story — every endpoint that returns a list uses cursors from day one.

Request shape

GET /v1/audits?cursor=<opaque>&limit=50
  • cursor — opaque string from a previous response. Omit on the first request.
  • limit — page size, 1 to 100 inclusive. Default 50.
curl "https://api.aidomination.app/v1/audits?limit=20" \
  -H "Authorization: Bearer $AD_TOKEN"

Response shape

{
  "data": [
    { "id": "aud_01", "headlineScore": 78 },
    { "id": "aud_02", "headlineScore": 81 }
  ],
  "pagination": {
    "nextCursor": "Y3Vyc29yOmF1ZF8wMg==",
    "hasMore": true
  }
}
  • data — array of items.
  • pagination.nextCursor — pass to the next request as the cursor param. null when there are no more pages.
  • pagination.hasMore — convenience boolean.

Full iteration example

async function fetchAllAudits(token: string) {
  const audits: unknown[] = [];
  let cursor: string | null = null;
  while (true) {
    const url = new URL("https://api.aidomination.app/v1/audits");
    if (cursor) url.searchParams.set("cursor", cursor);
    url.searchParams.set("limit", "100");
    const res = await fetch(url, {
      headers: { Authorization: `Bearer ${token}` },
    });
    if (!res.ok) throw new Error(`HTTP ${res.status}`);
    const json = await res.json();
    audits.push(...json.data);
    if (!json.pagination.nextCursor) break;
    cursor = json.pagination.nextCursor;
  }
  return audits;
}

Sort order

Lists return in created-descending order by default. The cursor encodes both an id and a created-at timestamp, so this ordering is stable across pages.

Some endpoints (notably /audits and /mentions) support ?sort= overrides — see the endpoint reference.

Filtering with pagination

Filters apply BEFORE pagination. The cursor encodes the filter set, so paginating a filtered list returns only filtered items.

GET /v1/content?status=approved&limit=50

If you paginate, then change the filter mid-iteration, the new cursor is incompatible — the API returns 400 cursor_mismatch. Restart the iteration.

Cursor opacity

Cursors are opaque, base64url-encoded structures. We reserve the right to change the internal format. Don't parse them or construct them yourself; treat them as opaque tokens.

Cursor lifetime

Cursors are valid for 24 hours from issue. After 24 hours, they return cursor_expired and you restart.

Reverse iteration

We support backward pagination via ?direction=before&cursor=<>. Useful for "load older" patterns in a UI. The default direction is forward.

Counts

We don't return total counts in list responses. Computing exact counts requires a full table scan on the larger lists, which is expensive enough that we suppress it by default.

For approximate counts (e.g. "about 1,200 audits"), use GET /v1/audits/count?approximate=true. The endpoint returns a count accurate to ±10%.

For exact counts on small lists (companies, API keys), the response includes a pagination.total field. The threshold is 1,000 items.

Common pitfalls

  • Re-using a cursor. Cursors are single-use within a page; calling GET with the same cursor twice returns the same page twice. That's fine — but don't assume the cursor mutates server state.
  • Pagination with sort and filter changing. Always recompute the cursor when sort or filter changes.
  • Limit too low. Limit < 10 produces excessive round-trips. Limit > 100 is rejected.

Was this article helpful?

Related docs

Pagination · AI Domination