Skip to content
PythonTutorialtweepysnscrapeTwitter API

GUIDE

How to Use the Twitter API with Python, 2026 Tutorial

A practical 2026 walkthrough for calling the Twitter API from Python. Runnable code for profile lookups, search, followers, replies, pagination, retries, async, and a tweepy port.

TwitterAPIs··Updated July 8, 2026
Python Twitter API tutorial, full working code samples for 2026

Open almost any "Twitter API in Python" tutorial and you land in 2022. It tells you to pip install tweepy, register four OAuth secrets, and promises a free tier that no longer exists. None of that survives contact with 2026. X retired the free read tier back in February 2023, swapped in a metered model where every request is billable, and tweepy inherits every rough edge of the official platform underneath it. If you have been wondering whether any free path remains, the short answer is covered in detail in is the Twitter API free: the genuinely free options either rate-limit you into uselessness or break the moment X changes a web-UI selector.

That outdated advice does real damage. A reader follows a 2022 walkthrough, copies four OAuth secrets into a config file, waits days for a developer-account review, and then discovers the endpoint they wanted now costs money per call and caps out under a paywall. The whole exercise was wasted on plumbing that has nothing to do with the data they actually came for. The point of this guide is to skip every dead branch of that decision tree and get you to a working response on the first try.

So here is the version that holds up today. We are going to wire Python to the Twitter / X API in about a minute, with no application queue, reads that work out to roughly four cents per thousand tweets, and nothing imported beyond requests. Each section ships code you can paste and run. By the last heading you will have working clients for profile data, search, timelines, followers, replies, write actions, pagination, retries, and async fan-out. If you are still deciding which provider to build on before you write a line of code, the trade-offs across the whole field are laid out in the best Twitter API for scraping and the head-to-head pricing in the cheapest Twitter API ranked by real per-1000-tweet cost. This page assumes you have made that call and just want the Python to work.

Short version: run pip install requests, create an account at twitterapis.com, copy the API key from your dashboard, and execute the snippet below. Everything after it is depth on each use case.

import requests

BASE = "https://api.twitterapis.com"
TOKEN = "YOUR_API_KEY"  # grab one in well under a minute at twitterapis.com/signup

r = requests.get(
    f"{BASE}/twitter/user/info",
    params={"userName": "nasa"},
    headers={"Authorization": f"Bearer {TOKEN}"},
)
profile = r.json()["data"]
print(profile["name"], "has", profile["followers"], "followers")

Point a live key at that and you get a JSON profile back: display name, follower count, bio, verification flag, and dozens of other fields. From zero to first response is under sixty seconds.

The 60-Second Version

requests against TwitterAPIs is the whole stack. There is no SDK to learn and no auth handshake to debug. The block above is already a complete, working integration, the rest of this guide is just the details for each thing you will eventually need: filtering search, walking followers, surviving a flaky network, and keeping costs down at volume. If you would rather see the production patterns first, jump to the ship checklist near the end.

Three Libraries, One Sensible Default

Python developers reach Twitter data through, broadly, three doors. They differ on price, on how much setup they demand, and on how reliable they are once you depend on them. Here is the honest comparison, weakest option first so the trade-off is obvious.

LibraryWhat it actually is2026 costSetup effortReach for it when
snscrapeOpen-source scraper, no auth at all.Free, but flaky and shrinking.Minutes, until it stops working.Almost never now. The project paused in 2023, and most endpoints broke once X hardened its scraping defenses.
tweepy + official X APIThe official Python wrapper over X API v2, OAuth 2.0 or Bearer.$0.005 per post read, $0.010 per user lookup, $0.015 per write, so roughly $5 to $10 per 1,000 tweets.Hours or days, gated on developer-account review.You genuinely need OAuth user-delegated flows, that is, apps where end users log in with their own X account.
TwitterAPIs + requestsA third-party REST layer behind one Bearer header.$0.04 per 1,000 tweets ($0.0008 per call), with $0.50 of credit free at signup.A couple of minutes, no review.Most collection work: research, analytics, monitoring, bots, dashboards.

Plenty of tutorials still hand you tweepy by reflex. That habit was formed in 2022. Today, choosing tweepy means paying official X rates (about a hundred times more per tweet) and writing developer-account paperwork, OAuth wiring, and retry scaffolding before a single line of your own logic runs. Unless you are literally building login-with-X, TwitterAPIs gets you there faster, cheaper, and with far less code.

Real Python's long-running Tweepy walkthrough is a fair snapshot of how much the official path still asks of you, a full OAuth setup before the bot ever posts a single tweet:

https://x.com/realpython/status/1949847415441244525

The friction shows up most clearly in the questions developers post when a two-year-old tutorial stops matching how the API behaves now. Two threads capture the pattern cleanly: someone wiring up Tweepy's search and not getting the results the old docs promised, and a first-time builder trying to stand up a bot against the renamed X API and hitting wall after wall.

Trying to use tweepy to search twitter not understanding how I am expecting to use the result. from r/learnpython
First time Python Creating a Bot for X(formerly Twitter) from r/learnpython

Getting Set Up

