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.
Tarun Khatri
Published · Updated · 7 min read
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"
- Read next_tokenIt sits in meta while more results exist.
- Send it backAs pagination_token, with the same query.
- Cap the pagesmax_pages stops a runaway bill.
- Page Weir by nextCursorStop only on null.
Step 1: Read next_token and send it back as pagination_token
X's pagination page describes three tokens:
next_tokensits in the response'smetawhile more results exist.previous_tokensits there too, and goes back one page.pagination_tokenis 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:
- First poll: set
since_idto the newest post id you've seen. - More pages: send
next_tokenwith the samesince_iduntil the token runs out. - Next poll: set
since_idto thenewest_idfrom 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:
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:
nullis 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
429or402. Wait out a429; a Weir402means 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:
- X API pagination, general and search: https://docs.x.com/x-api/fundamentals/pagination and https://docs.x.com/x-api/posts/search/integrate/paginate
- X API Search Recent Posts reference: https://docs.x.com/x-api/posts/search-recent-posts
- X OpenAPI spec: https://docs.x.com/openapi.json
- X API timelines: https://docs.x.com/x-api/posts/timelines/introduction
- X API rate limits and pricing: https://docs.x.com/x-api/fundamentals/rate-limits and https://docs.x.com/x-api/getting-started/pricing
- Tweepy Paginator: https://docs.tweepy.org/en/stable/v2_pagination.html
- Weir: https://docs.weirapp.io/pagination and https://docs.weirapp.io/billing
- Our @duolingo search through Weir, 22 October 2026, 4 credits