Skip to main content
← Back to blog
by Engineering
guiderate-limitstwitter-api

Twitter/X API Rate Limits in 2026 — Complete Guide

The #1 Pain Point for Twitter API Developers

If you've worked with the X (formerly Twitter) API for any length of time, you've hit a rate limit. It's practically a rite of passage. The dreaded 429 Too Many Requests response has derailed more projects than any other single issue in the Twitter developer ecosystem.

In 2026, X's API rate limits remain one of the most confusing and frustrating aspects of building on the platform. The tier system has evolved since the Elon Musk acquisition, and understanding exactly what you get at each level — and how to work within those constraints — is critical for any serious project.

This guide covers everything: the current tier structure, how rate limit headers work, strategies for staying under the limit, and what to do when you inevitably hit one.

X API v2 Tier Overview (2026)

X currently offers three API access tiers, each with dramatically different limits:

Free Tier

Tweet reads: ~100 tweets/month (essentially token-level)
Tweet writes: 1,500 tweets/month (write-only tier, but heavily restricted)
User lookups: 500 requests/month
App-level rate limit: 50 requests per 15-minute window
Cost: $0/month
Reality check: This tier is essentially useless for any production application. ~100 tweet reads per month means roughly 3 per day — you can't even pull one full page of results (max_results=100) more than once every few days.

Basic Tier

Tweet reads: 10,000 tweets/month
Tweet writes: 3,000 tweets/month
User lookups: 10,000 requests/month
App-level rate limit: ~15 requests per 15-minute window on typical read endpoints
Cost: $200/month (previously $100, increased in late 2025)
Reality check: Enough for a small bot or personal project, but you'll run out quickly if you're doing any kind of data collection or analysis.

Pro Tier

Tweet reads: 1,000,000 tweets/month
Tweet writes: 300,000 tweets/month
User lookups: 500,000 requests/month
App-level rate limit: 300 requests per 15-minute window
Cost: $5,000/month
Reality check: The massive price jump from Basic ($200) to Pro ($5,000) is where most developers get stuck. Many projects need more than Basic but can't justify Pro pricing.

Quick Comparison Table

FeatureFreeBasic ($200/mo)Pro ($5,000/mo)
Tweet reads/month~10010,0001,000,000
Tweet writes/month1,5003,000300,000
User lookups/month50010,000500,000
Requests per 15 min50~15300
Full-archive searchNoNoYes
Filtered streamNoNoYes
Monthly cost$0$200$5,000

App-Level vs User-Level Rate Limits

One of the most confusing aspects of X's rate limiting is the distinction between app-level and user-level limits.

App-Level Limits

These apply to your entire application regardless of how many users are authenticated. If your app makes 50 requests in a 15-minute window on the Free tier, all subsequent requests will be rejected — even for different users.

App-level limits use App-Only authentication (Bearer Token). Most read endpoints fall under this category.

User-Level Limits

These apply per authenticated user. If you're building an app where users sign in with their X account (OAuth 2.0), each user gets their own rate limit bucket. This can effectively multiply your capacity.

For example, the GET /2/tweets/:id endpoint allows:

App-level: 300 requests per 15 minutes
User-level: 900 requests per 15 minutes (per user)

The catch? User-level auth requires OAuth 2.0 PKCE flow, which adds significant complexity to your application.

Understanding Rate Limit Headers

Every response from the X API includes three rate limit headers:

x-rate-limit-limit: 300
x-rate-limit-remaining: 247
x-rate-limit-reset: 1704067200
x-rate-limit-limit: Maximum number of requests allowed in the current window
x-rate-limit-remaining: How many requests you have left
x-rate-limit-reset: Unix timestamp (seconds) when the window resets

Always read these headers. They're your early warning system. Don't wait until you get a 429 — proactively check remaining and slow down when it gets low.

PYTHON
import requests
import time

def check_rate_limit(response):
    remaining = int(response.headers.get("x-rate-limit-remaining", 0))
    reset_time = int(response.headers.get("x-rate-limit-reset", 0))

    if remaining < 5:
        sleep_time = reset_time - int(time.time()) + 1
        if sleep_time > 0:
            print("Rate limit nearly exhausted. Sleeping " + str(sleep_time) + "s...")
            time.sleep(sleep_time)

    return remaining