There are only four moves between you and a live call: pull in requests, claim a Bearer key at twitterapis.com (no developer review), stash the key in your environment, and hit an endpoint. Total time is a few minutes.

One dependency. pip install requests and you are done, no SDK and no native build steps. The requests library is the most widely installed HTTP client in the Python ecosystem, so it is almost certainly already in your environment, and its API is stable enough that code you write today will keep running for years. Already using httpx or aiohttp? Both work fine, since the API is plain JSON over HTTPS. There is nothing Twitter-specific to learn at the transport layer, which is the whole reason this approach stays small: you are making ordinary GET and POST requests and reading ordinary dictionaries back.

One key, issued instantly. Sign up at twitterapis.com/signup with Google or an email address. Your dashboard mints a Bearer key on the spot: no application, no waiting list, no card. New accounts start with $0.50 in credit, which is around 625 calls or roughly 12,500 tweets, plenty to exercise every endpoint before you commit a cent. For background on what an "X API key" even means and the four credential types the official platform uses, read What is a Twitter API key?, and for the full official sign-up walkthrough with its review queue and rejection reasons see how to get a Twitter API key. The contrast is the whole story: on the official side you register an app in the X developer documentation flow, wait for approval, and generate a Bearer token plus an OAuth client pair before you can read a single tweet, whereas here the key exists the instant the account does.

One call to prove it. Load the key from the environment (never paste it into source) and call out:

import os
import requests

BASE = "https://api.twitterapis.com"
HEADERS = {"Authorization": f"Bearer {os.environ['TWITTERAPIS_KEY']}"}

def fetch_profile(handle: str) -> dict:
    r = requests.get(f"{BASE}/twitter/user/info",
                     params={"userName": handle}, headers=HEADERS)
    r.raise_for_status()
    return r.json()["data"]

acct = fetch_profile("github")
print(f"{acct['name']} (@{acct['userName']})")
print(f"  followers: {acct['followers']:,}")
print(f"  following: {acct['following']:,}")
print(f"  verified : {acct.get('isVerified', False)}")

Export the key first (export TWITTERAPIS_KEY=...) and run it. The response carries the full profile with every field populated by default, so there is none of the field-expansion ceremony the official X API v2 forces on you.

Recipes You Will Actually Reach For

Below are the everyday jobs Python developers want against Twitter data. Each block runs as-is, drop it in a file, set your key, and go. We define BASE and HEADERS once, as above, and reuse them.

Look up a profile

def profile_card(handle: str) -> dict:
    r = requests.get(f"{BASE}/twitter/user/info",
                     params={"userName": handle}, headers=HEADERS)
    r.raise_for_status()
    return r.json()["data"]

p = profile_card("ycombinator")
print(p["name"], "/", p["followers"], "followers")
print("bio   :", p["description"])
print("joined:", p["createdAt"])

You get the complete profile object back: name, handle, bio, follower and following totals, verification state, avatar and banner URLs, the join date, and the pinned tweet ID.

Pull someone's recent timeline

def recent_timeline(handle: str) -> list[dict]:
    r = requests.get(f"{BASE}/twitter/user/tweets",
                     params={"userName": handle}, headers=HEADERS)
    r.raise_for_status()
    return r.json().get("tweets", [])

for tw in recent_timeline("vercel")[:5]:
    print(f"{tw['likeCount']:>6} likes  {tw['retweetCount']:>5} RT  {tw['text'][:80]}")

Author details ride along inside each tweet object, so there is no expansion step. Rank by engagement, slice by date, or stream it straight into a dashboard.

Run a search query

def run_search(query: str, cap: int = 20) -> list[dict]:
    r = requests.get(f"{BASE}/twitter/tweet/advanced_search",
                     params={"query": query, "product": "Latest"}, headers=HEADERS)
    r.raise_for_status()
    return r.json().get("tweets", [])[:cap]

for tw in run_search("from:nasa filter:images"):
    print(f"[{tw['createdAt']}] {tw['text'][:140]}")

The q field takes the full Twitter operator grammar: from:, to:, since:, until:, min_faves:, min_retweets:, lang:, filter:, and the rest. The complete list lives in the Twitter advanced search operators guide.

Export a follower list

def follower_sample(handle: str, cap: int = 200) -> list[dict]:
    r = requests.get(f"{BASE}/twitter/user/followers",
                     params={"userName": handle}, headers=HEADERS)
    r.raise_for_status()
    return r.json().get("followers", [])[:cap]

crowd = follower_sample("stripe")
checked = [f for f in crowd if f.get("isVerified")]
print(f"{len(crowd)} followers pulled, {len(checked)} of them verified")

If you only want verified accounts, skip the client-side filter and call the verified_followers endpoint instead, it returns the curated set directly. Exporting a large follower base is one of the spots where pagination matters most, since accounts with millions of followers return their lists in cursor-driven pages rather than one giant blob. Loop the cursor (the pattern is a few sections down) and write each page to disk as you go, so a network hiccup at page 400 does not cost you the first 399. The end-to-end pattern, including how to turn the raw follower objects into a clean CSV, is in how to export Twitter followers with the API. One edge case worth flagging: very large accounts will hand back follower counts in the profile object that exceed what any single export run can practically retrieve, so treat the follower list as a sample unless you genuinely page all the way through, and budget credits accordingly before you start a million-row pull.

