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

# Pagination

> Page through list endpoints with limit and offset, or with a cursor on the changes feed.

List endpoints return one page of results in `data` and a `pagination`
block:

```json theme={null}
{
  "data": [],
  "pagination": { "limit": 25, "offset": 0, "total": 33 }
}
```

`total` appears only when an exact count is available. It is `null` or absent
on relevance search and on trials.

## Limits per endpoint

| Endpoint                                       | `limit` range (default) | Paging                                  | Exact `total`                                |
| ---------------------------------------------- | ----------------------- | --------------------------------------- | -------------------------------------------- |
| `/v1/programs`                                 | 1–5000 (50)             | `offset`                                | `include_total=true` on filter-only listings |
| `/v1/companies`, `/v1/companies/{id}/programs` | 1–5000 (50)             | `offset`                                | No                                           |
| `/v1/trials`                                   | 1–1000 (100)            | `offset`; `offset + limit` at most 1000 | No                                           |
| `/v1/companies/{id}/trials`                    | 1–1000 (100)            | `offset`                                | `include_total=true`                         |
| `/v1/deals`                                    | 1–100 (25)              | `offset` up to 100,000                  | `include_total=true`                         |
| `/v1/patents`                                  | 1–100 (25)              | `offset` up to 10,000                   | `include_total=true`                         |
| `/v1/fda`                                      | 1–100 (25)              | `offset` up to 100,000                  | `include_total=true`                         |
| `/v1/changes`, `/v1/companies/{id}/changes`    | 1–5000 (50)             | `cursor`                                | No                                           |

Each page is one request against your [rate limit](/rate-limits).

## Offset paging

Request pages of `limit` rows, increasing `offset` by `limit`, until a page
returns fewer than `limit` rows.

<CodeGroup>
  ```python Python theme={null}
  import os
  import requests

  API = "https://api.pav.bio"
  HEADERS = {"Authorization": f"Bearer {os.environ['PAV_API_KEY']}"}

  deals, offset, limit = [], 0, 100
  while True:
      resp = requests.get(
          f"{API}/v1/deals",
          headers=HEADERS,
          params={"company_id": 218, "limit": limit, "offset": offset},
      )
      resp.raise_for_status()
      page = resp.json()["data"]
      deals.extend(page)
      if len(page) < limit:
          break
      offset += limit

  print(f"fetched {len(deals)} deals")
  ```

  ```javascript JavaScript theme={null}
  const API = "https://api.pav.bio";
  const headers = { Authorization: `Bearer ${process.env.PAV_API_KEY}` };

  const deals = [];
  const limit = 100;
  for (let offset = 0; ; offset += limit) {
    const params = new URLSearchParams({ company_id: "218", limit: String(limit), offset: String(offset) });
    const resp = await fetch(`${API}/v1/deals?${params}`, { headers });
    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
    const { data } = await resp.json();
    deals.push(...data);
    if (data.length < limit) break;
  }
  console.log(`fetched ${deals.length} deals`);
  ```
</CodeGroup>

## Cursor paging on the changes feed

The changes feed returns `next_cursor` when more events exist. Pass it back as
`cursor` with the same filters. The last page has no `next_cursor`. Cursors stay
correct while new events arrive, so no event is skipped or repeated. Treat the
cursor as opaque.

<CodeGroup>
  ```python Python theme={null}
  import os
  import requests

  API = "https://api.pav.bio"
  HEADERS = {"Authorization": f"Bearer {os.environ['PAV_API_KEY']}"}

  params = {"since": "2026-09-01T00:00:00Z", "change_type": "update", "limit": 500}
  events = []
  while True:
      resp = requests.get(f"{API}/v1/changes", headers=HEADERS, params=params)
      resp.raise_for_status()
      body = resp.json()
      events.extend(body["data"])
      if not body.get("next_cursor"):
          break
      params["cursor"] = body["next_cursor"]

  print(f"fetched {len(events)} phase changes")
  ```

  ```javascript JavaScript theme={null}
  const API = "https://api.pav.bio";
  const headers = { Authorization: `Bearer ${process.env.PAV_API_KEY}` };

  const params = new URLSearchParams({ since: "2026-09-01T00:00:00Z", change_type: "update", limit: "500" });
  const events = [];
  while (true) {
    const resp = await fetch(`${API}/v1/changes?${params}`, { headers });
    if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
    const body = await resp.json();
    events.push(...body.data);
    if (!body.next_cursor) break;
    params.set("cursor", body.next_cursor);
  }
  console.log(`fetched ${events.length} phase changes`);
  ```

  ```bash cURL theme={null}
  PARAMS=(--data-urlencode "since=2026-09-01T00:00:00Z" --data-urlencode "change_type=update" --data-urlencode "limit=500")
  CURSOR=""
  while : ; do
    RESP=$(curl -sG https://api.pav.bio/v1/changes -H "Authorization: Bearer $PAV_API_KEY" \
      "${PARAMS[@]}" ${CURSOR:+--data-urlencode "cursor=$CURSOR"})
    echo "$RESP" | jq '.data | length'
    CURSOR=$(echo "$RESP" | jq -r '.next_cursor // empty')
    [ -z "$CURSOR" ] && break
  done
  ```
</CodeGroup>

## Relevance search pages

Relevance search (`q` on programs, companies and trials) pages with `offset`
the same way. Results are ranked, so later pages hold weaker matches; most
workflows need only the first few pages.