Handling 429 Too Many Requests

When you exceed your rate limit, the API returns a 429 status code with a body like:

JSON
{
  "title": "Too Many Requests",
  "detail": "Too Many Requests",
  "type": "about:blank",
  "status": 429
}

The wrong way to handle this: immediately retry the request. This can cascade into a retry storm that keeps you locked out longer.

The right way: implement exponential backoff with jitter.

Exponential Backoff Implementation

PYTHON
import requests
import time
import random

def request_with_backoff(url, headers, max_retries=5):
    """Make an API request with exponential backoff on 429 errors."""
    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        if response.status_code == 200:
            return response

        if response.status_code == 429:
            # Check the reset header first
            reset_time = int(response.headers.get("x-rate-limit-reset", 0))
            now = int(time.time())

            if reset_time > now:
                # Sleep until the window resets
                wait = reset_time - now + 1
                print("Rate limited. Waiting " + str(wait) + "s until reset...")
                time.sleep(wait)
            else:
                # Fallback: exponential backoff with jitter
                base_wait = min(2 ** attempt, 64)
                jitter = random.uniform(0, base_wait * 0.5)
                wait = base_wait + jitter
                print("Rate limited (attempt " + str(attempt + 1) + "). Backing off " + str(round(wait, 1)) + "s...")
                time.sleep(wait)
        else:
            # Non-rate-limit error, raise immediately
            response.raise_for_status()

    raise Exception("Max retries exceeded after " + str(max_retries) + " attempts")

Why jitter matters: If 10 clients all hit the rate limit at the same time and all retry after exactly 2 seconds, they'll all collide again. Adding random jitter (noise) to the backoff spreads out the retries and reduces thundering herd problems.

Strategies to Maximize Your Quota

Rate limits don't have to ruin your project. Here are battle-tested strategies to get the most out of your allocation:

1. Aggressive Caching

Cache every response. Most Twitter data doesn't change second-by-second. A user's profile can be cached for 15-60 minutes. Tweet data can be cached even longer since tweets are immutable (aside from like/retweet counts).

PYTHON
import time

class SimpleCache:
    def __init__(self):
        self.store = {}

    def get(self, key, ttl=900):
        """Get a cached value if it exists and hasn't expired."""
        if key in self.store:
            value, timestamp = self.store[key]
            if time.time() - timestamp < ttl:
                return value
        return None

    def set(self, key, value):
        self.store[key] = (value, time.time())

cache = SimpleCache()

def get_user(username, headers):
    cached = cache.get("user:" + username, ttl=900)  # 15 min cache
    if cached:
        return cached

    url = "https://api.x.com/2/users/by/username/" + username
    response = request_with_backoff(url, headers)
    data = response.json()
    cache.set("user:" + username, data)
    return data

2. Batch Requests

Instead of fetching users one by one, use batch endpoints. The /2/users endpoint accepts up to 100 user IDs in a single request. That's 100x more efficient than individual lookups.

PYTHON
# Bad: 100 requests for 100 users
for user_id in user_ids:
    response = requests.get(
        "https://api.x.com/2/users/" + user_id,
        headers=headers
    )

# Good: 1 request for 100 users
ids_param = ",".join(user_ids[:100])
response = requests.get(
    "https://api.x.com/2/users?ids=" + ids_param,
    headers=headers
)

3. Request Queuing

Instead of making requests as fast as possible, queue them and process at a controlled rate. This prevents bursts that eat through your 15-minute window.

PYTHON
import time
from collections import deque

class RateLimitedQueue:
    def __init__(self, max_per_window=150, window_seconds=900):
        self.max_per_window = max_per_window
        self.window_seconds = window_seconds
        self.timestamps = deque()

    def wait_if_needed(self):
        """Block until we can safely make another request."""
        now = time.time()

        # Remove timestamps outside the current window
        while self.timestamps and self.timestamps[0] < now - self.window_seconds:
            self.timestamps.popleft()

        if len(self.timestamps) >= self.max_per_window:
            # Wait until the oldest request falls outside the window
            sleep_time = self.timestamps[0] - (now - self.window_seconds) + 0.1
            if sleep_time > 0:
                print("Queue full. Sleeping " + str(round(sleep_time, 1)) + "s...")
                time.sleep(sleep_time)

        self.timestamps.append(time.time())