Read the replies under a tweet

def reply_thread(tweet_id: str) -> list[dict]:
    r = requests.get(f"{BASE}/twitter/tweet/replies",
                     params={"tweetId": tweet_id}, headers=HEADERS)
    r.raise_for_status()
    return r.json().get("replies", [])

for reply in reply_thread("1234567890")[:10]:
    print(f"@{reply['author']['userName']}: {reply['text'][:100]}")

Replies are one of the spots where the official X API really stings: at $0.005 per resource (X API pricing), grabbing 100 of them runs you $0.50. On TwitterAPIs the same pull is a single $0.0008 read.

Search inside a date window

from datetime import date

def windowed_search(query: str, since: date, until: date) -> list[dict]:
    span = f"{query} since:{since.isoformat()} until:{until.isoformat()}"
    r = requests.get(f"{BASE}/twitter/tweet/advanced_search",
                     params={"query": span, "product": "Latest"}, headers=HEADERS)
    r.raise_for_status()
    return r.json().get("tweets", [])

hits = windowed_search("openai", date(2026, 4, 1), date(2026, 5, 1))
print(f"{len(hits)} 'openai' tweets in April 2026")

since: and until: expect YYYY-MM-DD. To reach further back than the rolling 7-day window, split the request into date slices and concatenate, the chunking recipe is in the advanced search operators guide. The reason you slice rather than send one wide range is that a single query is bounded by how many results it will return in one window, so a month-long pull asked for in one shot quietly truncates and you never see the gap. Walking it day by day, or week by week, keeps each slice inside the result ceiling and gives you a complete set you can de-duplicate afterward. If your work is specifically historical rather than recent, the patterns for reaching back across months and years are covered in scrape tweet history with the API, and the broader mechanics of pulling tweets at volume live in how to scrape tweets. A practical scenario: say you are reconstructing every mention of a product launch across a six-week campaign. You would loop one windowed_search per day, append the results to a running list, and key on tweet ID to drop the overlaps that show up at slice boundaries. That gives you a defensible, complete dataset instead of whatever a single greedy query happened to return.

Perform a write action

Reads are most of what people automate, but TwitterAPIs also exposes twelve write actions: favorite and unfavorite, retweet and unretweet, bookmark and unbookmark, follow and unfollow, delete, tweet creation, media upload, and DM send. The simple write actions bill at $0.0008 per call, the same as reads, while posting a tweet or sending a DM bills at $0.0016. Unlike reads, a write carries your own session credentials (auth_token and ct0) on each request, and those values are used in-flight to authorize the action and are never persisted server-side.

def favorite(tweet_id: str, auth_token: str, ct0: str) -> dict:
    r = requests.post(
        f"{BASE}/twitter/tweet/favorite",
        json={"tweetId": tweet_id, "auth_token": auth_token, "ct0": ct0},
        headers=HEADERS,
    )
    r.raise_for_status()
    return r.json()

outcome = favorite(
    "1234567890",
    auth_token=os.environ["X_AUTH_TOKEN"],
    ct0=os.environ["X_CT0"],
)
print("liked:", outcome.get("status"))

Follow, retweet, and bookmark follow the same shape, only the path and the body keys change. Keep the two session values in environment variables and rotate them if a 401 ever comes back. DM sending is part of this catalog too: a dm/send endpoint bills at $0.0016 per call and sends account-to-account direct messages under the same auth_token and ct0 model as the other writes, so the full write surface, the twelve engagement, follow, delete, tweet-creation, media-upload, and DM-send actions, is yours to automate. Treat it like any write, acting on behalf of an account whose session values you supply per request.

Write actions are the core of any automation that posts engagement on a schedule, and if that is your goal the full build, from auth handling to pacing the actions so they look human, is walked through in how to build a Twitter bot. Pacing matters more than people expect: firing a hundred follows in a tight loop is the kind of pattern platforms flag, so space them out and add jitter. The signals that get automated accounts flagged, and how to keep an automation on the right side of them, are the subject of the Twitter bot detection guide. The practical rule of thumb is that read-heavy collection almost never trips anything, while aggressive bursts of write actions on a fresh session are what draw attention, so treat the two halves of the API as carrying very different risk profiles.

Walking Through Pages

TwitterAPIs paginates with cursors. Every response carries a next_cursor string, which comes back null once the last page is served. To advance, send the previous next_cursor back as the cursor parameter, and stop the moment next_cursor comes back empty. The contract is identical across search, followers, and replies, so one loop covers all of them.

def collect_pages(path: str, params: dict, page_cap: int = 10) -> list[dict]:
    gathered, cursor = [], None
    for _ in range(page_cap):
        if cursor:
            params["cursor"] = cursor
        r = requests.get(f"{BASE}{path}", params=params, headers=HEADERS)
        r.raise_for_status()
        body = r.json()
        gathered += (body.get("tweets")
                     or body.get("followers")
                     or body.get("replies")
                     or [])
        cursor = body.get("next_cursor")
        if not cursor:
            break
    return gathered

