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.
| Endpoint | Per app | Per user |
|---|---|---|
| GET / | 450 | 300 |
| GET / | 300, 1 a second | 1 a second |
| GET / | 10,000 | 900 |
| GET / | 450 | 300 |
| GET / | 300 | 300 |
| GET / | 300 | 300 |
| GET / | 300 | 900 |
| GET / | 300 | 900 |
| GET / | 450 | 900 |
| GET / | 3,500 | 5,000 |
| GET / | 75 | 75 |
| GET / | 75 | 75 |
| POST / | 10,000 per 24 hours | 100 |
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.
| Requests | Limit | Retry-After |
|---|---|---|
| Reads, before the first top-up | 2 requests a second per account | 1s |
| Reads, after the first top-up | 10 requests a second per account | 1s |
| Requests in flight at once | 10 per account | 1s |
| Posting a tweet, sending a DM, editing the profile | 5 a minute per X account | 60s |
| Follow, unfollow | 15 a minute per X account | 60s |
| Like, retweet, bookmark, mute, block and other actions | 30 a minute per X account | 60s |
| Logging in to an X account | 10 per 10 minutes per API account | 60s |
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.
| API | Requests | Limit | Minimum time | Cost |
|---|---|---|---|---|
| Official X API, recent search | 1,000 | 450 per 15 min | about 30 min | $500.00 |
| Official X API, full-archive search | 200 | 1 a second | about 3 min | $500.00 |
| cheapxapi, before the first top-up | 5,000 | 2 a second | about 42 min | $3.00 |
| cheapxapi, after the first top-up | 5,000 | 10 a second | about 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.
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.