Skip to content
cheapxapi

Twitter API rate limits: 429s, fixed.

HTTP 429 “Rate limit exceeded” means you sent more requests to one X API endpoint than its window allows. Most windows are 15 minutes, counted per app or per user token. Read x-rate-limit-reset on the response, wait until that time, then retry.

Below: X’s limits for common endpoints, what a 429 looks like, the usual causes, and retry code in Python and Node.js that keeps you under the limit.

This guide is about calling an API. The same message inside the X app or website comes from limits X puts on accounts, which this page does not cover.

X API rate limits by endpoint.

Requests per 15 minutes unless noted. Each endpoint has its own counter, so a limit on search does not slow your user lookups.

Official X API v2 rate limits per endpoint, per app and per user
EndpointPer appPer user
GET /2/tweets/search/recentLast 7 days, up to 100 posts per request450300
GET /2/tweets/search/allFull archive, up to 500 posts per request300, 1 a second1 a second
GET /2/users/:id/tweets10,000900
GET /2/users/:id/mentions450300
GET /2/users/:id/followersUp to 1,000 users per request300300
GET /2/users/:id/following300300
GET /2/users/by/username/:username300900
GET /2/users/:id300900
GET /2/tweets/:id450900
GET /2/tweetsLookup of several posts by ID3,5005,000
GET /2/tweets/:id/liking_users7575
GET /2/tweets/:id/retweeted_by7575
POST /2/tweets10,000 per 24 hours100

Source: the rate limits page, search overview and endpoint reference in X’s developer documentation. Enterprise customers get custom limits. Separately from these, pay-per-use accounts can read up to 3 million posts per billing cycle.

What a 429 looks like.

X sends three headers on every response, so you can see a limit coming before you hit it:

x-rate-limit-limit
requests allowed in the current window
x-rate-limit-remaining
requests left in it
x-rate-limit-reset
Unix time when the window resets

cheapxapi counts per second instead of per 15 minutes, and tells you how long to wait in a standard Retry-After header, in seconds.

Official X API: header names and body from X’s docs

HTTP 429 Too Many Requests
x-rate-limit-limit: <requests allowed in the window>
x-rate-limit-remaining: <requests left in it>
x-rate-limit-reset: <Unix time when the window resets>

{"errors": [{"code": 88, "message": "Rate limit exceeded"}]}

cheapxapi, captured from the live API

HTTP/2 429
content-type: application/json; charset=utf-8
retry-after: 1
x-request-id: a24ab288-a01a-432b-bcf2-1c8a4d8e83d6

{"status":"error","msg":"Rate limit exceeded: your account is limited to 2 requests per second. Please slow down and retry."}

One of 14 parallel requests from a new account. Two went through; the rest got this response, and none of them was charged.

Why you keep hitting it.

  • Several workers share one token. Limits count per app or per user token, not per process. Put one throttle in front of all workers, or give each user’s work that user’s token.
  • Paging through results with no pause. A pagination loop runs as fast as the network allows. Throttle it like any other request.
  • Retrying at once after a 429. Instant retries keep the counter full. Wait for x-rate-limit-reset or Retry-After, and add random jitter so workers do not retry together.
  • Polling more often than the data changes. Poll at the interval you need, cache what you already have, and look up many posts in one request where the endpoint allows it.
  • The monthly read cap, not the rate limit. X’s error list includes a 429 for “usage cap exceeded” as well as for rate limits. Pay-per-use accounts are capped at 3 million post reads per monthly billing cycle, and a monthly cap does not clear when a 15-minute window resets.

Retry code that stays under the limit.

Two parts. A throttle spaces every request in the process evenly, so parallel workers cannot burst past the limit. A retry loop handles the 429s that still happen: it waits for Retry-After, or for X’s x-rate-limit-reset, or backs off exponentially, with random jitter.

The example looks up six profiles in parallel through the user info endpoint. To use it with the official API, change the URL and the auth header; the retry logic already reads X’s headers.

We ran both versions against the live API at 2 and at 20 requests a second on a new account. At 20, some requests got a 429, waited one second and succeeded; every profile came back either way.

Retry reads only. A post or follow that timed out may have gone through, and retrying it can do it twice.

import os, random, threading, time, requests
from concurrent.futures import ThreadPoolExecutor

API = "https://api.cheapxapi.com"
HEADERS = {"x-api-key": os.environ["CHEAPXAPI_KEY"]}
PER_SECOND = 2   # your limit: 2 for new accounts, 10 after the first top-up

class Throttle:
    """Spaces requests evenly so the whole process stays under PER_SECOND."""
    def __init__(self, per_second):
        self.gap, self.next, self.lock = 1 / per_second, 0.0, threading.Lock()

    def wait(self):
        with self.lock:
            now = time.monotonic()
            start = max(self.next, now)
            self.next = start + self.gap
        time.sleep(start - now)

