> ## Documentation Index
> Fetch the complete documentation index at: https://developers.beta.dealroom.co/llms.txt
> Use this file to discover all available pages before exploring further.

# Pagination

> Use the `cursor` query parameter to walk through results, with `limit` controlling page size and `include_total` opting into a total count.

List endpoints support **two pagination styles**, pickable per request:

* **Cursor (keyset) pagination** — round-trip `page.next_cursor` / `page.prev_cursor` via `?cursor=`. O(page size) regardless of depth. Recommended for browsing UIs and forward/backward walking.
* **Offset pagination** — classic `?limit=&offset=`. Supports random access — jump straight to any page within your plan's depth cap (see **Per-plan caps** below). Cost grows with the `offset` value.

Both produce the same response envelope; only the request shape differs. Cursor wins when both are sent (`?cursor=X&offset=N` ignores the offset).

<Note>
  Need the older `offset`-based shape? Send `API-Version: 2026-05-22` (or any earlier date) and the response will use the legacy `{ limit, offset, total }` envelope. Old clients keep working unchanged.
</Note>

## Parameters

| Parameter       | Type    | Default | Description                                                                                                                                                                                                |
| --------------- | ------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `cursor`        | string  | —       | Opaque token from a previous response's `page.next_cursor` or `page.prev_cursor`. **When present, the cursor carries its own `limit` and `offset` — `?limit=` and `?offset=` on the request are ignored.** |
| `limit`         | integer | `25`    | Page size. Used when no `cursor` is sent. Max varies by endpoint.                                                                                                                                          |
| `offset`        | integer | `0`     | Number of rows to skip. Used when no `cursor` is sent (offset pagination). Echoed in `page.offset` when a cursor is used (the cursor carries it).                                                          |
| `include_total` | boolean | —       | Pass `true` to include `page.total` (runs a separate COUNT query).                                                                                                                                         |

## Limits

Page size and pagination depth are separate axes: page size bounds a single response, depth
(`offset + limit`) bounds how far you can page. Each endpoint has a hard page-size ceiling:

| Endpoint                                                                 | Max `limit` |
| ------------------------------------------------------------------------ | ----------- |
| `/data/companies`, `/data/entities`, `/data/investors`, `/data/founders` | 500         |
| `/data/transactions`, `/data/valuations`, `/data/jobs`, `/data/news`     | 2000        |
| `/data/companies/geo`                                                    | 5000        |
| `/analytics/*` (aggregates)                                              | 500         |
| `/reference/*` (taxonomy)                                                | 1000        |

**Per-plan caps.** Requests without an API key get a lower page size and a bounded pagination depth,
by plan:

| Plan               | Max page size        | Max depth (`offset + limit`) |
| ------------------ | -------------------- | ---------------------------- |
| Anonymous (no key) | 50                   | 750                          |
| Free               | 100                  | 5,000                        |
| Premium            | 500                  | 50,000                       |
| API key            | endpoint max (above) | unbounded                    |

Page size is **clamped**, not rejected — an over-limit request returns the reduced size and sets
`page.capped` to `true` (see the response shape below). The per-plan page size applies to the core
list endpoints only (`companies`, `entities`, `investors`, `founders`, `transactions`, `valuations`,
`jobs`, `news`); `geo`, taxonomy, and aggregate endpoints are not per-plan page-capped.

Depth applies to every list endpoint, on both the offset and cursor styles. A request beyond the cap
returns `400` with error code `PAGINATION_DEPTH_EXCEEDED` — the page is **not** silently truncated.

## Response shape

```json theme={null}
{
  "data": [ ... ],
  "page": {
    "limit": 25,
    "offset": 0,
    "next_cursor": "eyJ2IjoxLCJkIjoibmV4dCJ9...",
    "prev_cursor": null,
    "total": 5634,
    "capped": false,
    "tier": "free",
    "ecosystem": null
  }
}
```

* `page.limit` — applied page size (carried by the cursor on subsequent pages)
* `page.offset` — the server's view of where this page sits in the result set (`0` for the first page, `limit` for the second, etc.). Use it for "showing rows X–Y of Z" display
* `page.next_cursor` — token to fetch the next page, or `null` when this is the last page
* `page.prev_cursor` — token to fetch the previous page, or `null` when this is the first page
* `page.total` — total matching records; present only when `include_total=true`
* `page.capped` — `true` when the requested `limit` was reduced to the per-plan page-size cap. Present on capped list endpoints for requests without an API key
* `page.tier` — the access tier applied to the request: `anonymous`, `free`, or `premium`. Present alongside `capped`
* `page.ecosystem` — slug of the active ecosystem when the request is scoped to one (resolved from the request origin), or `null` when global. Present alongside `capped`

