WEIR

X API Pagination (2026): next_token and pagination_token

X API pagination from X's docs: next_token vs pagination_token, max_results per endpoint, since_id polling, cost per page, and tested Python and JS loops.


Published · Updated · 7 min read

Cover image: Stop on null, not on empty

X API pagination works like this: each response's meta holds a next_token, and you send it back as pagination_token until none comes back.

This guide is for developers paging through X (Twitter) results. You get X's rules per endpoint, the cost of each page, and tested loops in Python and JavaScript. We run Weir, the API in Steps 5 and 6.

Page two of a search, with the token from page one:

curl "https://api.x.com/2/tweets/search/recent?query=from%3AXDevelopers&max_results=100&pagination_token=NEXT_TOKEN" \
  -H "Authorization: Bearer $X_BEARER_TOKEN"
  1. Read next_tokenIt sits in meta while more results exist.
  2. Send it backAs pagination_token, with the same query.
  3. Cap the pagesmax_pages stops a runaway bill.
  4. Page Weir by nextCursorStop only on null.

X's pagination page describes three tokens:

  • next_token sits in the response's meta while more results exist.
  • previous_token sits there too, and goes back one page.
  • pagination_token is the request parameter you set to either one.

On the last page, "next_token is omitted," in X's words. That missing key is your stop signal.

Pages run newest first. X says tokens are opaque strings, so don't parse or edit them.

Step 2: Set max_results for each X API endpoint

Each endpoint has its own page size and rate limit. Common ones, from X's docs as of October 2026:

Endpoint max_results Token you send Requests per 15 min, per app
GET /2/tweets/search/recent 10 to 100 Either name 450
GET /2/tweets/search/all 10 to 500 Either name 300, and 1 a second
GET /2/users/:id/tweets 5 to 100 pagination_token 10,000
GET /2/users/:id/followers 1 to 1,000 pagination_token 300
GET /2/lists/:id/members 1 to 100 pagination_token 900
GET /2/users/search 1 to 1,000 next_token only 300

X's advice is to "request the maximum allowed max_results" so you make fewer calls.

A short page is not the end, either. With fewer results than max_results, X's pagination page says "more may still exist."

Timelines have a limit of their own: X's timelines page says paging reaches only a user's 3,200 most recent posts.

How does X search use next_token?

Search is where the two names overlap. Recent and full-archive search accept next_token or pagination_token, and X's reference says "at most one" may be sent.

The docs disagree on whether tokens last. X's search guide says the next_token "does not expire," while its general pagination page says tokens "may expire after some time."

So don't build a resume plan on an old token. Save post ids instead.

Step 3: Poll with since_id and resume with until_id

To check for new posts, X says to use since_id instead of paging again. Its search guide gives the steps:

  1. First poll: set since_id to the newest post id you've seen.
  2. More pages: send next_token with the same since_id until the token runs out.
  3. Next poll: set since_id to the newest_id from that poll's first page.

until_id works the other way, returning posts before an id. That makes it a safe way to resume a long backfill: save each search page's meta.oldest_id, and restart with until_id set to it.

Step 4: Write the X API pagination loop in Python

This works on every endpoint in the table except user search. The max_pages cap stops a broad query from billing for hours:

import os, requests

X_API = os.environ.get("X_API_URL", "https://api.x.com")

def x_pages(path, params, max_pages=5):
    head = {"Authorization": f"Bearer {os.environ['X_BEARER_TOKEN']}"}
    for _ in range(max_pages):
        r = requests.get(f"{X_API}/2/{path}", params=params, headers=head, timeout=30)
        r.raise_for_status()  # 429: wait until x-rate-limit-reset
        page = r.json()
        yield from page.get("data", [])  # an empty last page has no "data"
        token = page["meta"].get("next_token")
        if not token:
            return
        params = {**params, "pagination_token": token}

Call x_pages("users/2244994945/tweets", {"max_results": 100}) for @XDevelopers' timeline.

We tested it on 22 October 2026 against a local stub with the meta shapes from X's docs. It made two requests and stopped.

Prefer a library? Tweepy's Paginator does the same with a limit, as our Tweepy tutorial shows.

What does each page cost on the X API?

Each page is one request against the 15-minute window, and each item on it is billed. From X's pricing page, as of October 2026:

$0.50a full 100-post page of recent search
$2.50a full 500-post page of full-archive search
$10a full page of 1,000 followers

Posts cost $0.005 each and followers $0.010. X bills a resource once per UTC day, so reading the same page twice that day costs nothing extra.

