If your snscrape or twscrape script stopped returning tweets, use TwittrAPI: it returns the same public data over plain HTTPS with one API key. You need no X developer account, no X accounts and no cookies. This post shows each old call next to its replacement, so you can swap the scraper and keep the rest of your code.
Why the old scrapers stopped working
snscrape. It read X without logging in. When X put a login wall in front of logged-out visitors in mid-2023, its Twitter module stopped working. The issue "All Twitter scrapes are failing" was opened on June 30, 2023 and is still open. Other snscrape modules are not affected.
twscrape. It still works, because it logs in with X accounts you supply (a login or the auth_token and ct0 cookies). That is also where the trouble is: limits apply per account, accounts get locked or rate limited, and you have to keep adding new ones. The scraping runs under those accounts, and X's terms apply to them.
Nitter. Most public Nitter instances have shut down, so RSS feeds and HTML parsing built on them no longer return data.
How to scrape X data in 2026 covers the rules and what still works without an API.
Setup
Create a key on /start. New accounts get $0.50 of test credit for 7 days, about 5,000 calls. Put the key in an environment variable:
export TWITTRAPI_API_KEY="your-key"
pip install requestsEvery request sends the key in the X-API-KEY header. With format=simple you get flat JSON with fixed field names (simple format).
Search tweets by query
The search query syntax is X's own, so your existing query strings work unchanged: from:, since:, until:, lang:, min_faves:, -filter:replies and the rest.
Before (snscrape):
import itertools
import snscrape.modules.twitter as sntwitter
query = "python lang:en since:2026-10-01"
for tweet in itertools.islice(sntwitter.TwitterSearchScraper(query).get_items(), 100):
print(tweet.date, tweet.user.username, tweet.rawContent)Before (twscrape):
import asyncio
from twscrape import API, gather
async def main():
api = API() # accounts added earlier with `twscrape add_accounts` and `twscrape login_accounts`
tweets = await gather(api.search("python lang:en since:2026-10-01", limit=100))
for tweet in tweets:
print(tweet.date, tweet.user.username, tweet.rawContent)
asyncio.run(main())After (TwittrAPI):
import os
import requests
API = "https://api.twittrapi.com"
HEADERS = {"X-API-KEY": os.environ["TWITTRAPI_API_KEY"]}
def pages(path, params, max_pages=50):
"""Yield pages from a paginated endpoint, following the cursor."""
params = {**params, "format": "simple"}
for _ in range(max_pages):
r = requests.get(f"{API}{path}", headers=HEADERS, params=params, timeout=30)
r.raise_for_status()
page = r.json()
yield page
if not page["hasNextPage"]:
return
params["cursor"] = page["nextCursor"]
def search(query, limit=100):
"""Tweets matching an X search query, newest first."""
n = 0
for page in pages("/search", {"searchItem": query, "category": "Latest", "count": 100}):
for t in page["data"]:
if t.get("unavailable"):
continue
yield t
n += 1
if n >= limit:
return
for t in search("python lang:en since:2026-10-01", limit=100):
print(t["createdAt"], t["author"]["username"], t["text"])The endpoint is GET /search. category=Latest returns every match newest first, which is what snscrape's search scraper gave you. Leave it out to get X's Top tab. max_pages is a safety limit: every page is a billed call.
Get a user's tweets
Before (snscrape):
for tweet in itertools.islice(sntwitter.TwitterUserScraper("NASA").get_items(), 100):
print(tweet.date, tweet.rawContent)Before (twscrape):
# inside main(), as above
user = await api.user_by_login("NASA")
tweets = await gather(api.user_tweets(user.id, limit=100))After (TwittrAPI):
def user_tweets(username, limit=100):
"""Tweets from a profile, newest first, without replies."""
n = 0
for page in pages("/user/-1/tweets", {"username": username, "count": 100}):
for t in page["data"]:
if t.get("unavailable"):
continue
yield t
n += 1
if n >= limit:
return
for t in user_tweets("NASA", limit=100):
print(t["createdAt"], t["text"])The endpoint is GET /user/{userId}/tweets. Passing -1 as the user ID and the handle as username saves the separate lookup that twscrape needs. If you already store numeric IDs, put the ID in the path instead.
How far back a profile goes is up to X: timelines stop after a few thousand tweets, for scrapers and for us alike. For older tweets, search with from:NASA until:2020-01-01 and walk back by date. Get tweets from an account with Python adds CSV export and a daily job that only fetches new tweets.
Pagination
Every list endpoint works the same way: a page holds data, hasNextPage and nextCursor. Send nextCursor back as cursor until hasNextPage is false. The pages helper above does exactly that.
Paging through tweets with a cursor
Call 1
GET /search
→ ~20 tweets
nextCursor: "A1"
Call 2
?cursor=A1
→ ~20 tweets
nextCursor: "B2"
Call n
?cursor=…
→ last tweets
hasNextPage: false
hasNextPage is false instead of counting.count is a hint; X decides how many tweets a page really holds. A page usually holds about 20 tweets, so 1,000 tweets cost about $0.00325 to $0.005, depending on your tier. The pricing page has the rates per 1,000 calls.
Field names, old and new
snscrape and twscrape use mostly the same attribute names. Here is where each one lives in the simple format:
| snscrape / twscrape | TwittrAPI (simple format) |
|---|---|
tweet.id | t["id"] (a string; compare as int) |
tweet.url | t["url"] |
tweet.rawContent | t["text"] |
tweet.date | t["createdAt"] (ISO 8601 string, UTC) |
tweet.lang | t["lang"] |
tweet.user.username | t["author"]["username"] |
tweet.user.displayname | t["author"]["name"] |
tweet.likeCount | t["counts"]["likes"] |
tweet.retweetCount | t["counts"]["retweets"] |
tweet.replyCount | t["counts"]["replies"] |
tweet.quoteCount | t["counts"]["quotes"] |
tweet.viewCount | t["counts"]["views"] |
tweet.quotedTweet | t["quoted"] |
tweet.retweetedTweet | t["retweetOf"] |
If the rest of your code expects a datetime, convert once:
from datetime import datetime
created = datetime.fromisoformat(t["createdAt"].replace("Z", "+00:00"))The field table lists every field, including media, urls, hashtags, mentions and replyTo.
Other calls you may have used
| What you did before | TwittrAPI endpoint |
|---|---|
Profile by handle (user_by_login, TwitterUserScraper(...).entity) | GET /user/by/username/{username} |
| Tweets and replies | GET /user/{userId}/tweetsAndReplies |
| Media tweets | GET /user/{userId}/media |
| Followers / following | GET /user/{userId}/followers, GET /user/{userId}/followings |
| One tweet by ID | GET /tweet/{tweetId} |
| Replies to a tweet | GET /tweet/{tweetId}/replies |
| List tweets | GET /lists/tweets |
All of them take the same key, format=simple and cursor. The API reference has the parameters.
Prefer an SDK?
The twittrapi package wraps the same calls, retries 429 and 5xx responses, and follows the cursor for you:
from twittrapi import TwittrAPI
client = TwittrAPI(response_format="simple") # key from TWITTRAPI_API_KEY
for page in client.paginate(client.search.tweets, "python lang:en", category="Latest", count=100, max_pages=10):
for t in page["data"]:
print(t["createdAt"], t["author"]["username"], t["text"][:80])Searching without code first? Search Twitter without a developer account covers operators, filters and polling.
Frequently asked questions
Does snscrape still work for Twitter in 2026?
No. Its Twitter module stopped working when X required login in mid-2023, and the issue reporting that all Twitter scrapes fail is still open. Use an API such as TwittrAPI to get the same public tweets.
Is twscrape still working?
It works as long as you supply logged-in X accounts and keep them healthy. Accounts can get locked or rate limited, and the tool can break when X changes its internal endpoints. TwittrAPI needs no X accounts or cookies.
Can I keep my snscrape search queries?
Yes. TwittrAPI's search takes X's search syntax, so queries with from:, since:, until:, lang: and similar operators work unchanged. Use category=Latest for the newest-first order snscrape gave you.
How much does it cost to replace a scraper?
You pay per call, and a search or timeline call returns a page of tweets. At about 20 tweets per page, 1,000 tweets cost $0.00325 to $0.005. New accounts get $0.50 of test credit.
Sources
- snscrape issue #996: All Twitter scrapes are failing
- twscrape on GitHub
- Search reference, user tweets reference and simple format (TwittrAPI docs)
TwittrAPI is not affiliated with X Corp. It reads only public data.



