---
title: "Lists and pages"
description: "How the admin and runtime APIs, the CLI and the SDKs read a list that can grow, a page at a time."
canonical_url: "https://docs.oz0.ai/reference/list-pages"
---
# 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:

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      In
    </th>
    
    <th>
      What
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        page_size
      </code>
    </td>
    
    <td>
      the request
    </td>
    
    <td>
      At most this many rows. 25 when 0, and a larger size than 100 is cut to 100. A negative size is refused.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        page_token
      </code>
    </td>
    
    <td>
      the request
    </td>
    
    <td>
      Empty for the first page; then the <code>
        next_page_token
      </code>
      
       of the page before.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        next_page_token
      </code>
    </td>
    
    <td>
      the response
    </td>
    
    <td>
      The token of the next page; empty on the last page.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        total_size
      </code>
    </td>
    
    <td>
      the response
    </td>
    
    <td>
      How 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.
    </td>
  </tr>
</tbody>
</table>

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:

<table>
<thead>
  <tr>
    <th>
      List
    </th>
    
    <th>
      Order
    </th>
    
    <th>
      Read as of the first page
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Eval findings
    </td>
    
    <td>
      Open ones first, then the most recently opened, then by ID.
    </td>
    
    <td>
      The findings opened by then, open first if they were open then; the <code>
        state
      </code>
      
       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.
    </td>
  </tr>
  
  <tr>
    <td>
      Conversations
    </td>
    
    <td>
      The one with the newest turn first, then by ID.
    </td>
    
    <td>
      Only the turns started by then count, for the order, the <code>
        agent
      </code>
      
       filter and the count of turns. A conversation that gets a new turn while you read keeps its place.
    </td>
  </tr>
  
  <tr>
    <td>
      Error groups
    </td>
    
    <td>
      The jobs a group was seen in, most first, then by fingerprint.
    </td>
    
    <td>
      The period ends then at the latest, and <code>
        since
      </code>
      
       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.
    </td>
  </tr>
</tbody>
</table>

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.

<table>
<thead>
  <tr>
    <th>
      Filter
    </th>
    
    <th>
      What
    </th>
    
    <th>
      Lists
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        since
      </code>
      
      , <code>
        until
      </code>
    </td>
    
    <td>
      The rows of a period, by the time the list sorts by: from <code>
        since
      </code>
      
       (inclusive) to <code>
        until
      </code>
      
       (exclusive). Either may be empty: no lower bound, or now; an <code>
        until
      </code>
      
       after now is now.
    </td>
    
    <td>
      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).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        until
      </code>
      
       beside a duration
    </td>
    
    <td>
      Job summaries and error groups keep their <code>
        since
      </code>
      
      , a duration, and take an <code>
        until
      </code>
      
       it reaches back from, so <code>
        since: 24h, until: &lt;a Monday's end>
      </code>
      
       is that Monday.
    </td>
    
    <td>
      Job summaries, error groups.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        query
      </code>
    </td>
    
    <td>
      Words the rows must each hold, as a substring, without case: <code>
        blue 50%
      </code>
      
       keeps the rows that hold both <code>
        blue
      </code>
      
       and <code>
        50%
      </code>
      
       (the <code>
        %
      </code>
      
       and <code>
        _
      </code>
      
       are text, not patterns). At most 8 words and 200 characters, or the request is refused.
    </td>
    
    <td>
      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).
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        query
      </code>
      
       of jobs
    </td>
    
    <td>
      Text the job's ID holds, without case.
    </td>
    
    <td>
      The runtime API's jobs.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        status
      </code>
    </td>
    
    <td>
      One condition, or empty for any; another value is refused.
    </td>
    
    <td>
      Jobs: <code>
        running
      </code>
      
      , <code>
        waiting
      </code>
      
       (running, with an approval to answer), <code>
        completed
      </code>
      
      , <code>
        failed
      </code>
      
      , <code>
        canceled
      </code>
      
      , <code>
        timed_out
      </code>
      
      , <code>
        terminated
      </code>
      
      . Nodes: <code>
        online
      </code>
      
       and <code>
        offline
      </code>
      
       (accepted, and connected with a recent heartbeat or not), <code>
        pending
      </code>
      
      , <code>
        blocked
      </code>
      
      , <code>
        draining
      </code>
      
      , <code>
        purging
      </code>
      
      , <code>
        rejected
      </code>
      
      , <code>
        findings
      </code>
      
       (with findings of their own, such as a sandbox at tier 0). Routines: <code>
        active
      </code>
      
      , <code>
        paused
      </code>
      
      .
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        agent
      </code>
    </td>
    
    <td>
      The jobs of one agent or flow.
    </td>
    
    <td>
      The runtime API's jobs.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        risk
      </code>
      
      , <code>
        owner
      </code>
    </td>
    
    <td>
      <code>
        risk
      </code>
      
       keeps one risk class, or <code>
        none
      </code>
      
       for the agents with none yet; <code>
        owner
      </code>
      
       is <code>
        none
      </code>
      
       for the agents in the catalog, not retired, that no person answers for, or <code>
        unused
      </code>
      
       for the items nobody started in 90 days.
    </td>
    
    <td>
      The inventory.
    </td>
  </tr>
</tbody>
</table>

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](https://docs.oz0.ai/getting-started/web-ui/tenants#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.

<table>
<thead>
  <tr>
    <th>
      Field
    </th>
    
    <th>
      In
    </th>
    
    <th>
      What
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        every_tenant
      </code>
      
      , <code>
        tenants
      </code>
    </td>
    
    <td>
      the request
    </td>
    
    <td>
      The tenants to read; one or the other, not both.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tenant
      </code>
    </td>
    
    <td>
      each row
    </td>
    
    <td>
      The row's tenant: a job summary's, a usage total's, an approval's.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tenants_read
      </code>
    </td>
    
    <td>
      the response
    </td>
    
    <td>
      The tenants whose rows the answer holds.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tenants_refused
      </code>
    </td>
    
    <td>
      the response
    </td>
    
    <td>
      Tenants 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.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        tenants_unread
      </code>
    </td>
    
    <td>
      the response
    </td>
    
    <td>
      Approvals 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.
    </td>
  </tr>
</tbody>
</table>

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:

<table>
<thead>
  <tr>
    <th>
      Flag
    </th>
    
    <th>
      What
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      <code>
        --limit
      </code>
    </td>
    
    <td>
      Rows a page: the server's 25 when not set, at most 100.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        --page-token
      </code>
    </td>
    
    <td>
      Go on after the page before, with the token it printed.
    </td>
  </tr>
  
  <tr>
    <td>
      <code>
        --all
      </code>
    </td>
    
    <td>
      Read every page (100 rows at a time unless <code>
        --limit
      </code>
      
       says otherwise).
    </td>
  </tr>
</tbody>
</table>

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:

```bash
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.

```go
for job, err := range c.AllJobs(ctx, false) {
    if err != nil {
        return err
    }
    fmt.Println(job.GetId())
}
```

```python
for job in client.iter_jobs():
    print(job.id)
```

```ts
for await (const job of client.allJobs()) console.log(job.id);
```


## Sitemap

See the full [sitemap](https://docs.oz0.ai/sitemap.md) for all pages.
