Protocols and contracts

Pagination and filtering

Traverse opaque keyset pages, poll transcripts forward, and apply each collection's supported filters.

Long lists come back one page at a time. Pass the cursor from one page into the next request to keep reading. Transcripts add a second cursor so you can poll for new messages without re-reading old ones.

Contract

A standard page is { data, nextCursor }. Pass a non-null nextCursor back as cursor to the same endpoint with the same filters. null means there are no more pages in that direction. Cursors are opaque: do not decode, edit, or reuse one on another list. A cursor the API cannot read returns 400 invalid_cursor; other bad paging parameters return 400 validation_failed.

Lists run newest first: agent versions by version number, tasks by updatedAt, memories by createdAt then id, and other lists by their own timestamp. Transcripts return the newest page first, with messages in chronological order inside each page.

CollectionSDK methodREST operationResponse cursor fieldsPage sizeFilters
Agentsagents.listlist-agentsnonebounded; no public limituserId
Agent Versionsagents.listVersionslist-agent-versionsnextCursordefault 50, max 200none
Providersproviders.listlist-providersnonebounded; no public limitnone
MCP ConnectionsmcpConnections.listlist-mcp-connectionsnonebounded; no public limitnone
MCP Attachmentsagents.listMcpAttachmentslist-agent-mcp-attachmentsnonebounded; no public limitowning Agent path
Promptsprompts.listlist-promptsnonebounded; no public limituserId
Sessionssessions.listlist-sessionsnextCursordefault 50, max 200userId
Latest Sessionssessions.listLatestlist-latest-sessionsnextCursordefault 50, max 200userId, byAgent
Session messagessessions.messageslist-session-messagesnextCursor, latestCursordefault 50, max 200cursor or after
Artifactsartifacts.listlist-artifactsnextCursorfixed 50; no public limitagentId, sessionId
Memoriesmemories.listlist-memoriesnextCursordefault 50, max 100userId, search
Taskstasks.listlist-tasksnextCursordefault 50, max 200agentId, userId
Task runstasks.listRunslist-task-runsnextCursordefault 50, max 200owning Task path
Task run messagestasks.runMessageslist-task-run-messagesnextCursor, latestCursordefault 50, max 200cursor or after
Usageusage.get, getForAgentget-usage, get-agent-usagenonenot cursored; Session top-N default 50, max 200dates, Agent, Session, Attribution, grouping

Lists without cursor fields return everything in one bounded response. No list supports offsets, total counts, or custom sorting.

Session and task-run transcripts add latestCursor:

  • cursor walks backward to older messages.
  • after reads messages newer than a saved position.
  • Send one or the other, never both.
  • When data is not empty, save latestCursor and use it as the next after value, even if nextCursor is null.
  • In forward mode, pass a non-null nextCursor back as after to finish the current result. latestCursor marks where the next poll starts once you have drained it.

Every userId filter works the same way: leave it out to include everyone, send "" for tenant-level records, or send a value to select that end user. userId groups your data; it does not restrict access.

Usage from and to are inclusive UTC dates and must be sent together. Without them you get the last 30 days ending today, and the range can span at most 31 days. groupBy defaults to day and also accepts agent, model, session, and user. sessionId: "" selects stateless turns, which come back with sessionId: null. Tenant-level buckets in groupBy=user keep userId: "".

Only memories support full-text search.

Multi-value filters such as status on list tenant chat deliveries take a comma-separated list in one parameter.

Examples

Read every session for an agent, one page at a time:

let cursor: string | undefined;

do {
  const page = await client.sessions.list({ agentId, cursor, limit: 100 });
  for (const session of page.data) {
    console.log(session.id);
  }
  cursor = page.nextCursor ?? undefined;
} while (cursor);

Read a task run's transcript once, save its tail, then poll forward. Only pass back as after a nextCursor that came from a forward request:

const bootstrap = await client.tasks.runMessages({ taskId, runId, limit: 50 });
for (const message of bootstrap.data) console.log(message);

if (bootstrap.latestCursor === null) {
  throw new Error("The task run has no transcript yet");
}

await saveTail(bootstrap.latestCursor);
let after: string | undefined = bootstrap.latestCursor;

do {
  const page = await client.tasks.runMessages({
    taskId,
    runId,
    after,
    limit: 50,
  });

  for (const message of page.data) console.log(message);
  if (page.latestCursor !== null) await saveTail(page.latestCursor);
  after = page.nextCursor ?? undefined;
} while (after);

Read tenant-level usage and keep the overall totals apart from the top-N buckets:

const usage = await client.usage.get({
  from: "2026-07-01",
  to: "2026-07-20",
  userId: "",
  groupBy: "session",
  limit: 25,
});

console.log(usage.totals.requestCount, usage.buckets);

Next

On this page