How it works
Every Moderate and Contentful collection response has the same shape:
Page
{
"items": [ … ],
"count": 20,
"pagination": {
"next_cursor": "DAABCgAB…",
"has_more": true
}
}items | Records on this page. Posts, accounts or trends depending on the route. |
count | Number of records on this page. You pay for this many records. |
pagination.next_cursor | Opaque string for the next page, or null. |
pagination.has_more | true when a next cursor was returned. |
X sets the page size. There is no page-size parameter.
Request the next page
Pass the cursor exactly as received, URL-encoded.
cURL
curl -G "https://twitterxapi.com/api/v1/users/marclou/tweets" \
-H "Authorization: Bearer $TWITTERXAPI_KEY" \
--data-urlencode "cursor=DAABCgAB…"Read every page
import os, requests
def pages(path):
"""Yield every item from a collection endpoint."""
url = f"https://twitterxapi.com/api{path}"
headers = {"Authorization": f"Bearer {os.environ['TWITTERXAPI_KEY']}"}
cursor = None
while True:
params = {"cursor": cursor} if cursor else {}
response = requests.get(url, headers=headers, params=params, timeout=60)
response.raise_for_status()
page = response.json()
yield from page["items"]
cursor = page["pagination"]["next_cursor"]
if not page["pagination"]["has_more"] or not cursor:
return
for post in pages("/v1/users/marclou/tweets"):
print(post["id"], post["content"][:80])async function* pages(path) {
let cursor;
do {
const url = new URL(`https://twitterxapi.com/api${path}`);
if (cursor) url.searchParams.set("cursor", cursor);
const response = await fetch(url, {
headers: { Authorization: `Bearer ${process.env.TWITTERXAPI_KEY}` },
});
if (!response.ok) throw new Error(`HTTP ${response.status}`);
const page = await response.json();
yield* page.items;
cursor = page.pagination.has_more ? page.pagination.next_cursor : null;
} while (cursor);
}
for await (const post of pages("/v1/users/marclou/tweets")) {
console.log(post.id, post.content.slice(0, 80));
}Rules for reliable loops
- Stop when
has_moreis false ornext_cursoris empty. - Do not decode, edit or reuse a cursor with a different route or query.
- Save each page before requesting the next, and store the last cursor so a stopped job can resume.
- Deduplicate by ID. Live timelines and follower lists change while you read them.
- Retry 429 and 5xx responses with backoff. Honour
Retry-Afterwhen it is present. - Do not send
limit. Live routes reject it with400 limit_removed. - Raw responses have no
paginationobject. See Raw responses.
Larger collections
For an account's post history or a large search, a queued job collects the pages and delivers one file.