queue = RateLimitedQueue(max_per_window=140)  # Leave 10 request buffer

def queued_request(url, headers):
    queue.wait_if_needed()
    return requests.get(url, headers=headers)

4. Use Fields and Expansions Wisely

Every X API request supports tweet.fields, user.fields, and expansions parameters. Request only the fields you need. Smaller responses are faster and count the same toward your rate limit as large ones — so there's no penalty for being specific, but you reduce bandwidth and processing time.

PYTHON
# Instead of getting everything:
url = "https://api.x.com/2/tweets/" + tweet_id

# Only request what you need:
url = ("https://api.x.com/2/tweets/" + tweet_id
       + "?tweet.fields=created_at,public_metrics"
       + "&expansions=author_id"
       + "&user.fields=username,verified")

5. Use Conditional Requests

For endpoints that support it, use If-None-Match headers with ETags. If the data hasn't changed, the API returns 304 Not Modified — which does not count against your rate limit.

Monthly Quotas: The Hidden Rate Limit

Beyond the per-window limits, X enforces monthly caps on tweet reads and writes. This is separate from the 15-minute window system and arguably more painful.

On the Basic tier, 10,000 tweet reads per month means:

~333 tweets per day
~14 tweets per hour
About 6-7 API calls with max_results=50

If you're building a monitoring dashboard that checks 20 accounts every hour, you'll blow through your monthly limit in about 3 days. This is where the math stops working for most Basic tier users.

There's no way to buy more monthly quota without upgrading tiers. You can't add $50 for another 5,000 reads. It's either $200/month for 10K or $5,000/month for 1M. The gap is brutal.

Common Pitfalls

Pagination Drains

Each page of results counts as a separate request. Fetching a user's followers with 50 per page across 20 pages uses 20 requests — and if that user has 100K followers, you'll never finish on Free or Basic.

Search is Expensive

The /2/tweets/search/recent endpoint has tighter limits than most other endpoints: 60 requests per 15 minutes on Basic. Complex queries with multiple operators don't reduce the count — each request is one request regardless of query complexity.

Streaming Costs

The filtered stream endpoint (/2/tweets/search/stream) is only available on Pro. There's no Basic tier access. If you need real-time data, you're looking at $5,000/month minimum.

Deleted Tweets Still Count

If you request a tweet that has been deleted, the API returns an error response — but it still counts against your rate limit. There's no way to avoid this.

Rate Limiting Best Practices Checklist

1. Always read rate limit headers before making the next request
2. Implement exponential backoff with random jitter for 429 responses
3. Cache aggressively — most Twitter data can be cached for 5-60 minutes
4. Use batch endpoints wherever possible (users, tweets)
5. Queue requests instead of firing them as fast as possible
6. Monitor monthly quotas separately from per-window limits
7. Log every 429 so you can analyze patterns and optimize
8. Test with lower limits in development to catch issues early
9. Use webhooks instead of polling where available
10. Consider your tier carefully — sometimes upgrading is cheaper than engineering around limits

The Bigger Picture

Rate limits exist for a reason — they protect the platform from abuse and ensure fair access. But the current pricing structure creates a dead zone between Basic ($200/mo, 10K tweets) and Pro ($5,000/mo, 1M tweets) that's painful for mid-size projects.

Many developers spend more engineering time working around rate limits than building their actual product. Between implementing caching layers, backoff logic, request queues, and quota monitoring — the infrastructure overhead is significant.

Alternatively, services like XCROP provide higher limits without the complexity of managing Twitter's rate limit tiers. Instead of dealing with 15-minute windows and monthly caps, you get a simple credit-based system where each API call costs a fixed number of credits — no surprise throttling, no tier math.

Whatever approach you choose, understanding rate limits deeply is essential. The strategies in this guide will help you build more resilient applications and avoid the most common traps that catch developers off guard.