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

# Pagination

> Cursor-based pagination via PaginationRequest / PaginationResponse — no offsets, no skips, no missed records.

Every list endpoint in the SDK uses **cursor-based pagination** through the shared types in [`sdk/shared/v1/pagination.proto`](https://github.com/anghami/sdk/blob/main/sdk/shared/v1/pagination.proto). There is no `offset` parameter, anywhere.

## Request

```protobuf theme={null}
message PaginationRequest {
  int32 page_size = 1;   // 1–100, default 20
  string page_token = 2; // empty for first page
}
```

| Field        | Notes                                                                                                               |
| ------------ | ------------------------------------------------------------------------------------------------------------------- |
| `page_size`  | Validated server-side: `1 ≤ page_size ≤ 100`. Out-of-range values return `ERROR_CODE_INVALID_REQUEST`.              |
| `page_token` | Opaque string returned by the previous response. Don't construct it yourself. Empty string requests the first page. |

## Response

```protobuf theme={null}
message PaginationResponse {
  string next_page_token = 1; // empty if no more pages
}
```

List requests carry `page_size` and `page_token` directly as top-level fields (not nested under a `pagination` object). Responses similarly expose a top-level `next_page_token`. When it's empty, you have reached the end.

## Iterating

```ts theme={null}
import { discovery } from "@anghami/sdk";

let pageToken = "";
const all = [];

for (;;) {
  const res = await discovery.search({
    q: "Fairouz",
    pageSize: 50,
    pageToken,
  });
  all.push(...res.results);
  if (!res.nextPageToken) break;
  pageToken = res.nextPageToken;
}
```

```python theme={null}
page_token = ""
results = []
while True:
    res = discovery.Search(SearchRequest(
        q="Fairouz",
        page_size=50,
        page_token=page_token,
    ))
    results.extend(res.results)
    if not res.next_page_token:
        break
    page_token = res.next_page_token
```

## Why cursors

* **No skipped or duplicated records when the catalog changes mid-scan.** Offsets shift when items are inserted/deleted; cursors don't.
* **Stable performance.** Server-side cursor lookups are O(1) regardless of page depth.
* **Token opacity.** The token encodes server-internal state (sort key, snapshot ID, etc.). Don't parse it; don't reuse a token across endpoints.

## Tips

* Pick the largest `page_size` (up to 100) you can render — fewer round-trips, lower rate-limit pressure.
* Tokens are tied to the originating request shape. If you change `query`, filters, or sort order, start from an empty token.
* Tokens are short-lived. Treat 24 hours as a soft cap — if you persist a token longer, expect to restart pagination on stale-token errors.
