X API 429 Too Many Requests: Rate Limits, Headers, and Backoff
Last updated: July 9, 2026
A 429 Too Many Requests from the X API means you exceeded a rate limit on that specific endpoint. The confusing part is that X enforces several limits at once, on different clocks, at different levels, and paying per request does not remove them. This guide explains how the limits actually work and how to write code that respects them.
User-level vs app-level limits
X tracks two separate counters for every endpoint:
- Per-user limits apply to requests made with user-context auth (OAuth 1.0a or an OAuth 2.0 user token). The counter is per authenticated user, so 10 different users each get their own allowance through your app.
- Per-app limits apply to requests made with app-only auth (OAuth 2.0 Bearer token), and some endpoints enforce an app-wide cap on top of the per-user one. All requests through your app share this counter.
Whichever counter empties first triggers the 429. This is why one busy user can exhaust an app-level limit and cause 429s for every other user of your app, and why the same request can succeed with one auth mode and fail with the other.
The 15-minute window and the 24-hour posting cap
Most X API rate limits are expressed per 15-minute window. The window is fixed from your first request, and the counter resets fully when it expires; there is no gradual refill. Posting endpoints additionally carry 24-hour caps at both the user and app level, and those daily caps are what most people actually hit when publishing tweets. On the legacy free tier, POST /2/tweets is capped at 17 requests per 24 hours per user and 17 per app, so an automation posting hourly hits 429 before the day is out.
Decoding the rate limit headers
Every X API response includes headers describing the limit for that endpoint:
| Header | Meaning |
|---|---|
x-rate-limit-limit | Total requests allowed in the current window. |
x-rate-limit-remaining | Requests you have left in the current window. |
x-rate-limit-reset | When the window resets, as Unix epoch seconds (UTC). |
Posting endpoints also return separate 24-hour counters (headers in the x-user-limit-24hour-* and x-app-limit-24hour-* families), so check both: your 15-minute window can show plenty remaining while the daily cap is exhausted. The correct behavior on 429 is to stop, read x-rate-limit-reset, and sleep until that timestamp.
Exponential backoff done right
Prefer the reset header when it is present, and fall back to exponential backoff with jitter when it is not:
import time
import random
import requests
def post_with_backoff(url, headers, payload, max_retries=5):
for attempt in range(max_retries):
r = requests.post(url, headers=headers, json=payload)
if r.status_code != 429:
return r
reset = int(r.headers.get("x-rate-limit-reset", 0))
wait = max(reset - time.time(), 2 ** attempt)
time.sleep(wait + random.uniform(0, 1))
raise RuntimeError("Still rate limited after retries")
Two rules matter more than the code: never retry in a tight loop (X can flag aggressive retrying as abusive behavior), and add jitter so that multiple workers do not all retry at the same instant and re-trigger the limit together.
Why do I hit 429 after only a few posts?
Because the posting caps on the lower tiers are genuinely tiny. The legacy free tier allows 17 tweets per 24 hours. Pay-per-use (the default for new developers since February 6, 2026) charges per request but still enforces rate limits on top of billing; buying credits raises your spend ceiling, not your request ceiling. If your app serves multiple users through app-only auth, they also share one app-level counter. A scheduler that naively fires a burst of queued posts at 9:00 AM will spend most of the day rate limited.
How do I prevent 429s structurally?
- Queue per endpoint: serialize writes through a single queue so bursts become a steady drip.
- Track remaining, not errors: read
x-rate-limit-remainingon every response and pause proactively at zero instead of reacting to 429. - Spread schedules: distribute posts across the day rather than clustering them at round hours.
- Cache reads: repeated lookups of the same tweet or user burn window allowance for nothing.
Or skip the plumbing entirely. OpenTweet queues your posts, spaces them out, retries rate-limited publishes automatically after the window resets, and manages X auth and token refresh for you. Your code talks to the OpenTweet REST API or the hosted MCP endpoint with one key, and never sees a 429 from X. See the developer page to get started.