At 450 recent searches per 15 minutes, one app reads at most 45,000 posts in that window. Our X API rate limits guide covers the 429 and its reset header.

Step 5: Page Weir's cursor until nextCursor is null

Weir pages with one cursor. Send meta.nextCursor back as cursor: in the body for search, in the query string for GET endpoints.

Weir's pagination docs set three rules:

  • null is the only stop. A page can come back empty with more behind it.
  • The cursor is bound to its request. Change any filter and you get a 400.
  • Store it exactly as given to resume later. It's signed, not an offset.

Short pages are normal: X serves about 20 posts per search page, even when limit is 100. On X, the last page with posts usually still has a cursor, so a full walk ends with one empty call for 1 credit.

import os, requests
API = os.environ.get("WEIR_API_URL", "https://api.weirapp.io")
HEAD = {"Authorization": f"Bearer {os.environ['WEIR_API_KEY']}"}

def search_all(body, max_pages=10):
    cursor = None
    for page in range(1, max_pages + 1):
        payload = {**body, "cursor": cursor} if cursor else body
        r = requests.post(f"{API}/v1/x/search", json=payload, headers=HEAD, timeout=60)
        r.raise_for_status()  # 402 = out of credits; 429 = wait for Retry-After
        data, cursor = r.json()["data"], r.json()["meta"]["nextCursor"]
        print(f"page {page}: {len(data)} posts, more: {cursor is not None}")
        yield from data
        if cursor is None:  # the only stop: an empty page is not the end
            return

On 22 October 2026 we made one real call for @duolingo's three latest original posts. It cost 4 credits and returned three posts and a nextCursor.

A local stub replayed that response, then a hand-made empty page with a null cursor, the way a walk on X ends. search_all printed:

page 1: 3 posts, more: True
page 2: 0 posts, more: False

Step 6: The same Weir pagination loop in JavaScript

This version yields one page at a time and runs on Node 18 or later:

const API = process.env.WEIR_API_URL ?? "https://api.weirapp.io";
const KEY = process.env.WEIR_API_KEY;
const headers = { Authorization: `Bearer ${KEY}`, "Content-Type": "application/json" };

async function* searchPages(body, maxPages = 10) {
  for (let page = 1, cursor = null; page <= maxPages; page++) {
    const payload = JSON.stringify(cursor ? { ...body, cursor } : body);
    const res = await fetch(`${API}/v1/x/search`, { method: "POST", headers, body: payload });
    if (!res.ok) throw new Error(`Weir answered ${res.status}`); // 402: top up; 429: wait
    const { data, meta } = await res.json();
    yield data; // one page of posts, which can be empty
    if (meta.nextCursor === null) return; // the only stop
    cursor = meta.nextCursor;
  }
}

To test the cap, we made the stub return the real page, cursor and all, forever. searchPages(body, 3) read three pages and stopped.

Each Weir page costs 1 credit plus 1 per post, so a 20-post page is 21 credits.

If your loop stops early or never ends

  • It stops after one page. You stopped on a short or empty page; check the token instead.
  • It never ends. Add a page cap, because a broad query keeps finding posts.
  • Page two returns 400. On Weir, the body changed between pages. On X, send only one token name.
  • You get 429 or 402. Wait out a 429; a Weir 402 means no credits, so retrying won't help.

When not to use Weir

  • You need to post, or a contract with X. Weir is read-only and isn't licensed by X, so access can change.
  • You want 500 posts per request. X's full-archive search returns more per call.
  • You only page your own posts. X charges $0.001 per post for Owned Reads of your own timeline.

Check X's terms for your use; this isn't legal advice.

FAQ

What is next_token in the Twitter API?

It's a string in a response's meta that points to the next page. You send it back, usually as pagination_token, to get that page. When a response has no next_token, you've reached the last page.

What is the difference between next_token and pagination_token?

next_token is what X returns; pagination_token is the request parameter you send it back in. Search also accepts next_token as the parameter name, but X's reference says to send only one of the two.

How do I paginate the X API in Python?

Loop over requests, send meta.next_token back as pagination_token, and stop when it's missing. Cap the pages, since each one costs money. Tweepy's Paginator does this with a limit.

Does the X API next_token expire?

X's docs disagree: the search guide says it doesn't, the general pagination page says tokens may. To resume a long job safely, save the oldest post id and restart with until_id.

Sources

All checked on 22 October 2026. We read docs.x.com as Markdown, adding .md to each URL: