X (Twitter) API Setup Support

Your integration works in testing. It goes to production, traffic picks up, and calls start coming back 429 Too Many Requests. So you add a delay, then a longer delay, then a retry queue — and the 429s keep arriving anyway.

That is usually the moment a team discovers the thing X’s own error table says in a single line and almost no tutorial repeats: 429 does not mean one thing on this API. It means one of two completely different things.

The distinction that decides whether backing off helps at all

X documents 429 as “Rate limit or usage cap exceeded.” Those are separate systems. A rate limit is a request-frequency ceiling that refills when its window rolls over. A usage cap is a consumption ceiling on how much data you are entitled to pull, and it does not roll over in fifteen minutes.

Sleeping until the reset timestamp fixes the first one. Applied to the second, it produces an integration that sleeps, retries, fails, and sleeps again — indefinitely, while looking like a throttling problem.

This page explains how the limits are actually enforced, how to tell the two 429s apart from the response itself, which published numbers catch teams out, and where these builds stall. It is written for teams running social listening, scheduling, inbox, and analytics products on the X API.

Rate limits are enforced twice, not once

Every endpoint carries its own limit, and that limit is applied against two independent counters at the same time. Which one you consume depends entirely on how the call is authenticated.

Per-app

Applies when you call with an app-only Bearer Token. One shared pool for your entire application, no matter how many customers are behind it.

Per-user

Applies when you call with an OAuth 1.0a or OAuth 2.0 user token. Each authorised account gets its own allowance.

Windows differ per endpoint

Most are 15 minutes. Some are 24 hours. A few are per second. Several endpoints carry a 15-minute limit and a 24-hour limit simultaneously.

Some endpoints exist on one side only

Write and private-data endpoints often publish no per-app figure at all. App-only authentication simply cannot reach them.

That last point is the single most useful architectural fact on this page. A design that funnels every customer’s requests through one Bearer Token is a design that shares one bucket between all of them — and the customer who triggers the 429 is rarely the customer who caused it. Moving read traffic onto per-user tokens changes the arithmetic completely, and it is a change that is painful to retrofit and cheap to plan for.

Telling the two 429s apart

The response tells you which failure you are looking at, if you read past the status code. X returns a structured error body with a type field that identifies the failure class.

Error typeWhat it meansDoes waiting help?
rate-limit-exceededYou exceeded the request frequency for this endpoint in the current windowYes — until the reset timestamp
usage-cappedYou exceeded your entitled data consumptionNo — this is an entitlement problem, not a timing one
client-forbiddenThe app is not enrolled or lacks the required access for that endpointNo — and this one returns 403, not 429

An inconsistency worth knowing about. The rate-limits documentation illustrates a 429 using the legacy body shape — an errors array carrying code: 88 and the message Rate limit exceeded — while the response-codes page documents the newer problem-type format shown above. Both appear in current official documentation. Write a handler that tolerates both shapes rather than picking one and hoping.

The headers are on every response, not just the failures

Three headers accompany responses: the maximum for the current window, how many requests remain in it, and a Unix timestamp for when it resets. Reading them only after a 429 has already landed is the difference between an integration that throttles itself and one that discovers its ceiling by hitting it. A partial success complicates this further — a 200 response can contain both data and an errors array, so checking the status code alone is not enough to know the request fully succeeded.

Published numbers that break assumptions

Most capacity plans are built on a mental model of “a few hundred calls per fifteen minutes” applied uniformly. The published tables do not work that way at all — the spread between endpoints is enormous, and the tightest limits sit on exactly the endpoints that consumer-facing products depend on.

EndpointPublished limitWhy it bites
Full-archive search1 request per second, alongside a 15-minute ceilingA per-second gate defeats any batch job written as a tight loop, regardless of the 15-minute budget
Filtered stream1 connection; 1,000 rules; 250 posts per secondA single connection means no naive horizontal scaling and no zero-downtime redeploy without planning
Direct message lookup15 per 15 minutes, per userInbox products routinely design polling loops that cannot fit inside this
Post creationPer-user 15-minute limit plus a separate per-app 24-hour ceilingSchedulers pass testing on the short window and hit the daily one only at real volume
Post deletionPer-user only — no per-app figure publishedCleanup jobs written against app-only auth have nowhere to run