throttle = Throttle(PER_SECOND)

def backoff(res, attempt):
    """Seconds to wait: Retry-After, then X's x-rate-limit-reset, then exponential."""
    if res is not None and res.headers.get("retry-after", "").isdigit():
        return int(res.headers["retry-after"]) + random.random()
    if res is not None and res.headers.get("x-rate-limit-reset", "").isdigit():
        return max(0, int(res.headers["x-rate-limit-reset"]) - time.time()) + 1
    return min(60, 2 ** attempt) + random.random()

def get(path, params, attempts=6):
    """GET with throttling and retries. Only for reads: never retry a post blindly."""
    for attempt in range(attempts):
        throttle.wait()
        try:
            res = requests.get(API + path, headers=HEADERS, params=params, timeout=75)
        except requests.ConnectionError:
            res = None
        if res is not None and res.status_code != 429 and res.status_code < 500:
            res.raise_for_status()
            return res.json()
        time.sleep(backoff(res, attempt))
    raise RuntimeError(f"{path} still failing after {attempts} attempts")

names = ["XDevelopers", "NASA", "SpaceX", "nytimes", "BBCWorld", "github"]
with ThreadPoolExecutor(max_workers=8) as pool:
    for user in pool.map(lambda n: get("/twitter/user/info", {"userName": n})["data"], names):
        print(f'{user["userName"]:<12} {user["followers"]:>12,} followers')

cheapxapi’s limits.

Limits are per API account, counted per second, the same for every read endpoint. Account actions have extra limits per X account, to keep that account from being restricted by X. A request over any limit gets a 429 and is not charged. So does a request that arrives while the whole service is at capacity, with a one-second Retry-After.

cheapxapi rate limits and Retry-After values
RequestsLimitRetry-After
Reads, before the first top-up2 requests a second per account1s
Reads, after the first top-up10 requests a second per account1s
Requests in flight at once10 per account1s
Posting a tweet, sending a DM, editing the profile5 a minute per X account60s
Follow, unfollow15 a minute per X account60s
Like, retweet, bookmark, mute, block and other actions30 a minute per X account60s
Logging in to an X account10 per 10 minutes per API account60s

100,000 tweets: time and cost.

What the rate limit means for a real job: pulling 100,000 tweets from one search. The time is the least the limit allows; response times add to it. X’s full-archive search is the fastest here because each request returns up to 500 posts, but X bills every post returned.

Requests, rate limit, minimum time and cost to fetch 100,000 tweets
APIRequestsLimitMinimum timeCost
Official X API, recent search1,000450 per 15 minabout 30 min$500.00
Official X API, full-archive search2001 a secondabout 3 min$500.00
cheapxapi, before the first top-up5,0002 a secondabout 42 min$3.00
cheapxapi, after the first top-up5,00010 a secondabout 8 min$3.00

X: 100,000 posts at $0.005 each (pay-per-usage pricing page); recent search covers the last 7 days only. cheapxapi: up to 20 tweets per request at $0.0006. Other pay-as-you-go providers in our comparison charge $4.00 (twitterapis.com) to $40.00 (Apify Tweet Scraper V2) for the same 100,000 tweets; their rate limits are not compared here.

Get your API key

Paging through a search: advanced search reference. Date ranges: search tweets by date. Watching for new tweets: webhooks and polling.

Questions.

What does “Rate limit exceeded” mean on the Twitter API?

Your app or token sent more requests to one endpoint than X allows in the current window, usually 15 minutes. X answers HTTP 429 Too Many Requests, documented with error code 88, and serves that endpoint again when the window resets.

How long does a Twitter API rate limit last?

Until the window resets. Most X API limits are counted per 15 minutes, a few per 24 hours or per second. The x-rate-limit-reset header on every response gives the reset time as a Unix timestamp, so you can wait exactly that long instead of guessing.

Are Twitter API rate limits per app or per user?

Both, and they differ by endpoint. Requests with an app-only Bearer token count against the per-app limit; requests with an OAuth user token count against that user’s limit. Recent search, for example, allows 450 requests per 15 minutes per app and 300 per user.

Do rate-limited requests cost money?

X says rate limits and billing are separate: you pay for data returned, and you can hit a rate limit without extra cost. On cheapxapi a request rejected with 429 is never charged; $0.60 buys 1,000 successful requests.

How do I avoid 429 errors?

Space requests below your limit with a client-side throttle, share one throttle across all workers, read x-rate-limit-remaining as you go, and on a 429 wait for the reset time or Retry-After with some random jitter before retrying. The scripts on this page do all four.