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.
Related#
- Responses and errors - the envelope that wraps every page
- Idempotency - safe retries for writes