twitterapi.io is an independent third-party service. Not affiliated with X Corp.

Blogtwitter lists api

Twitter (X) Lists API — Programmatic Guide

By Alex Chen5 min read

Twitter (X) Lists are user-curated collections of accounts — the platform's oldest and most under-appreciated organizing primitive. For developers building content-monitoring, competitive-intelligence, or curated-signal workflows, Lists are the single most efficient way to reduce noise vs polling the full firehose.

This guide walks the programmatic API surface for Lists: reading list metadata, enumerating members, pulling list-scoped tweet streams, and (with write auth) managing list membership. Runnable Python + cost math per typical workload.

01 — Section

Why Lists matter for programmatic monitoring

A well-curated List of 20-50 accounts in your niche produces a signal-dense tweet stream that would take 100× the API calls to reconstruct via keyword search. The trade-off: curation effort upfront.

Typical use cases:

Industry-analyst tweet monitoring — Follow the 30 analysts covering your industry via a List; pull their tweets every 15 min. Alerts your team to competitor moves, funding rounds, market signals before they hit mainstream news.

Competitor tracking — Add 5-10 competitor accounts to a private List; monitor their content velocity + engagement patterns as leading indicators of their strategy shifts.

Curated feeds for downstream products — Public-facing Lists (e.g. 'Top 50 dev tools voices') can power your own website's tweet-widget or newsletter section.

Audience segmentation — Group your customers, prospects, or partners into Lists; pull their content to understand what your ICP talks about.

02 — Section

Core endpoints — read

/twitter/list/info?list_id= — Get list metadata: name, description, member count, subscriber count, owner, private/public.

/twitter/list/members?list_id=&count=1000 — Enumerate members. Cursor-paginated for lists >1000 members. Each entry returns user_id, userName, followers_count, verified.

/twitter/list/tweets?list_id=&count=200 — Pull recent tweets from all list members. Cursor-paginated. Chronologically-sorted. This is the workhorse endpoint for stream-monitoring workflows.

/twitter/list/subscribers?list_id= — Enumerate accounts that follow (subscribe to) the list. Signal for list influence.

All read endpoints are X-API-Key auth (no OAuth user-context needed for public lists). Private lists require the owner's session cookie.

03 — Section

Runnable — list-scoped tweet monitor

One end-to-end script: pull all new tweets from a List every 15 min, alert on high-engagement mentions of your brand.

python
import os, requests, time, json
from pathlib import Path
from datetime import datetime, timezone

HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"

LIST_ID = os.environ["WATCH_LIST_ID"]           # e.g. '1234567890'
BRAND = "stripe"
MIN_ENGAGEMENT = 20

STATE = Path(".state/list_seen.json"); STATE.parent.mkdir(exist_ok=True)

def load_seen() -> set:
    return set(json.loads(STATE.read_text())) if STATE.exists() else set()

def save_seen(s: set):
    STATE.write_text(json.dumps(sorted(s)[-2000:]))

def pull_list_tweets(list_id: str, max_pages: int = 5) -> list:
    tweets, cursor = [], None
    for _ in range(max_pages):
        params = {"list_id": list_id, "count": 200}
        if cursor: params["cursor"] = cursor
        r = requests.get(f"{BASE}/twitter/list/tweets", headers=HEADERS, params=params, timeout=15)
        r.raise_for_status()
        resp = r.json()
        tweets.extend(resp.get("tweets", []))
        cursor = resp.get("next_cursor")
        if not cursor: break
    return tweets

def poll_once():
    seen = load_seen()
    all_tweets = pull_list_tweets(LIST_ID)
    new_tweets = [t for t in all_tweets if t["id"] not in seen]
    print(f"  {len(new_tweets)} new tweets from list · {len(all_tweets)} total in poll window")

    for t in new_tweets:
        text = (t.get("text") or "").lower()
        engagement = t.get("favorite_count", 0) + t.get("retweet_count", 0)
        if BRAND in text and engagement >= MIN_ENGAGEMENT:
            author = t.get("author", {}).get("userName", "?")
            print(f"  \U0001F6A8 High-signal brand mention from @{author} ({engagement} engagement): {t.get('text', '')[:150]}")

    seen.update(t["id"] for t in new_tweets)
    save_seen(seen)

while True:
    try:
        poll_once()
    except Exception as e:
        print(f"  ERROR: {e}")
    time.sleep(900)  # 15-min poll cycle

