Twikit Not Working? What Broke, and Alternatives to twscrape, snscrape and Nitter
Twikit not working, snscrape dead, Nitter under legal threat? Why X scrapers break, a twscrape alternative for each case, and migration code for twikit.
By Tarun Khatri · · 10 min read
If twikit is not working for you in October 2026, you are not alone: its last release shipped in February 2025, and since March 2026 its issue tracker has been full of reports of breakages caused by changes on X's side, with the fixes sitting in unmerged pull requests. snscrape's Twitter module has been broken since 2023, and Nitter, which ntscraper depends on, received cease-and-desist letters from X in August 2026. twscrape is still maintained, but it breaks the same way the others do. The durable alternatives are the official X API and hosted APIs. This post covers what broke and why, compares the options, and shows how to port a twikit search and timeline script to plain REST calls.
Disclosure: Weir, the API in the migration example, is our product. The other options are covered with their real prices, and some of them are the better choice for particular jobs.
TL;DR: where each library stands
Checked on PyPI and GitHub on 19 October 2026:
| Library | Last release | State |
|---|---|---|
| twikit | 2.3.3, 7 February 2025 | Unreleased fixes; open breakage reports through August 2026 |
| twscrape | 0.20.1, 25 August 2026 | Maintained; needs logged-in accounts; regular breakages and fixes |
| snscrape | June 2023 | Twitter scraping broken since June 2023 |
| ntscraper | 0.4.0, 24 May 2025 | Depends on Nitter instances; Nitter's repository is now archived |
Why twikit is not working
twikit logs in as a normal X user, from a username and password or exported browser cookies, and calls the same internal GraphQL endpoints the x.com web app uses. That design is why it needs no API key, and it is also why it breaks whenever X changes its web app.
The 2026 pattern is visible in the repository:
- March 2026: issue #408, "ClientTransaction broken as of March 18 2026", reports that every call failed with "Couldn't get KEY_BYTE indices" after X changed a script the library parses. The issue is still open.
- April and May 2026: reports of a SearchTimeline 404 and a "KeyError since April 23 2026" as X's responses changed shape.
- August 2026: "status: 429, error" on
search_tweet, with an HTML error page as the body.
Contributors have opened pull requests for these (for example #419 and #432), but none of the 2026 fixes has been merged, and the last code change on the main branch dates from April 2025. On PyPI, 2.3.3 is still the latest version. So when twikit is not working, upgrading will not fix it. You would have to run a patched fork, and accept that it will break again.
There is also an older class of failures that has nothing to do with parsing: issue #396 (November 2025) reports Cloudflare 403 responses on every cookie-based call, and #392 reports X refusing actions with "This request looks like it might be automated". Those are X deciding the traffic is automated, and the account you logged in with is the one at risk.
twscrape, snscrape and Nitter
twscrape
twscrape is the healthiest of the group. It shipped 0.20.1 in August 2026 and its maintainer closes breakage reports quickly: a transaction-id failure in #327 was fixed within days. But the issue list from the last two months looks like twikit's: "X web scripts not found" behind a Cloudflare challenge (#330), user_by_id "blocked by CloudFlare" (#337), and another script-parsing failure (#340), all still open.
Its README says plainly that it "requires authorized X/Twitter accounts", added from browser cookies, and that "X/Twitter's Terms of Service discourage using multiple accounts." It is designed around a pool of accounts that it rotates when one is rate-limited. That is the point at which a twscrape alternative is worth considering: the library is fine, but the method is one X actively works against.
snscrape
snscrape worked without logging in, which is exactly what X closed off in mid-2023. Issue #996, "All Twitter scrapes are failing: blocked (404)", opened on 30 June 2023, is still open, the last release on PyPI is from 22 June 2023, and the repository has not been updated since November 2023. If you need a snscrape alternative for Twitter, assume its Twitter module is gone for good. Its other modules are a separate question.
ntscraper and Nitter
ntscraper scrapes Nitter, the open-source alternative front end, rather than X. Its own README warns that "most Nitter instances have shut down or will shut down shortly." On 24 August 2026, according to nitter.net, X Corp. sent cease-and-desist letters demanding a permanent takedown of Nitter instances and the project's repository, and sent another on 8 September. The Nitter repository is now archived, and the 10 October update says the project has no legal counsel. Don't build anything new on Nitter.
Why these libraries keep breaking
The causes are the same across all four:
- They use internal endpoints. X can change a GraphQL query id, a response field or a client-side script at any time, without notice, and owes those callers nothing.
- X now requires a signed-in user for most reads. Logged-out access is what killed snscrape. Everything that still works needs a real account, and that account takes the risk.
- X detects automation. Cloudflare challenges, 429s and "looks automated" errors are X's controls doing their job. We won't cover ways around them. Working around them is the opposite of a stable integration, and X's terms forbid it.
- X's terms say no. The Terms of Service, effective 9 October 2026, say "you cannot scrape the Services without X's express written permission" or "try to work around any technical limitations we impose." This isn't legal advice, but it is a risk you take on yourself if you use these libraries.
The alternatives: official API or a hosted API
There are two ways to read X data that don't involve your own accounts. One is X's own API. The other is a hosted API run by a third party. Prices as of October 2026:
| Option | Price to read posts | Good at |
|---|---|---|
| Official X API | $0.005 per post read; 3M post reads/month cap on pay-per-use | X's own terms, writes, real-time streams |
| TwitterAPI.io | $0.15 per 1,000 tweets | Lowest per-tweet price, writes, real-time add-on |
| SocialData | $0.20 per 1,000 results | One flat price, monitoring API |
| Apify Tweet Scraper V2 | $0.40 per 1,000 tweets, at least 50 per query | No-code runs, scheduling, dataset exports |
| Weir (ours) | 1 credit per post plus 1 per call; $0.10 to $0.20 per 1,000 credits | X and Reddit on one key, monitors, MCP; 5,000 free credits a month |
Some notes on fit:
- The official X API is the only option with X's permission behind it. It is also the only one here that can post on behalf of users at scale under X's terms. Use it if you need any of that. X API pricing in 2026 has the full rate card.
- TwitterAPI.io is cheaper per tweet than Weir and also does writes, so for high-volume tweet collection or posting it is the better pick.
- SocialData is a simple flat price if you only need X.
- Apify suits analysts who want a spreadsheet without writing code.
- Weir is read-only. It fits if you also need Reddit, want free monthly credits, or want results in an AI assistant over MCP.
Every hosted API, ours included, reads public X data without being X, so X's rules still shape what you can do with the data. Our comparison of Twitter API alternatives goes through each one in more depth.
Migrate a twikit snippet to a REST call
Here is a typical twikit script: log in, search for recent posts, page once, then read an account's timeline.
Before: twikit
import asyncio
from twikit import Client
client = Client("en-US")
async def main():
client.load_cookies("cookies.json") # a logged-in X session
tweets = await client.search_tweet("python", "Latest")
for tweet in tweets:
print(tweet.id, tweet.created_at, tweet.user.screen_name, tweet.favorite_count)
more = await tweets.next() # the next page
user = await client.get_user_by_screen_name("XDevelopers")
timeline = await client.get_user_tweets(user.id, "Tweets")
for tweet in timeline:
print(tweet.text)
asyncio.run(main())
After: search with POST /v1/x/search
No cookies or session, just a bearer key. Search is POST /v1/x/search with a JSON body, and pagination is a cursor you send back unchanged:
import os
import requests
API = "https://api.weirapp.io"
session = requests.Session()
session.headers["Authorization"] = f"Bearer {os.environ['WEIR_API_KEY']}"
def search(body: dict, max_pages: int = 3):
cursor = None
for _ in range(max_pages):
payload = {**body, **({"cursor": cursor} if cursor else {})}
r = session.post(f"{API}/v1/x/search", json=payload, timeout=60)
r.raise_for_status() # a 402 means out of credits: stop, don't retry
page = r.json()
yield from page["data"]
cursor = page["meta"]["nextCursor"]
if not cursor: # null is the only stop condition
return
for post in search({"query": "python", "sort": "latest", "language": "en"}):
print(post["id"], post["createdAt"], post["author"]["username"], post["likeCount"])
sort: "latest" matches twikit's "Latest" product, and "top" matches "Top". You can write X search operators in query, or use structured fields such as fromUsers, minLikes, onlyOriginal, since and until. A cursor is bound to the request that produced it, so change a filter and you get a 400 rather than a wrong page.
After: a user's posts with GET /v1/x/users/{user}/posts
twikit needs two calls, one to resolve the screen name and one for the timeline. Here {user} accepts the username directly:
def user_posts(user: str, max_pages: int = 3):
cursor = None
for _ in range(max_pages):
params = {"cursor": cursor} if cursor else {}
r = session.get(f"{API}/v1/x/users/{user}/posts", params=params, timeout=60)
r.raise_for_status() # 404: the account doesn't exist or is protected
page = r.json()
yield from page["data"]
cursor = page["meta"]["nextCursor"]
if not cursor:
return
for post in user_posts("XDevelopers", max_pages=1):
print(post["createdAt"], post["text"][:80])
It returns the account's posts and reposts, newest first, with any pinned post at the top, much like the Posts tab on the profile. Replies are at /v1/x/users/{user}/replies. A trimmed item from a real call on 19 October 2026:
{
"id": "2111172163776708859",
"text": "New guide to the 𝕏 Chat API from @santiagomed: ...",
"url": "https://x.com/XDevelopers/status/2111172163776708859",
"createdAt": "2026-10-16T19:07:22.000Z",
"author": { "username": "XDevelopers", "verified": true, "followersCount": 698073 },
"likeCount": 117,
"retweetCount": 17,
"replyCount": 7,
"viewCount": 12604,
"quotedPost": {
"id": "2111171539479711824",
"url": "https://x.com/santiagomed/status/2111171539479711824"
}
}
Field mapping
| twikit | Weir |
|---|---|
tweet.id |
id (a string; keep it one) |
tweet.text / tweet.full_text |
text (always the full text) |
tweet.created_at |
createdAt (ISO 8601, UTC) |
tweet.user.screen_name |
author.username |
tweet.favorite_count |
likeCount |
tweet.retweet_count / tweet.reply_count |
retweetCount / replyCount |
tweet.view_count |
viewCount (null if X didn't report it) |
await tweets.next() |
send meta.nextCursor back as cursor |
Two behavioural differences. First, Weir's counts can be null, which means X didn't report the number. It doesn't mean zero. Second, the timeline endpoint's limit is a request, not a guarantee: X decides the page size, which is about 20 posts. Plan on pages of about 20, and cap pages instead of items.
What it costs
A Weir X post is 1 credit, and every successful call adds 1 credit, so a full 20-post page is 21 credits. A timeline's first page also costs 2 credits for the profile lookup (billing). Failed calls cost nothing. The 5,000 free monthly credits cover about 4,700 posts. Beyond that, 100,000 posts is about 105,000 credits: roughly $21 at the $5 pack's rate or $10.50 at the $1,000 pack's rate. The same 100,000 reads are $500 on the official API, $15 on TwitterAPI.io and $20 on SocialData, at the prices linked above. Our rates are on the pricing page.
Which one to pick
- Keep twscrape only for throwaway research, on accounts you can afford to lose, and with X's terms in mind.
- Move to the official X API if you post, need X's permission, or need full-archive search at 500 posts per request.
- Move to a hosted API if you only read public data and want a plain HTTP interface that someone else keeps working. Pick on price and coverage, using the table above.
Doing this with Weir
If the hosted route fits, create a free account (no card needed), set WEIR_API_KEY, and the code above runs as it is. The searchX reference lists every search field, getXUserPosts covers timelines, and pagination explains cursors. For a longer walk-through, including CSV export, see how to get tweets with Python.
Sources
- twikit on PyPI: https://pypi.org/project/twikit/
- twikit repository: https://github.com/d60/twikit
- twikit issue #408: https://github.com/d60/twikit/issues/408
- twikit issue #425: https://github.com/d60/twikit/issues/425
- twikit issue #433: https://github.com/d60/twikit/issues/433
- twikit issue #396: https://github.com/d60/twikit/issues/396
- twikit issue #392: https://github.com/d60/twikit/issues/392
- twikit pull request #419: https://github.com/d60/twikit/pull/419
- twikit pull request #432: https://github.com/d60/twikit/pull/432
- twscrape on PyPI: https://pypi.org/project/twscrape/
- twscrape repository and README: https://github.com/vladkens/twscrape
- twscrape issues #327, #330, #337, #340: https://github.com/vladkens/twscrape/issues
- snscrape on PyPI: https://pypi.org/project/snscrape/
- snscrape issue #996: https://github.com/JustAnotherArchivist/snscrape/issues/996
- ntscraper on PyPI: https://pypi.org/project/ntscraper/
- Nitter repository: https://github.com/zedeus/nitter
- Nitter status update: https://nitter.net
- X Terms of Service: https://x.com/en/tos
- X API pricing: https://docs.x.com/x-api/getting-started/pricing
- TwitterAPI.io pricing: https://twitterapi.io/pricing
- SocialData: https://socialdata.tools/
- Apify Tweet Scraper V2: https://apify.com/apidojo/tweet-scraper
- Weir billing: https://docs.weirapp.io/billing