---
title: Pagination
description: One cursor shape across every list route — pass nextCursor back as cursor until it stops coming.
sidebar:
  order: 4
  icon: chevrons-right
---

Every list route paginates the same way — search, profiles, followers, and following, on both the permalink surface and `/api/v1/*`. Cursors are the preferred form; numbered pages exist for the cases where you cannot keep one.

## The shape

| Request parameter | Type | Default | Bounds |
| --- | --- | --- | --- |
| `cursor` | opaque string | — | The `nextCursor` from the previous response, unmodified. |
| `page` | integer | `1` | `1`–`10`. Larger values are clamped to `10`. Ignored when `cursor` is present. |
| `limit` | integer | `20` | `1`–`20`. Larger values are clamped to `20`. |

| Response field | Type | Meaning |
| --- | --- | --- |
| `nextCursor` | string, optional | Continuation token. **Absent means there is nothing more to fetch.** |
| `page` | integer | The effective page this response represents. |
| `limit` | integer | The effective limit applied to this response. |

Anything that is not a positive integer — a negative number, `0`, or text — falls back to the default rather than erroring.

## Prefer cursors

Pass `nextCursor` back as `cursor` with the same route, query, feed, and options:

```js
const url = new URL('https://x.pcstyle.dev/api/v1/search')
url.search = new URLSearchParams({
  q: 'typescript', feed: 'latest', format: 'json', limit: '20'
}).toString()

for (let page = 0; page < 3; page++) {
  const response = await fetch(url)
  if (!response.ok) throw new Error(`Request failed: ${response.status}`)
  const data = await response.json()
  console.log(data.posts)
  if (!data.nextCursor) break
  url.searchParams.set('cursor', data.nextCursor)
}
```

Stop when `nextCursor` is absent — not when a page comes back short. A page can be shorter than `limit`, or empty, and still have a continuation: profile results filter out replies and reposts *after* retrieval, and provider coverage varies.

A cursor fetches exactly one upstream page, so it is both faster and cheaper than walking to the same offset with `page`.

:::warning
Treat cursors as opaque. Do not decode them, strip their `fxtwitter:` or `xsearch:` prefix, or reuse one with a different route, query, or feed. A search cursor names the provider that issued it, and returning it elsewhere is undefined.
:::

Markdown responses carry the same thing as a **Continue** link at the end of the page, alongside a **Next page** link.

## Request a numbered page

```bash
curl -sS 'https://x.pcstyle.dev/api/v1/search?q=typescript&feed=latest&page=2'
```

Numbered pagination walks every preceding upstream page to reach the one you asked for, so `page=5` costs five upstream calls where a cursor costs one. It is slower, spends more of your [search allowance](/reliability#rate-limits), and stops at `page=10`. Use it for a bounded scan, or when a cursor cannot be carried across process boundaries; use cursors for everything else.

A supplied `cursor` takes precedence: `page` is ignored when both are sent.

## Where pagination does not apply

- **Degraded search.** When live search is unavailable, Latest and Top can fall back to web-indexed snippets. Those responses carry `X-Search-Degraded: true`, no `nextCursor`, and only a first page.
- **Post reads.** `/api/v1/posts` and `/{handle}/status/{id}` return a conversation, not a list. Their size is controlled by `thread`, `context`, and `replies` — see [posts and threads](/posts).

## Machine-readable

[`/openapi.json`](https://x.pcstyle.dev/openapi.json) describes the same contract twice: as `cursor`, `page`, and `limit` parameters with their bounds on every list operation, and as an `x-pagination` object naming the request parameters, the `nextCursor` response field, the limits, and the termination rule in one place.
