---
title: Pagination
description: One cursor shape across every list route — pass nextCursor back as cursor until it stops coming.
sidebar:
  order: 5
  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. Cursors have no such ceiling. |
| `limit` | integer | `20` | `1`–`100`. Larger values are clamped to `100`. Search feeds served by own accounts (`photos`, `videos`, `users`, or a fallback) answer at most `20` and say so in `limit`. |

| 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. Only the last page of a list is shorter than `limit`.

## Exact pages

A page holds exactly `limit` items. x.md assembles it from as many upstream pages as it takes (X serves ~30 posts or ~70 accounts at a time) and cuts it precisely. When the cut falls inside an upstream page, the continuation carries an offset — a cursor like `fxtwitter:DAAHCg…@15` — so the next request re-reads that page and skips what you already have. Nothing is lost, nothing repeats. A profile page continues from an exact position instead: its cursor is minted at the last post returned.

Cursor mode is still the cheap path: `limit=100` costs four or five upstream pages once, where `page=5` walks every earlier block again.

For an account's whole history do not page at all — [import it](/bulk-import).

:::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.