batch = collect_pages(
    "/twitter/tweet/advanced_search",
    {"query": "from:nasa", "product": "Latest"},
    page_cap=10,
)
print(f"pulled {len(batch)} tweets over multiple pages")

The equivalent on the official side is tweepy.Paginator, same idea, but you also juggle tweet_fields=, expansions=, and user_fields=, and you pay $0.005 per result (X API pricing) rather than $0.0008 per call.

Start building with TwitterAPIs

$0.04 per 1,000 tweets. $0.50 free credits. No credit card required.

Making Requests Survive the Real World

Three transient failures show up in production: a 429 when you outrun a limit (uncommon here but possible), 5xx errors when X itself hiccups upstream, and plain network timeouts on a hung socket. The right response is exponential backoff with a cap, retry only on 429 and 5xx, and fail fast on any other 4xx.

import time
from requests.exceptions import RequestException

def get_resilient(path: str, params: dict, attempts: int = 3) -> dict:
    backoff = 1.0
    for n in range(attempts):
        try:
            r = requests.get(f"{BASE}{path}", params=params,
                             headers=HEADERS, timeout=15)
            if r.status_code == 429:
                time.sleep(float(r.headers.get("Retry-After", backoff)))
                continue
            if r.status_code >= 500:
                time.sleep(backoff)
                backoff *= 2
                continue
            r.raise_for_status()
            return r.json()
        except RequestException:
            if n == attempts - 1:
                raise
            time.sleep(backoff)
            backoff *= 2
    raise RuntimeError("retries exhausted")

If you want the backoff written for you, tenacity is a clean option, decorate a function with @retry(stop=stop_after_attempt(3), wait=wait_exponential()) and you inherit the same behavior for free.

Rate Limits, Side by Side

This is precisely where targeting the official X API turns painful, and where the third-party design earns its keep.

On the official X API, every endpoint sits behind a rolling 15-minute window, and some add a 24-hour daily ceiling on top. Cross one and you get 429 Too Many Requests with x-rate-limit-reset headers. Production tweepy code answers that with per-endpoint queues, backoff, and reset-time parsing, often a hundred-plus lines of plumbing before any real work happens.

On TwitterAPIs, there are no platform-wide rate caps, and that includes advanced_search. The same single-call loop scales to ten thousand calls untouched, so you skip the queues, the backoff bookkeeping, and the reset-time math entirely. For the full per-endpoint table and the headers to watch on the official side, see the Twitter API rate limits comparison and the deeper field guide in the Twitter API rate limit guide.

The official numbers come straight from the platform: every v2 endpoint publishes its own window, and the canonical reference is the X API rate limits documentation. The practical consequence for Python is that on the official side a chunk of your codebase exists only to model those windows, one queue per endpoint, a clock that tracks each reset, and logic to pause the right worker at the right time. None of that is business logic, it is pure ceremony around the platform's constraints. On the third-party side that whole layer disappears outright, because there is no window to model in the first place. That asymmetry is the single biggest reason production collection code is so much shorter on this path.

Going Concurrent with httpx

For high-throughput collection, swap in httpx.AsyncClient and run calls in parallel. The httpx async client gives you connection pooling and HTTP/2 out of the box, and because there is no platform-level window to coordinate around, asyncio.gather just works, a five-account fetch that takes five seconds in series finishes in under two concurrently, and the same pattern scales to dozens of in-flight requests with no semaphore logic.

It is worth being concrete about why this is so much simpler than the official equivalent. On a windowed API, firing twenty parallel requests at the same endpoint is the fastest way to burn your entire 15-minute allowance in one second and spend the next fourteen minutes throttled. So async code there is not really about speed, it is about carefully not exceeding a limit, which means a semaphore to cap concurrency plus a shared clock to track the window. Here, the absence of a per-endpoint window means concurrency buys you pure throughput with none of that coordination. The one thing you still want at very high fan-out is a sane connection pool and a per-request timeout, both of which httpx.AsyncClient handles. If you are pushing real volume from multiple machines or want to distribute load across egress IPs, pair the async client with rotating proxies, the selection and rotation strategy is covered in the best residential proxies for Twitter scraping.

import asyncio
import httpx

HEADERS = {"Authorization": f"Bearer {os.environ['TWITTERAPIS_KEY']}"}

async def one_profile(client: httpx.AsyncClient, handle: str) -> dict:
    r = await client.get("https://api.twitterapis.com/twitter/user/info",
                         params={"userName": handle}, headers=HEADERS)
    r.raise_for_status()
    return r.json()["data"]

async def many_profiles(handles: list[str]) -> list[dict]:
    async with httpx.AsyncClient(timeout=15) as client:
        return await asyncio.gather(*(one_profile(client, h) for h in handles))

names = ["nasa", "github", "vercel", "stripe", "ycombinator"]
for prof in asyncio.run(many_profiles(names)):
    print(prof["name"], "/", prof["followers"], "followers")