# Cost per twitterapi.io/pricing:
#   Typical 30-account List = ~50-200 tweets/poll x 96 polls/day = ~5-20K tweets/day = $0.75-3/day
04 — Section

Manage list membership (write endpoints)

Write operations require user-context auth (session cookie via login flow OR OAuth 2.0 on X official):

POST /twitter/list/create — Create a new List with name + description + private/public setting. Returns new list_id.

POST /twitter/list/add_member — Add a member to your List. Payload: list_id, user_id (or userName).

POST /twitter/list/remove_member — Remove a member. Same payload shape as add.

Rate-limit safe pacing: 1 add/remove per 3-5 seconds per authenticated account to stay well below X's write ceilings. For bulk List import (100+ members), distribute across hours.

python
# Bulk import competitor accounts to a private tracking List
import os, requests, time, random

BASE = "https://api.twitterapi.io"
SESSION = os.environ["TWITTERAPI_SESSION_COOKIE"]

def create_list(name: str, description: str, private: bool = True) -> str:
    r = requests.post(
        f"{BASE}/twitter/list/create",
        cookies={"session": SESSION},
        json={"name": name, "description": description, "mode": "private" if private else "public"},
        timeout=15,
    )
    r.raise_for_status()
    return r.json()["list_id"]

def add_member(list_id: str, user_name: str):
    r = requests.post(
        f"{BASE}/twitter/list/add_member",
        cookies={"session": SESSION},
        json={"list_id": list_id, "userName": user_name},
        timeout=15,
    )
    r.raise_for_status()

COMPETITORS = ["paypal", "stripe", "adyen", "checkout", "square"]
list_id = create_list("Payment competitor tracking", "Auto-imported for competitive monitoring", private=True)
print(f"created list_id={list_id}")

for c in COMPETITORS:
    try:
        add_member(list_id, c)
        print(f"  added @{c}")
    except Exception as e:
        print(f"  ERROR adding @{c}: {e}")
    time.sleep(3 + random.uniform(1, 2))  # safe pacing
05 — Section

Comparison — 3 paths for List-based data

