🛡️Gatekeeper/ SDKs

List endpoints use keyset (cursor) pagination, not offsets. Each call returns a page of items plus an opaque cursor that points at where the next page begins. There are no page numbers and no total count - you walk forward until the cursor is exhausted.

The Page shape#

Every paginated list resolves to the same envelope:

interface Page<T> {
  items: T[];
  nextCursor: string | null; // opaque; null means no more pages
}

The cursor is opaque - treat it as a token, do not parse or construct it. A null / None nextCursor means you have reached the end.

Looping through every page#

Pass the previous page's nextCursor back in as the cursor filter, and stop when it comes back empty.

import { GatekeeperCore, AuditService } from '@orkait/sdk';
import type { AuditEvent } from '@orkait/sdk';
 
const audit = new AuditService(core);
 
async function allEvents(tenantId: string): Promise<AuditEvent[]> {
  const out: AuditEvent[] = [];
  let cursor: string | undefined;
 
  do {
    const page = await audit.query(tenantId, { cursor, limit: 100 });
    out.push(...page.items);
    cursor = page.nextCursor ?? undefined;
  } while (cursor);
 
  return out;
}

The first call omits the cursor (or passes undefined / None). The SDK drops undefined / None query params, so an absent cursor simply starts from the beginning.

Streaming instead of buffering#

If you do not need every row in memory, process each page as it arrives rather than collecting into one list. The control flow is identical - swap the push / extend for whatever you do per item, and the loop ends the same way when nextCursor is empty.