All five lookups fire at once. That is safe here precisely because there is no shared rate window to throttle against. On the official X API the same code would need a semaphore plus per-endpoint window tracking to avoid getting burst-throttled.

Porting a tweepy Project

Moving an existing tweepy codebase over is mostly mechanical. The two cover the same ground, what changes is the auth model and the method names. If you want to keep one foot in the official world while you migrate, the tweepy documentation is the reference for the left column below, and the official methods it wraps are catalogued in the X API documentation. Map the calls like this:

tweepy (official X API)TwitterAPIs
client.get_user(username="...")GET /twitter/user/info?userName=...
client.get_users_tweets(user_id, ...)GET /twitter/user/tweets?userName=...
client.search_recent_tweets(query=...)GET /twitter/tweet/advanced_search?query=...
client.get_users_followers(user_id)GET /twitter/user/followers?userName=...
client.get_tweet(tweet_id)GET /twitter/tweet/detail?tweetId=...
client.like(tweet_id)POST /twitter/tweet/favorite
client.follow_user(target_user_id)POST /twitter/user/follow

Here is the same job, the last 20 tweets from an account, written both ways:

# tweepy against the official X API
import tweepy
client = tweepy.Client(bearer_token="OFFICIAL_X_BEARER_TOKEN")
who = client.get_user(username="nasa").data
rows = client.get_users_tweets(
    who.id, max_results=20,
    tweet_fields=["created_at", "public_metrics"],
    expansions=["author_id"],
).data

# requests against TwitterAPIs
import requests
r = requests.get("https://api.twitterapis.com/twitter/user/tweets",
                 params={"userName": "nasa"},
                 headers={"Authorization": f"Bearer {TWITTERAPIS_KEY}"})
rows = r.json()["tweets"]

The TwitterAPIs side is about half the lines, returns author and engagement data inline (no expansions, no field picking), and costs roughly a hundredth as much per tweet. The full feature-by-feature breakdown is in Twitter API v2 vs TwitterAPIs. A migration like this is rarely a big-bang rewrite. The realistic path is to port one job at a time, profile lookups first since they are the simplest, then search, then the write actions, running both code paths side by side and diffing the output until you trust the new one. If your starting point is a different third-party provider rather than tweepy, the same incremental approach applies, and the step-by-step is in migrate from twitterapi.io to TwitterAPIs. And if the project that needs the data is not Python at all, the equivalent walkthrough for the other common backend runtime is the Twitter API Node.js tutorial, which maps the same endpoints onto fetch and axios.

If you want to watch the official Tweepy setup before you port away from it, this end-to-end walkthrough builds a working Twitter API v2 client in Python with Tweepy, OAuth and all, which is a useful baseline for spotting exactly which steps disappear on the third-party side:

https://www.youtube.com/watch?v=GqzbvoAsSrg

And for a sense of how little Tweepy code it takes to post once the credentials are wired, this snippet authenticating with four separate API keys before a single create-tweet call is the canonical shape you are replacing:

https://x.com/imSaichowdary_/status/1986365656351318404

A Reusable Client Class

On anything beyond a script, fold the endpoints into a small class. It pools connections through one requests.Session, sets the Bearer header a single time, and gives you tidy methods. Save it as client.py and import it anywhere:

import os
import requests

class TwitterAPIs:
    BASE = "https://api.twitterapis.com"

    def __init__(self, key: str | None = None, timeout: int = 15):
        self.timeout = timeout
        self.s = requests.Session()
        self.s.headers["Authorization"] = f"Bearer {key or os.environ['TWITTERAPIS_KEY']}"

    def _read(self, path: str, **params) -> dict:
        r = self.s.get(f"{self.BASE}{path}", params=params, timeout=self.timeout)
        r.raise_for_status()
        return r.json()

    def _write(self, path: str, **body) -> dict:
        r = self.s.post(f"{self.BASE}{path}", json=body, timeout=self.timeout)
        r.raise_for_status()
        return r.json()

    def profile(self, handle):
        return self._read("/twitter/user/info", userName=handle)["data"]

    def search(self, query, product="Latest"):
        return self._read("/twitter/tweet/advanced_search", q=query, product=product).get("tweets", [])

    def timeline(self, handle):
        return self._read("/twitter/user/tweets", userName=handle).get("tweets", [])

    def followers(self, handle):
        return self._read("/twitter/user/followers", userName=handle).get("followers", [])

    def replies(self, tweet_id):
        return self._read("/twitter/tweet/replies", tweetId=tweet_id).get("replies", [])

    def favorite(self, tweet_id, auth_token, ct0):
        return self._write("/twitter/tweet/favorite", tweetId=tweet_id,
                           auth_token=auth_token, ct0=ct0)


api = TwitterAPIs()
print(api.profile("ycombinator")["name"])
print(len(api.search("from:vercel")))

That is a compact client covering connection pooling, timeouts, auth, and the common endpoints in one place. Layer pagination, retries, and async variants on top as the project grows.

The cheapest pay-as-you-go Twitter API. Try it free.

$0.04 per 1,000 tweets. $0.50 free credits. No credit card required.

A Pre-Ship Checklist

