Mastering Twitter API v2 Pagination with next_token
Learn how to use Twitter API v2's next_token for reliable cursor‑based pagination, see a Python example that handles rate limits, and understand the trade‑offs involved.
03 Jan 2026, 12:47 UTC

Why pulling large tweet sets can go wrong
When you request a recent‑search stream without handling pagination, the API returns only the first page (up to 100 tweets). If you keep issuing the same request you either get duplicate data or hit the rate limit because you’re making unnecessary calls. Missing the next_token field means you can’t guarantee you’ve seen every matching tweet, and you risk incomplete analysis or unexpected 429 errors.
How cursor‑based pagination works in Twitter API v2
Twitter’s v2 endpoints use a cursor model: each response includes a meta.next_token value that acts as an opaque pointer to the next logical page. Supplying that token in the pagination_token query parameter tells the service to continue where it left off, preserving order and avoiding duplicates. When there are no more results, next_token is absent or set to null.
Worked example: Python loop that respects rate limits
The following snippet shows a robust way to fetch all recent tweets matching a query. It runs wherever you have Python 3.8+ and the requests library installed. Replace YOUR_BEARER_TOKEN with a valid app‑only bearer token.
import time
import requests
BEARER_TOKEN = "YOUR_BEARER_TOKEN"
HEADERS = {"Authorization": f"Bearer {BEARER_TOKEN}"}
BASE_URL = "https://api.twitter.com/2/tweets/search/recent"
params = {
"query": "from:TwitterDev -is:retweet",
"max_results": 100, # max per page
"tweet.fields": "created_at,author_id"
}
all_tweets = []
while True:
resp = requests.get(BASE_URL, headers=HEADERS, params=params)
if resp.status_code == 429:
# Simple exponential back‑off; adjust as needed
reset = int(resp.headers.get("x-rate-limit-reset", time.time() + 60))
sleep_for = max(reset - int(time.time()), 1)
print(f"Rate limited – sleeping {sleep_for}s")
time.sleep(sleep_for)
continue
resp.raise_for_status()
data = resp.json()
all_tweets.extend(data.get("data", []))
meta = data.get("meta", {})
next_token = meta.get("next_token")
if not next_token:
break
params["pagination_token"] = next_token
# Be courteous: pause a bit between pages
time.sleep(1)
print(f"Collected {len(all_tweets)} tweets")
The loop continues until next_token is missing, handling 429 responses with a back‑off based on the x-rate-limit-reset header. Adjust the sleep interval if you need tighter control over the 300‑request/15‑minute window.
Trade‑offs and limitations
- Complexity: You must manage state (the token) and implement retry/back‑off logic, which adds code compared to a simple offset‑based approach.
- Token expiry: If the underlying tweet set changes dramatically (e.g., a large volume of new matching tweets), the token may become stale. In rare cases you’ll need to restart from the beginning.
- Rate‑limit awareness: Each paginated request counts toward the endpoint’s limit; looping without pause can quickly exhaust your allowance.
Despite these costs, cursor pagination eliminates duplicate or missing data and reduces the number of unnecessary calls compared to naïvely re‑issuing the same query.
Verification steps
- Call the recent‑search endpoint with a test query and no pagination token:
GET /2/tweets/search/recent?query=twitter&max_results=10. - Inspect the JSON response; confirm the presence of a
meta.next_tokenfield. - Make a second request adding
&pagination_token=<value from step 2>and verify that the returned tweets do not overlap with the first page. - Repeat until
next_tokenis absent; count the total tweets and compare with the sum ofresult_countvalues across pages to ensure no loss.
Refer to the official pagination section on developer.twitter.com for the definitive behavior.
Actionable closing
Start by wrapping your existing API call in a while‑loop that checks for next_token, adds exponential back‑off for 429 responses, and respects the per‑endpoint limit. Test with a small query set first, then scale up. With cursor‑based pagination in place you’ll collect complete, ordered tweet sets without wasting requests or risking data gaps.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.