How to Fetch Tweets from an X List with TwexAPI
X Lists are useful because someone has already done part of the editorial work for you. A list groups accounts around a market, beat, community, or competitor set, so the resulting timeline is often cleaner than a broad keyword search.
This guide shows how to fetch tweets from a public X List with TwexAPI. Use the fixed-count endpoint when you need a quick snapshot, and use the cursor-based page endpoint when you are building a feed, archive, or recurring monitor.
Pick the Right Endpoint
TwexAPI exposes two practical ways to collect list tweets:
| Goal | Endpoint | Use it when |
|---|---|---|
| One-off snapshot | GET /twitter/list/{list_id}/tweets/{target_count} | You want a fixed number of recent tweets from a list. |
| Repeatable feed or archive | POST /twitter/list/tweets/page | You want to page through results with next_cursor and store collection state. |
Both require a Bearer token. The list_id is the numeric identifier for the X List you want to read.
Get a Fixed Snapshot
For quick checks, call the path-based endpoint with a list_id and target_count.
curl --request GET \
--url https://api.twexapi.io/twitter/list/<list_id>/tweets/50 \
--header 'Authorization: Bearer <token>'A successful response contains code, msg, and data, where data is an array of tweet objects.
1{
2 "code": 200,
3 "msg": "success",
4 "data": [
5 {
6 "tweet_id": "1234567890123456789",
7 "created_at": "Mon Jul 01 12:34:56 +0000 2025",
8 "text": "<tweet text returned by the API>",
9 "favorite_count": 120,
10 "retweet_count": 18
11 }
12 ]
13}This endpoint is simple, but it does not give you a cursor. If you need to continue where the last run stopped, use the page endpoint instead.
Page Through a List Feed
For ongoing monitoring, call POST /twitter/list/tweets/page. Leave next_cursor empty on the first request, then pass the returned cursor into the next request.
curl --request POST \
--url https://api.twexapi.io/twitter/list/tweets/page \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"list_id": "<list_id>"
}'When the response includes has_next_page: true, request the next page:
curl --request POST \
--url https://api.twexapi.io/twitter/list/tweets/page \
--header 'Authorization: Bearer <token>' \
--header 'Content-Type: application/json' \
--data '{
"list_id": "<list_id>",
"next_cursor": "cursor_from_previous_response"
}'Keep the list_id unchanged while paging. A cursor is only meaningful for the list and collection state that produced it.
Python Export Script
The script below exports list tweets to JSONL and writes a metadata file with the cursor chain. That makes the collection easier to resume and audit later.
1import json
2import time
3from datetime import datetime, timezone
4from pathlib import Path
5
6import requests
7
8TOKEN = "<your_bearer_token>"
9LIST_ID = "<list_id>"
10URL = "https://api.twexapi.io/twitter/list/tweets/page"
11OUT = Path(f"x-list-{LIST_ID}-tweets.jsonl")
12META = Path(f"x-list-{LIST_ID}-tweets-meta.json")
13
14headers = {
15 "Authorization": f"Bearer {TOKEN}",
16 "Content-Type": "application/json",
17}
18
19seen_ids = set()
20cursor = None
21page_log = []
22page_number = 0
23
24with OUT.open("w", encoding="utf-8") as f:
25 while True:
26 payload = {"list_id": LIST_ID}
27 if cursor:
28 payload["next_cursor"] = cursor
29
30 response = requests.post(URL, headers=headers, json=payload, timeout=30)
31 response.raise_for_status()
32 body = response.json()
33
34 page_number += 1
35 tweets = body.get("data") or []
36
37 for tweet in tweets:
38 tweet_id = tweet.get("tweet_id")
39 if not tweet_id or tweet_id in seen_ids:
40 continue
41 seen_ids.add(tweet_id)
42 f.write(json.dumps(tweet, ensure_ascii=False) + "\n")
43
44 page_log.append({
45 "page": page_number,
46 "items": len(tweets),
47 "has_next_page": body.get("has_next_page"),
48 "next_cursor": body.get("next_cursor"),
49 })
50
51 if not body.get("has_next_page") or not body.get("next_cursor"):
52 break
53
54 cursor = body["next_cursor"]
55 time.sleep(1)
56
57META.write_text(json.dumps({
58 "list_id": LIST_ID,
59 "exported_at": datetime.now(timezone.utc).isoformat(),
60 "unique_tweets": len(seen_ids),
61 "pages": page_log,
62}, ensure_ascii=False, indent=2), encoding="utf-8")
63
64print(f"Saved {len(seen_ids)} tweets to {OUT}")For production jobs, persist the last successful cursor after every page. If a run stops halfway through, you can resume from the last known cursor instead of starting over.
Turn List Tweets into a Usable Feed
After export, normalize the fields you need for your product:
tweet_id,created_at, text fields, author fields, and engagement counts if present.- A canonical URL such as
https://x.com/i/web/status/<tweet_id>or the author's status URL when author metadata is available. - The
list_id, export time, and page cursor that produced the record. - A stable dedupe key, usually
tweet_id.
Lists are curated by people, and people change lists. If the members of a list change, future exports may reflect a different editorial lens. Store list metadata separately when that matters for your analysis.
Practical Use Cases
- Build a news desk feed from a list of reporters and subject-matter experts.
- Track a competitor category by following a list of founders, analysts, and product accounts.
- Create a research dataset from a public list that represents a community or market.
- Power a content curation workflow where editors review posts before sharing.
- Monitor a narrow topic without writing a broad keyword query.
The list approach works best when list membership is meaningful. If the list is stale, too broad, or maintained by an unknown curator, inspect the members before trusting the feed.
Common Pitfalls
- Using the wrong path. Current list tweet endpoints use the singular
listpath segment, not the older plural form. - Treating a list as the whole conversation. A list is a curated slice, not a complete topic archive.
- Changing the list while comparing historical exports. Member changes can shift what appears in the feed.
- Dropping cursor metadata. Without it, you cannot explain how far the export progressed.
- Overwriting engagement counts without timestamps. Engagement changes over time, so snapshots need collection dates.
Wrap-up
For a quick pull, use GET /twitter/list/{list_id}/tweets/{target_count}. For a feed or archive, use POST /twitter/list/tweets/page, save each page, and keep the cursor trail with the dataset.
That gives you a cleaner, more explainable workflow than scraping a broad timeline or relying on a keyword query that catches too much noise.