## Walking forward (cursor)

```bash theme={null}
# Page 1: no cursor — set sort + filter + limit
curl "https://api.beta.dealroom.app/data/entities?sort=-launch_date&filter=organization_subtype%5Beq%5D%3Acompany&limit=25" \
  -H "Authorization: Bearer $TOKEN"

# Page 2 onward: round-trip the cursor AND echo the same sort + filter back.
# The cursor carries limit/offset for you; sort and filter must match the
# request that minted the cursor or the server returns 400.
curl "https://api.beta.dealroom.app/data/entities?cursor=$NEXT&sort=-launch_date&filter=organization_subtype%5Beq%5D%3Acompany" \
  -H "Authorization: Bearer $TOKEN"

# Keep going until page.next_cursor is null
```

## Random access (offset)

```bash theme={null}
# Jump straight to page 11 (offset=250 with limit=25):
curl "https://api.beta.dealroom.app/data/entities?sort=-launch_date&limit=25&offset=250" \
  -H "Authorization: Bearer $TOKEN"
```

Offset cost grows with the offset value — for deep pages, prefer cursor walking. Offset is fine for small jumps (a few hundred rows) and for one-shot fetches. Random access is bounded: an `offset + limit` beyond your plan's depth cap returns `400 PAGINATION_DEPTH_EXCEEDED` (see **Per-plan caps** above).

## Walking back

```bash theme={null}
# From any page, replay page.prev_cursor to step back one page. Echo the
# same sort + filter as the request that minted the cursor.
curl "https://api.beta.dealroom.app/data/entities?cursor=$PREV&sort=-launch_date&filter=organization_subtype%5Beq%5D%3Acompany" \
  -H "Authorization: Bearer $TOKEN"
```

`prev_cursor` is `null` exactly when you're on the first page.

For a "Page N of M" UI, read `page.offset` and `page.limit` straight from the response — the cursor token already carries the current offset, so the server reports it back without the client doing any arithmetic.

## Cursor rules

* **Opaque** — treat the cursor as a black box. Do not decode, modify, or construct it manually.
* **Carries limit/offset only** — the cursor token embeds page size and offset, so `?limit=` and `?offset=` are ignored when `?cursor=` is sent. To change page size, restart from the first page with the new `?limit=`.
* **Echo `sort` and `filter` on every request** — the cursor does NOT contain the sort or filter expression. The server compares the cursor's fingerprints against the *resolved* sort and filter of the current request, so the rule is "the request's resolved sort/filter must match what minted the cursor." In practice that means resending the same `?sort=` and `?filter=` you used on page 1. Technically, if a cursor was minted under default sort and empty filter, omitting both is fine because the defaults still match — but the safe pattern is to always echo them so client code doesn't break the moment a non-default sort or filter is in play. Mismatch returns a 400.
* **Bound to sort** — changing `?sort=` mid-paging returns `400 Invalid cursor for this sort order; restart from the first page`.
* **Bound to filter** — changing `?filter=` mid-paging returns `400 Cursor is bound to a different filter; restart from the first page`. Currency (`?currency=`) is **not** bound — switching currencies mid-paging is safe.
* **No random jumps** — cursors are sequential: no cursor jumps straight to an arbitrary page (e.g. "page 47"). Walk `next_cursor` / `prev_cursor`, or restart from the first page. (Offset pagination *does* allow random access, but both styles are bounded by the per-plan depth cap above.)

## Including the total count

By default `page.total` is omitted — cursor pages stay cheap. Pass `include_total=true` to include it:

```bash theme={null}
curl "https://api.beta.dealroom.app/data/entities?sort=-launch_date&limit=25&include_total=true" \
  -H "Authorization: Bearer $TOKEN"
```

For large datasets, skip the total count when you don't need it — omitting it avoids a full-table count query.

<Note>
  In-memory list endpoints (`/platform/ecosystems`, `/platform/teams`, `/platform/api-keys`) keep the legacy `{ limit, offset, total }` shape even on the new version — their datasets are small and bounded, so cursor pagination offers no benefit.
</Note>
