> ## Documentation Index
> Fetch the complete documentation index at: https://usefoil.com/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Paginate Foil API list endpoints with cursor-based pagination, including limit and cursor parameters, filters, and SDK auto-pagination helpers.

Foil uses **cursor-based pagination** on every list endpoint. Cursors are opaque — don't parse them, construct them, or rely on their format. They're stable across process restarts and safe to persist in your own database if you want to resume a long iteration.

## Response envelope

Every list response has the same shape:

```json theme={"dark"}
{
  "data": [
    { "object": "session", "id": "sid_...", "...": "..." },
    { "object": "session", "id": "sid_...", "...": "..." }
  ],
  "pagination": {
    "limit": 20,
    "has_more": true,
    "next_cursor": "cur_AbC1234..."
  },
  "meta": {
    "request_id": "req_..."
  }
}
```

| Field                    | Type             | Meaning                                                                                     |
| ------------------------ | ---------------- | ------------------------------------------------------------------------------------------- |
| `data`                   | array            | Up to `limit` items for this page.                                                          |
| `pagination.limit`       | number           | The effective page size the server used.                                                    |
| `pagination.has_more`    | boolean          | `true` if more pages exist beyond this one.                                                 |
| `pagination.next_cursor` | string, optional | Present when `has_more` is `true`. Pass it as `cursor` on the next request.                 |
| `meta.request_id`        | string           | Per-request ID — see [Errors → Using `request_id`](/api-reference/errors#using-request_id). |

Individual resources aren't returned inside an extra envelope — they unwrap to the resource object itself — but list responses always use the shape above.

## Request parameters

Every list endpoint accepts these two query parameters:

| Parameter | Type   | Default | Max   | Notes                                                                                                  |
| --------- | ------ | ------- | ----- | ------------------------------------------------------------------------------------------------------ |
| `limit`   | number | `20`    | `100` | Items per page. Smaller pages keep responses fast; larger pages reduce round trips for bulk iteration. |
| `cursor`  | string | —       | —     | The `pagination.next_cursor` from a prior page. Omit on the first request.                             |

Individual endpoints may expose additional filter parameters (e.g. `verdict`, `search`, `sort`) on top of these. See the per-resource filter sections below.

## Iterating

The canonical pattern: call the endpoint, process `data`, and loop while `has_more` is true, passing `next_cursor` forward.

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  const { Foil } = require("@abxy/foil-server");
  const client = new Foil({ secretKey: process.env.FOIL_SECRET_KEY });

  async function iterateSessions() {
    let cursor = undefined;
    do {
      const page = await client.sessions.list({ limit: 100, cursor });
      for (const session of page.items) {
        await processSession(session);
      }
      cursor = page.has_more ? page.next_cursor : undefined;
    } while (cursor);
  }
  ```

  ```python Python theme={"dark"}
  from foil_server import Foil
  import os

  client = Foil(secret_key=os.environ["FOIL_SECRET_KEY"])

  def iterate_sessions():
      cursor = None
      while True:
          page = client.sessions.list(limit=100, cursor=cursor)
          for session in page.items:
              process_session(session)
          if not page.has_more:
              break
          cursor = page.next_cursor
  ```

  ```go Go theme={"dark"}
  import foil "github.com/abxy-labs/foil-server-go"

  func iterateSessions(ctx context.Context, client *foil.Client) error {
      var cursor string
      for {
          page, err := client.Sessions.List(ctx, foil.SessionListParams{
              Limit:  100,
              Cursor: cursor,
          })
          if err != nil {
              return err
          }
          for _, s := range page.Items {
              processSession(s)
          }
          if !page.HasMore {
              return nil
          }
          cursor = page.NextCursor
      }
  }
  ```

  ```ruby Ruby theme={"dark"}
  require "foil/server"

  client = Foil::Server::Client.new(secret_key: ENV["FOIL_SECRET_KEY"])

  cursor = nil
  loop do
    page = client.sessions.list(limit: 100, cursor: cursor)
    page.items.each { |s| process_session(s) }
    break unless page.has_more
    cursor = page.next_cursor
  end
  ```

  ```php PHP theme={"dark"}
  $client = new \Foil\Server\Client([
      "secret_key" => getenv("FOIL_SECRET_KEY"),
  ]);

  $cursor = null;
  do {
      $page = $client->sessions()->list([
          "limit" => 100,
          "cursor" => $cursor,
      ]);
      foreach ($page->items as $session) {
          processSession($session);
      }
      $cursor = $page->has_more ? $page->next_cursor : null;
  } while ($cursor !== null);
  ```

  ```bash cURL theme={"dark"}
  # First page
  curl "https://api.usefoil.com/v1/sessions?limit=100" \
    -H "Authorization: Bearer sk_live_..."

  # Subsequent page — pass the next_cursor from the previous response
  curl "https://api.usefoil.com/v1/sessions?limit=100&cursor=cur_AbC1234..." \
    -H "Authorization: Bearer sk_live_..."
  ```
</CodeGroup>

## Auto-pagination in the SDKs

The server SDKs ship with an auto-pagination helper that abstracts the cursor loop. Use it when you want to iterate the entire result set without managing state.

<CodeGroup>
  ```javascript Node.js theme={"dark"}
  for await (const session of client.sessions.listAll({ limit: 100 })) {
    await processSession(session);
  }
  ```

  ```python Python theme={"dark"}
  for session in client.sessions.list_all(limit=100):
      process_session(session)
  ```

  ```go Go theme={"dark"}
  iter := client.Sessions.ListAll(ctx, foil.SessionListParams{Limit: 100})
  for iter.Next() {
      processSession(iter.Value())
  }
  if err := iter.Err(); err != nil {
      return err
  }
  ```
</CodeGroup>

Auto-pagination makes one API call per page and yields items as it goes — safe to use on result sets that wouldn't fit in memory, and it respects the same rate limits as manual iteration.

## Filters and sorting

Filter parameters layer on top of the standard pagination params. They narrow the result set but don't change the envelope shape.

### Sessions

| Parameter | Type                                 | Notes                                                |
| --------- | ------------------------------------ | ---------------------------------------------------- |
| `verdict` | `"bot" \| "human" \| "inconclusive"` | Filter to a single verdict category.                 |
| `search`  | string                               | Fuzzy match against session ID and request metadata. |

### Fingerprints

| Parameter | Type                           | Notes                                              |
| --------- | ------------------------------ | -------------------------------------------------- |
| `search`  | string                         | Fuzzy match against visitor ID, user agent, or IP. |
| `sort`    | `"seen_count" \| "first_seen"` | Orders results. Default is most-recently-seen.     |

### API keys

| Parameter         | Type | Notes                                         |
| ----------------- | ---- | --------------------------------------------- |
| `limit`, `cursor` | —    | Pagination only. No additional filters today. |

Combining filters and pagination is supported: pass the filters once, and carry the cursor forward as normal. The server applies filters before paginating, so `limit` controls page size of the filtered result set.

## Caveats

* **Don't construct cursors.** They're opaque tokens — the server may change their encoding. If you need to page from a specific timestamp or ID, use a filter parameter instead.
* **Cursor lifetimes.** Cursors remain valid as long as the underlying result set does. For sessions (15-minute durable window) and fingerprints (365-day sliding window), a cursor minted today is valid until the records it points into expire.
* **Result stability under concurrent writes.** New records created during a long iteration may or may not appear in later pages — cursors provide "snapshot-like" stability without strong isolation guarantees. For auditing across a fixed window, pass a time filter when one is available for that resource.
* **Ordering.** Each list endpoint documents its default sort. Don't assume an order that isn't explicitly documented.

## What's next

<CardGroup cols={2}>
  <Card title="Authentication" icon="lock-keyhole" href="/api-reference/authentication">
    Key types and scopes.
  </Card>

  <Card title="Errors" icon="triangle-alert" href="/api-reference/errors">
    Envelope shape, status codes, retry semantics.
  </Card>

  <Card title="Sessions" icon="monitor" href="/api-reference/sessions">
    The most common list endpoint.
  </Card>

  <Card title="Fingerprints" icon="fingerprint" href="/api-reference/fingerprints">
    Durable visitor fingerprint lookup.
  </Card>
</CardGroup>