Before any Python integration touches production, walk this list. These eight catch the failures that quietly burn credits or leave gaps in your data:

  • The API key comes from an environment variable, never a hardcoded literal.
  • Retries cover 429, 502, and 503 with exponential backoff, and nothing else.
  • Pagination stops on an empty next_cursor, not on a guessed page count.
  • A page_cap guards against runaway cursors looping forever.
  • Write-action credentials live in the environment and get rotated on a 401.
  • Logs never echo the raw key or the auth_token.
  • Long searches are sliced into date windows rather than fired as one giant query.
  • Tweet IDs are de-duplicated when workers or query slices overlap.

Tick all eight and the integration is ready for real traffic.

Mistakes That Bite Quietly

These recur in Twitter API work, and most are silent, they do not throw, they just hand back wrong or partial data.

Never advancing the cursor. The only stop signal is next_cursor: when it comes back empty, the data ran out. The failure mode is a loop that reads a fixed page count, or one that never feeds the newest cursor back, so it either stops early or re-reads page one forever. Drive the loop off next_cursor and always send the latest one back:

# fragile: guesses a page count and never re-checks the stop signal
for _ in range(10):
    ...

# correct: stop the instant next_cursor comes back empty
cursor = None
while True:
    if cursor:
        params["cursor"] = cursor
    data = requests.get(url, params=params, headers=HEADERS).json()
    ...
    cursor = data.get("next_cursor")
    if not cursor:
        break

Hardcoding the result key. Different endpoints nest their payload under different keys, tweets, followers, replies, users. Reach for data["tweets"] on a follower response and you get a KeyError. Coalesce instead:

rows = (data.get("tweets") or data.get("followers")
        or data.get("replies") or data.get("users") or [])

Retrying a permanent error. A 401 (bad key) retried three times just wastes three calls. A 404 (deleted or suspended account) burns credit chasing data that is gone. Only 429, 502, 503, and timeouts deserve a retry.

Forgetting the timeout. With no timeout, one stuck request can wedge the whole pipeline. Always pass timeout=15 to requests.get(), and push it to 30 only when you are pulling big paginated results over a slow link.

Re-fetching the same profile per tweet. A thousand tweets with unique authors can mean a thousand $0.0008 lookups. Cache profiles by handle with a TTL and re-fetch only on expiry, on real datasets where popular accounts repeat, that trims the bill by an estimated 80% or more.

Searching when you meant the timeline. from:handle in search and user/tweets for that handle return the same data through different paths. Search reads the web index, which can lag a few minutes, while user/tweets reads the timeline directly. For watching one account in real time, user/tweets is the reliable pick, search shines for multi-account or complex-filter queries. Using search for a single timeline just spends extra credit for no gain. For the full set of production patterns, including proxy strategy, session-credential handling, and cost tuning across the endpoint catalog, see the Twitter scraping best practices guide.

Estimating Cost Before You Scale

Before a collection job grows from a script into something that runs nightly, it pays to model the bill rather than discover it. The arithmetic is friendly because the per-unit rate is flat: reads are $0.0008 per call, a call returns roughly 20 tweets, so you can reason in either currency. A monitoring job that pulls 50,000 tweets a day is about 2,500 calls, which is an estimated $2.00 a day or $60 a month at the read rate, before any caching savings. Write actions, if your project uses them, bill separately at $0.0008 per call for simple engagements (tweet creation is $0.0016), so a bot that fires a few hundred engagement actions a day adds well under a dollar. These are estimated figures, not a quote, but the shape holds: the dominant cost is read volume, and read volume is the thing caching attacks hardest.

The comparison that makes the case is against the official rate. At roughly $0.005 per single post read (X API pricing), the same 50,000-tweet day on the official X API models out to an estimated $250 a day, a difference of about two orders of magnitude that compounds every single day the job runs. That gap is why teams doing serious collection migrate, and the full receipts, with real per-1,000-tweet costs across providers, are in the Twitter API cost benchmark and the per-call breakdown in the Twitter API cost guide. If you are weighing this against marketplace-style options, the trade-offs against a RapidAPI listing are in the RapidAPI Twitter alternative, and against a managed scraper like Apify in Apify Twitter scraper vs TwitterAPIs. One more thing to factor into any forward model: official pricing has moved more than once, and the most recent shifts are tracked in the X API pricing change for 2026, so build your budget on current numbers rather than a tutorial from two years ago.

The single highest-leverage cost control in Python is the profile cache mentioned in the mistakes section. On a dataset where popular accounts recur, caching author lookups by handle with a time-to-live can cut the lookup half of your bill dramatically, because you stop paying to re-fetch the same fifty accounts thousands of times. Wire it before you scale, not after the first surprising invoice.

Common Projects This Powers

It helps to see where these recipes actually land, because the right combination of endpoints depends on what you are building. A few of the most common Python projects map cleanly onto the snippets above.

