Reference

Lists and pages

How the admin and runtime APIs, the CLI and the SDKs read a list that can grow, a page at a time.

For: Developers reading paginated APIs.

Growing lists, including jobs, approvals, nodes, people and audit entries, are read one page at a time. A page token lets a client continue the same query without loading the whole collection.

In the APIs

Every list of the admin API and the runtime API that can grow takes the same two fields and answers the same two:

FieldInWhat
page_sizethe requestAt most this many rows. 25 when 0, and a larger size than 100 is cut to 100. A negative size is refused.
page_tokenthe requestEmpty for the first page; then the next_page_token of the page before.
next_page_tokenthe responseThe token of the next page; empty on the last page.
total_sizethe responseHow many rows match in all, on every page, where counting them is cheap: a tenant's nodes, plugins, teams, keys and the other things it configures, and the lists the server holds whole anyway. The audit log, jobs and conversations are not counted.

A list keeps its filters on every page: send the same filters with the token. A page follows a stable order that ends in an ID (most lists go newest first, by a time and then the ID; lists of named things go by name), and the token carries where the page ended, not a count of rows to skip.

So a row that arrives while you read is not shown twice and does not push another one out of sight: what arrives before the page you are on is in the first page the next time you read from the start.

In a list that goes oldest first by a time each row is stamped with, such as the approvals that wait, a row stamped by a clock behind the others (another server's, or one that stepped back) can fall before the page you are on: it is not on the later pages of that read, and the first page shows it the next time you read from the start.

Four lists are ordered by what changes while you read them. Three of them are read as of the moment of their first page, and the token carries that moment to every later page, so the pages of one read agree with each other however the rows change in between:

ListOrderRead as of the first page
Eval findingsOpen ones first, then the most recently opened, then by ID.The findings opened by then, open first if they were open then; the state filter is the state then and the period is of when they were opened. A finding resolved, or raised again, while you read keeps its place, and shows its state now.
ConversationsThe one with the newest turn first, then by ID.Only the turns started by then count, for the order, the agent filter and the count of turns. A conversation that gets a new turn while you read keeps its place.
Error groupsThe jobs a group was seen in, most first, then by fingerprint.The period ends then at the latest, and since reaches back from that end, so it does not slide from page to page; only the errors that began by then count. Only a job that was still running then, and whose errors are written later, can change a group's count and move it.

The period of a read is fixed by its first page too: an until before that moment ends it there, and one after it, or none, ends it at the moment, whatever the server's clock says when it serves a later page. So a clock that steps back between two pages, or an until ahead of the server's clock, changes neither the rows nor the counts of a later page.

A token of these three lists is good for a day; after that, or after an upgrade from a release before they were read this way, the server refuses it with INVALID_ARGUMENT and you ask for the first page again. A new read, from the first page, sees the list as it is now.

A person's chats (pinned first, then by the latest activity) cannot be read as of a moment: a pin and the latest activity change in place and keep no history. A chat that changes there while you read moves: a chat unpinned after it was shown can show again on a later page, and one pinned or active before it was shown goes above the page you are on and is not on the later pages.

The CLI's --all, the SDKs' iterators and the web UI show each chat once; a client of the API that merges pages itself should drop a chat whose ID it showed already, and read from the start again to see the list as it is now.

Filters on the server

The lists filter on the server, before a page is cut, so that every page but the last is full and total_size counts the rows the filters keep. Each filter is part of what a page token is bound to.

FilterWhatLists
since, untilThe rows of a period, by the time the list sorts by: from since (inclusive) to until (exclusive). Either may be empty: no lower bound, or now; an until after now is now.Change requests (when asked), incidents (opened), eval findings (opened), channel threads (started), the runtime API's jobs and a routine's fires (when the run started), conversations (the latest turn) and a person's chats (the latest activity).
until beside a durationJob summaries and error groups keep their since, a duration, and take an until it reaches back from, so since: 24h, until: <a Monday's end> is that Monday.Job summaries, error groups.
queryWords the rows must each hold, as a substring, without case: blue 50% keeps the rows that hold both blue and 50% (the % and _ are text, not patterns). At most 8 words and 200 characters, or the request is refused.Nodes (ID, name, labels), plugins (name, description, source), routines (name, owning team, the account it runs as), teams (name, display name), accounts (name, role), API keys (ID, name, role, creator), credential bindings (ID, secret, host, path, plugin, function), secrets (name, outside reference, who changed it last), connections (name, description, plugin), approvals (ID, job, agent, kind, what it is for; never the call's arguments or a question's text).
query of jobsText the job's ID holds, without case.The runtime API's jobs.
statusOne condition, or empty for any; another value is refused.Jobs: running, waiting (running, with an approval to answer), completed, failed, canceled, timed_out, terminated. Nodes: online and offline (accepted, and connected with a recent heartbeat or not), pending, blocked, draining, purging, rejected, findings (with findings of their own, such as a sandbox at tier 0). Routines: active, paused.
agentThe jobs of one agent or flow.The runtime API's jobs.
risk, ownerrisk keeps one risk class, or none for the agents with none yet; owner is none for the agents in the catalog, not retired, that no person answers for, or unused for the items nobody started in 90 days.The inventory.

The runtime API's jobs come from the workflow backend's visibility: the period and the status are in its query, and the agent and the ID's text are kept from what it answers, so a page of an agent whose jobs are few among many may come back short, with a token that goes on.

Page tokens

A page token is opaque: its shape may change between releases, so read it only as a string to send back. It is good only for the list, the tenant, the caller and the filters it was made for, and the server refuses it with INVALID_ARGUMENT otherwise: after changing a filter, ask for the first page again.

A token never widens what you may read: each page is read with the caller's permissions and the request's filters, and the token only says where the page starts.

Lists that had a limit field before they paged still read it as the old name of page_size when page_size is 0, and lists that had their own cursor (chats' cursor and next_cursor, the MCP registry's) still answer it beside next_page_token.

Lists across tenants

Three lists also answer for several tenants at once, for the web UI's All tenants and for any client that reads them: the admin API's ListJobSummaries and GetUsage (totals, not paged) and the runtime API's ListApprovals. A request asks across tenants with every_tenant, every tenant the caller has a role in (every tenant for a cluster admin or reader), or names them in tenants, at most 100 (20 for approvals), and leaves tenant empty: an empty tenant without them is still the default tenant, as the CLI and the SDKs read it.

FieldInWhat
every_tenant, tenantsthe requestThe tenants to read; one or the other, not both.
tenanteach rowThe row's tenant: a job summary's, a usage total's, an approval's.
tenants_readthe responseThe tenants whose rows the answer holds.
tenants_refusedthe responseTenants asked for, or held a role in, where the caller's roles do not let it read the list: their rows are left out, and the call does not fail for them. A tenant the caller holds no role in is named only if the request named it.
tenants_unreadthe responseApprovals only: tenants the caller may read that this request did not reach. Each tenant's approvals are read from its own jobs, so a request reads at most 20 tenants, the first 20 by name; a tenant served by another region, or whose jobs did not answer, is named here too.

Each tenant is read only where the caller holds the permission one tenant's list needs, with the caller's grant in that tenant (its content permission, an agent user's own jobs, a scoped key's scopes). The rows go in one order across tenants: job summaries newest first by start, then by tenant and job ID; approvals oldest first by when they were asked, then by tenant, job and approval ID.

A page token is bound to the tenants asked for as well as to the caller and the filters, and one tenant's tokens are not taken across tenants. Usage across tenants is totalled by tenant, agent, provider and model, with job and node empty; a job's usage is asked in its tenant, as are a job's approvals.

In the CLI

Every command that lists one of them takes the same flags:

FlagWhat
--limitRows a page: the server's 25 when not set, at most 100.
--page-tokenGo on after the page before, with the token it printed.
--allRead every page (100 rows at a time unless --limit says otherwise).

When more rows follow, a table's command prints the next token on standard error, so a script that reads standard output gets only rows:

oz0 audit --action node.
# ...25 entries...
# More rows follow: add --page-token eyJ2Ijo..., or --all for every page.
oz0 audit --action node. --page-token eyJ2Ijo...
oz0 audit --action node. --all --json > audit.json

With --json, the response's next_page_token is in the JSON; with --all it is empty and the rows of every page are in one response, each row once: a chat that moved while the pages were read is listed where it was first shown.

In the SDKs

The SDKs read the pages for you: their list methods are iterators that ask for the next page when you reach the end of one, so a loop over them reads only as far as it goes. The iterators over conversations and chats yield each one once, even a chat pinned or unpinned while the loop runs.

for job, err := range c.AllJobs(ctx, false) {
    if err != nil {
        return err
    }
    fmt.Println(job.GetId())
}
for job in client.iter_jobs():
    print(job.id)
for await (const job of client.allJobs()) console.log(job.id);
Copyright © 2026