Dimensiontwitterapi.ioX official /2/listsManual (twitter.com)
Endpoint/twitter/list/*/2/lists/* per docs.x.com/x-api/lists/introductionUI browse only
Read authX-API-Key (simple)OAuth 2.0 user-contextLogin
Write authsession cookieOAuth 2.0 + list.write scopeManual click
Cost per tweet read$0.00015 (twitterapi.io/pricing)$0.005 per post (docs.x.com)free but manual
Bulk enumerate✓ cursor pagination to 5000+ members✓ with tier limitstedious scroll
Historical list tweets✓ pulls historical if in-windowrecent-search only on Basiclive view only
Best forany programmatic List workflowX-native OAuth appsone-off manual check
06 — Section

Cost math at 4 monitoring volumes

WorkflowEst. tweets/motwitterapi.io ($0.00015)X official Basic ($0.005)
1 List (30 members) x 15-min poll~50K~$7.50/mo~$250/mo
5 Lists (industry analysts + competitors + partners + press + prospects)~250K~$37.50/mo~$1,250/mo
20 Lists (enterprise-scale segmentation)~1M~$150/mo~$5,000/mo
100 Lists (agency multi-client)~5M~$750/mo~$25,000/mo

Practical rule: List-based polling is 30-90% more cost-efficient than keyword-search polling for the same signal density, because Lists are pre-filtered by curator judgment.

07 — Section

Common patterns + gotchas

Poll cadence sweet spot: 15-min for most curated Lists (dev analysts, industry watchers). Sub-minute polls waste credits without much added latency benefit. Longer polls (>60 min) risk missing high-velocity moments.

Private vs public Lists: private Lists are only visible to the owner. Access via API requires the owner's session cookie. Public Lists (anyone can view via twitter.com/i/lists/) accessible with any read auth.

Removed members disappear from stream: if a List member gets removed (by owner) mid-poll, their historical tweets stay retrievable via /twitter/user/last_tweets?userId=, but they stop appearing in list-tweets stream.

Suspended member handling: suspended accounts (X TOS) drop out of list-tweets automatically. Log as member_suspended events for compliance / audit trail.

Rate-limit at bulk write: creating a List then bulk-adding 100 members hits X's write ceiling around 30-50 adds/hour per authenticated account. For bulk import >100 members, pace across 3-5 hours or distribute across multiple accounts.

python
# Complete pattern: monitor 3 curated Lists + differential signals per List
import os, requests, time, json
from pathlib import Path
from collections import Counter
from concurrent.futures import ThreadPoolExecutor

HEADERS = {"X-API-Key": os.environ["TWITTERAPI_IO_KEY"]}
BASE = "https://api.twitterapi.io"

WATCHLISTS = {
    "industry_analysts": os.environ["LIST_ANALYSTS_ID"],
    "competitor_ceos":   os.environ["LIST_COMPETITORS_ID"],
    "customer_advocates": os.environ["LIST_ADVOCATES_ID"],
}

STATE_DIR = Path(".state"); STATE_DIR.mkdir(exist_ok=True)

def seen_file(list_name): return STATE_DIR / f"list_{list_name}_seen.json"
def load_seen(list_name):
    f = seen_file(list_name)
    return set(json.loads(f.read_text())) if f.exists() else set()
def save_seen(list_name, s):
    seen_file(list_name).write_text(json.dumps(sorted(s)[-2000:]))

def pull_list_new(list_name: str, list_id: str) -> tuple[str, list]:
    r = requests.get(f"{BASE}/twitter/list/tweets", headers=HEADERS, params={"list_id": list_id, "count": 200}, timeout=15)
    r.raise_for_status()
    all_tweets = r.json().get("tweets", [])
    seen = load_seen(list_name)
    new_tweets = [t for t in all_tweets if t["id"] not in seen]
    seen.update(t["id"] for t in new_tweets)
    save_seen(list_name, seen)
    return (list_name, new_tweets)

def poll_all():
    with ThreadPoolExecutor(max_workers=3) as ex:
        results = list(ex.map(lambda kv: pull_list_new(kv[0], kv[1]), WATCHLISTS.items()))

    for list_name, new_tweets in results:
        if not new_tweets: continue
        total_engagement = sum(t.get("favorite_count", 0) + t.get("retweet_count", 0) for t in new_tweets)
        avg_engagement = total_engagement / max(len(new_tweets), 1)
        top_engaged = max(new_tweets, key=lambda t: t.get("favorite_count", 0) + t.get("retweet_count", 0))
        print(f"  [{list_name}] {len(new_tweets)} new · avg_engagement {avg_engagement:.0f}")
        author = top_engaged.get("author", {}).get("userName", "?")
        print(f"      TOP · @{author}: {top_engaged.get('text', '')[:120]}")

while True:
    try:
        poll_all()
    except Exception as e:
        print(f"  ERROR: {e}")
    time.sleep(900)

# Cost per twitterapi.io/pricing:
#   3 Lists x avg 30 members x ~50 new tweets/poll x 96 polls/day = ~15K tweets/day = ~$2.25/day = ~$67/month total
08 — Questions

Questions readers ask

Can I read a private List I don't own?

No — private Lists are only visible to the owner via the API. To read another user's private List, you'd need their session cookie (not typically shareable). For monitoring competitor Lists, use the public ones or build your own private List with the same members.

What's the max members in one List?

X's platform limit is 5,000 members per List. API pagination handles this transparently via cursor. For >5,000 tracked accounts, split across multiple Lists (grouped by topic or region).

How do I find Lists a user is a member of?

/twitter/user/memberships?userName= returns all Lists the user has been added to (public + private-that-they-own). Useful for reverse lookup: what curators consider this account authoritative in their niche.

Do List add/remove operations notify the added user?

Adding a member to a public List generates a notification on X. Private List adds do not notify. If you're building tracking workflows with sensitivity concerns, use private Lists.

Can I get engagement metrics scoped to a List?

Not directly via a single call. Pull tweets from the List with /twitter/list/tweets, then aggregate favorite_count + retweet_count + reply_count in your downstream code for List-scoped engagement analytics.

How does this differ from Following list monitoring?

Following = the accounts you personally follow (up to 5,000). Lists = curated collections of accounts (up to 5,000 per List, with unlimited Lists per account). Lists are the standard programmatic monitoring primitive — Following is your personal feed.

Any ToS concern with automated List management?

Standard rate limits + human-like pacing apply (see /blog/twitter-api-error-handling-best-practices). List reads are read-only public data — no ToS issue. List writes (create / add / remove) follow the same write-endpoint pacing rules as tweeting or following.

09 — Further reading

Continue

Sources & further reading
More from this series
Build it

Stop reading. Start building.

Starter credits cover real testing on real data. Google sign-in, no card, no application queue.

Get an API key
    Twitter (X) Lists API — Programmatic Guide | TwitterAPI.io