A sentiment pipeline is search plus a scoring model: you pull tweets for a keyword or cashtag with advanced_search, slice them into date windows, and feed the text into a classifier. The full Python build, from collection to scoring, is walked through in Twitter sentiment analysis in Python. A trend monitor is closer to a scheduled timeline-or-search loop that diffs results between runs to surface what is rising, and the data side of that, including how trends are exposed, is covered in the Twitter trends API guide. An engagement bot leans on the write actions and the pacing discipline described earlier. And a research corpus is mostly windowed search plus rigorous de-duplication and storage.

What all of these share is the same three-line core: one Bearer header, one GET or POST, one dictionary back. The project-specific work is everything around that core, the scoring model, the diffing logic, the storage schema, not the API plumbing. That is the entire argument for keeping the transport layer this thin. If you want the canonical end-to-end reference that ties every endpoint together in one place, it lives in the complete Twitter API tutorial for 2026.

The 2026 Library Map

Since 2023 the Python landscape for Twitter data has narrowed to two production-grade choices, plus a few that are best avoided. Here is where things stand:

LibraryStateUse it for
requests + TwitterAPIsActive, recommendedCollection at any scale
httpx + TwitterAPIsActive, recommendedAsync and concurrent collection
tweepy (v4)Active, maintainedOAuth user-delegated flows only
twitter-api-v2 (PyPI)Thin wrapper, maintainedAn object-oriented alternative to tweepy
snscrapeMostly brokenHistorical reference at best
twintArchived, brokenSkip it

The decision rule is simple. Building a consumer app where users log in with their own X account? You need tweepy and the official OAuth flow. Everything else, collection, analytics, monitoring, bots, dashboards, research, points at requests plus TwitterAPIs: faster to stand up, cheaper to run, and far lighter on error-handling scaffolding.

Get Your Python Twitter API Key

Every new TwitterAPIs account opens with $0.50 in free credit and no card required, enough to run every snippet on this page. Sign up with Google or email, copy the Bearer key, and your first Python call is a minute away.

Get your API key → · View pricing · Cost calculator

For the official route, developer console, OAuth credentials, and the usual rejection reasons, read How to Get a Twitter API Key (2026 Step-by-Step). For the full feature comparison, see Twitter API v2 vs TwitterAPIs. For the operator syntax used in q throughout this guide, see the Twitter search operators reference. For per-call cost at different volumes, see the Twitter API cost guide.


Pricing verified May 6, 2026 against the official X API pricing page and TwitterAPIs pricing. All Python snippets were run against live TwitterAPIs endpoints. snscrape status drawn from the snscrape GitHub repository (active development paused in 2023).

// sources

Where these numbers come from

Each row is a figure in this post and the artefact it was read from. Prices and limits on this platform move, so check the date on the source before you plan against it.

X API pricing page
Source of the official per-resource rates used in the cost comparisons, including the $0.005 per resource that makes a 100-reply pull cost $0.50 on the official API.
X API rate limits documentation
The canonical reference for the per-endpoint windows the post says force a Python codebase to model one queue per endpoint with its own reset clock.
Python requests library documentation
The single dependency the whole tutorial builds on, supporting the claim that the integration is plain GET and POST calls over HTTPS with nothing platform-specific at the transport layer.
httpx async client documentation
Backs the high-throughput section, that swapping in httpx.AsyncClient gives connection pooling and HTTP/2 so parallel account fetches finish in a fraction of the serial time.
Tweepy documentation
The reference for the left-hand column of the migration table, the official-side methods each direct call is mapped from when porting an existing tweepy codebase.
snscrape GitHub repository
The stated source for the snscrape status line in the library comparison table, that active development paused in 2023.

Frequently Asked Questions

The shortest route is a third-party REST API plus the requests library. You set one Authorization: Bearer header, fire a GET at an endpoint such as advanced_search or user/followers, and read the JSON back. TwitterAPIs hands you a key with no OAuth dance and no developer-account review, so a real script is running in a couple of minutes. tweepy is still an option against the official X API, but it expects a paid developer account and full OAuth wiring first.

Not for the third-party path. TwitterAPIs gives you a Bearer token at signup with no application form, no OAuth client ID or secret, and no callback URL to register. A developer account only matters if you intend to hit the official X API v2 directly, for instance to perform OAuth user-delegated actions on behalf of people who sign in with their own X accounts.

Yes. Use httpx.AsyncClient or aiohttp for concurrent calls. Because TwitterAPIs has no platform-wide rate window to coordinate around, asyncio.gather runs many lookups in parallel without semaphores or per-endpoint window tracking. On the official X API the same pattern needs a semaphore plus reset-time bookkeeping to avoid burst-throttling, which is most of the reason async collection code there is so much heavier.

Yes. TwitterAPIs exposes twelve write actions, the engagement and follow pairs (favorite, retweet, bookmark, follow, and their reverses) plus delete, tweet creation, media upload, and DM send, that each take your auth_token and ct0 session values per request. Simple write actions bill at $0.0008 per call, the same as reads, and posting a tweet or sending a DM bills at $0.0016. Those credentials authorize the action in-flight and are not persisted server-side, so account-to-account direct messaging runs on the same bring-your-own auth model as the other writes.