These figures are the currently published ones and this area of the platform has changed repeatedly. Treat any number in a blog post — including this one — as a starting point to verify against the live documentation and the developer console at the time you build, not as a contract.

Rate limits and billing are not the same ceiling

X states this explicitly: rate limits exist to control request frequency for system stability, while usage billing charges for data retrieved. The two move independently. You can sit comfortably inside every rate limit and still accumulate usage cost, and you can be rate limited without any additional charge at all.

The practical consequence is that “we are not near our rate limits” is not evidence that consumption is under control, and “we reduced our bill” is not evidence that throttling will stop. Teams that model only one of the two are surprised by the other.

What a properly instrumented client actually does

  1. Reads the headers on every responseRemaining-request counts are tracked continuously, not consulted after a failure. The client slows itself down before the ceiling, rather than discovering it.
  2. Branches on the error type, not the status codeA frequency problem and an entitlement problem get different handling. Retrying the second one forever is the most common way an integration converts a solvable problem into a silent outage.
  3. Waits until the published reset, then backs offThe reset timestamp is authoritative for the current window. Exponential backoff is the documented recommendation for 429 and 5xx responses — but backoff on top of a known reset time, not instead of it.
  4. Separates the pools it draws fromApp-level and user-level traffic are accounted separately by design. A client that knows which pool each call consumes can route around a saturated one; a client that does not simply stops.
  5. Caches and streams instead of pollingThe documented guidance is to store results, use the filtered stream for realtime data instead of polling, and spread requests across the window. Most 429 problems are architectural, and no retry logic fixes an architecture that asks for the same data repeatedly.

Where these integrations stall

  • Backoff logic applied to a usage cap. The client retries, waits longer, retries again, and never recovers — because nothing about waiting restores an exhausted entitlement.
  • One Bearer Token for every customer. The per-app pool is shared, so one heavy account throttles everyone, and the logs point at the wrong tenant.
  • Capacity planned from a single number. The plan assumes a uniform limit; production meets a per-second gate on one endpoint and a 24-hour ceiling on another.
  • Headers read only on failure. The integration has no idea how close it is running until it is already over.
  • A 200 treated as complete success. Partial errors inside a 200 response go unhandled, so missing records look like platform bugs.
  • A single stream connection scaled horizontally. Extra workers open extra connections, and the additional connections are refused.
  • Error handling written against one body format. The handler parses the legacy shape, the endpoint returns the newer one, and the retry path never triggers.

What getting this right looks like

Predictable throughputThe client throttles itself from live headers instead of discovering ceilings in production
Correct failure handlingFrequency problems and entitlement problems are told apart and treated differently
Capacity that matches the productAuth model and call patterns chosen against the real published limits, not an assumed average

Getting this right before it reaches production

None of this is conceptually difficult in isolation. What makes rate limiting on X expensive is that the diagnosis is ambiguous by design — one status code covers two unrelated failures, the published limits vary by two orders of magnitude between endpoints, the enforcement model depends on an authentication decision usually made in the first hour of the project, and the official documentation contains more than one description of the same error. Teams rarely lose weeks to writing a retry loop. They lose weeks to retrying the wrong failure.

I provide setup, review-preparation and approval support for social platform API integrations, including reviewing your authentication model and call patterns against the current published limits before they become a production problem. The same class of ceiling exists on other platforms and is covered in Meta Graph API rate limits and error codes and Instagram Graph API rate limits and 429 errors. Current engagement options are on the pricing page.

Rate limits, tiers, entitlements and pricing on X change frequently and must be confirmed against the official documentation and your developer console at the time you build. This page is technical setup and approval support — no specific outcome or timeline can be guaranteed.