TwitterAPIs reads bill at $0.0008 per call, and a call returns roughly 20 tweets, so reads land near $0.04 per 1,000 tweets. Every signup includes $0.50 of free credit with no card on file. Write actions like favorite, retweet, bookmark, and follow and their reverses bill at $0.0008 per call, the same as reads, while posting a tweet bills at $0.0016. The official X API charges $0.005 for a single standard post read, which is $5.00 per 1,000 tweets, so for read-heavy collection the third-party route is roughly two orders of magnitude cheaper.

For most collection work, plain requests against TwitterAPIs. It costs roughly a hundredth per tweet of tweepy on the official X API, sets up in under a minute, and handles search, profile lookups, follower exports, and replies without any OAuth scaffolding. Reach for tweepy only when you genuinely need OAuth user-delegated flows, meaning apps where end users log in with their own X account. snscrape is effectively retired and should not anchor anything you plan to keep running.

TwitterAPIs reads bill at $0.0008 per call, and a call returns roughly 20 tweets, so reads land near $0.04 per 1,000 tweets. The official X API charges about $0.005 per single post read, which is near $5.00 per 1,000 tweets, so the third-party route is roughly two orders of magnitude cheaper on read-heavy work. Every signup includes $0.50 of free credit, enough to run every snippet in a tutorial before spending anything.

Check out similar blogs

More guides on the Twitter/X API, scraping, and pricing.

How to get the full list of accounts that retweeted a tweet via API in 2026, with Python and Node.js, cursor pagination, and amplifier analysis
Twitter Retweeters APITutorial

How to Get Everyone Who Retweeted a Tweet via API (2026)

Pull the full list of accounts that reposted any tweet with a real 2026 API. Runnable Python and Node.js, cursor pagination for the whole list, a real amplifier ranking over live data, a bot filter, and the honest per-call cost.

TwitterAPIs·
How to get all replies to a tweet via API in 2026, with Python and Node.js, cursor pagination, the conversation_id long-tail sweep, and nested reply handling
Tweet Replies APIConversation ID

How to Get All Replies to a Tweet via API (2026)

Pull the replies under any tweet with a real 2026 API. Runnable Python and Node.js, cursor pagination, the conversation_id tail sweep for the long tail, nested replies-to-replies, signal-versus-noise filtering over live data, and the honest per-call cost.

TwitterAPIs·
How to search tweets by hashtag via API in 2026 with Python and Node.js, showing the hashtag search endpoint and its per-call cost
Twitter Hashtag APITutorial

How to Search Tweets by Hashtag via API 2026 (Python + Node.js)

Search tweets by hashtag with a real 2026 API in Python and Node.js. Runnable code for the hashtag operator, engagement filters, cursor pagination, deduping retweets, counting authors, and the real per-call cost.

TwitterAPIs·
Building a production tweet-collection pipeline in 2026: the tweet object model, search-operator query craft, cursor pagination, rate-limit budgeting, deduplication, and storage
ScrapingPython

How to Scrape Tweets in 2026: Build a Collector That Does Not Break

A production engineering guide to collecting tweets in 2026: the tweet object, search operators, cursor pagination, rate-limit math, deduplication, storage, and live-tested code.

TwitterAPIs·
Twitter API tutorial 2026 complete developer guide, pricing collapse era, with auth flows, endpoints, code samples, and cost math
TutorialDeveloper Guide

Twitter API Tutorial 2026: The Complete Developer Guide

The 2026 Twitter API tutorial built after the pricing collapse. Auth, endpoints, code, rate limits, real costs, and the alternative when official gets too expensive.

TwitterAPIs·
Four routes for Twitter scraping in Python compared in 2026: the official X API v2 pay-per-use rate, a cookie-authenticated account pool, a headless browser fleet, and a managed read API, weighed on unit cost, maintenance burden, terms-of-service exposure, and data completeness
Twitter scrapingPython

Twitter Scraping in Python (2026): Pick a Route Before You Write Code

Four ways to collect X data in Python, priced honestly: the official X API v2 pay-per-use rate, an account-pool scraper, a headless browser, and a managed read API. What each one actually costs to run for a year.

TwitterAPIs·
Twitter (X) API authentication in 2026, covering OAuth 1.0a and OAuth 2.0 bearer tokens, the four credential types, and how to fix 401 Unauthorized and 403 errors in Python and Node.js
Twitter API AuthenticationOAuth 2.0

Twitter API Authentication in 2026: OAuth, Bearer Tokens, and Fixing 401

How Twitter (X) API authentication works in 2026: the four credential types, OAuth 1.0a versus OAuth 2.0, generating and using a bearer token, runnable Python and Node.js, and a fix for every 401 Unauthorized and 403 error, plus the one-header alternative.

TwitterAPIs·
Tweepy vs Twikit vs snscrape 2026 decision guide comparing Python Twitter scraping libraries across authentication model, API-key requirement, account-ban risk, maintenance health, and cost at scale
tweepytwikit

Tweepy vs Twikit vs snscrape: Which Twitter Scraper to Use in 2026

Tweepy vs Twikit vs snscrape in 2026: a 3-way decision matrix, the same task coded in each, ban-risk and cost tables, and the fix now snscrape is broken.

TwitterAPIs·