# X API rate limits for posting, explained

A `429 Too Many Requests` from the X API means a rate limit or usage cap was reached. For posting, X's current rate-limit
table lists **100 requests per 15 minutes per user** and **10,000 per 24 hours per app** for `POST /2/tweets`. Those are
request windows, not a monthly posting subscription or a promise that every app can publish that many posts.

Checked on October 26, 2026 against X's own [rate-limit reference](https://docs.x.com/x-api/fundamentals/rate-limits),
[manage-posts guide](https://docs.x.com/x-api/posts/manage-tweets/integrate),
[create-post reference](https://docs.x.com/x-api/posts/create-post),
[response codes](https://docs.x.com/x-api/fundamentals/response-codes-and-errors) and
[pricing documentation](https://docs.x.com/x-api/getting-started/pricing). The pages disagree on one posting limit;
that difference is called out below. Check your app's [Developer Console](https://console.x.com) and actual response
headers before planning a burst.

## Pay-per-use, Enterprise and the old plan names

X's current pricing page describes **pay-per-use, with no required subscription**: credits pay for usage. Its current
rate-limit reference publishes one endpoint table, not separate Free, Basic and Pro posting tables. Enterprise customers
have custom rate limits.

If you found an old Twitter API Free, Basic or Pro quota, don't treat it as today's pay-per-use allowance. These current
pages don't establish a current posting quota for each old tier or the terms of a particular legacy account. Check the
plan and limits shown for that app in the Console. An X Premium account subscription is not an API request allowance.

Three different constraints can stop a post:

- **Endpoint rate limits:** how many requests a user or app can make within a window.
- **Usage caps:** a separate allowance, which can also produce a 429. The endpoint's short-window reset may not clear it.
- **Billing:** credits and a billing-cycle spending limit. Having credits doesn't remove a rate limit, and waiting for a
  rate window doesn't refill a balance.

For costs and credit behavior, use [X API pricing, explained](/docs/guides/x-api-pricing). The numbers below are request
limits, not prices or monthly plan entitlements.

## Posting limits: per user and per app

These are the figures in X's rate-limit reference on the checked date:

| Endpoint | Per user | Per app |
|---|---|---|
| `POST /2/tweets`: a post, reply or thread part | 100 per 15 minutes | 10,000 per 24 hours |
| `DELETE /2/tweets/:id` | 50 per 15 minutes | Not listed |

**Per user** means the account represented by the user token. The manage-posts guide says its user allowance applies
across any developer App: two tools posting for the same account don't each get a fresh user allowance. **Per app** is
the app-level column, not 10,000 for each connected person. A new access token is not a new person or a new allowance.

There is an authentication wrinkle in X's documentation: the general rate-limit page labels per-app limits as applying
to app-only bearer tokens, but the create-post reference requires **user authorization**. An app-only bearer token cannot
publish a post. The per-app column is not another way to authenticate; confirm the counters that apply to your app in
the Console rather than assuming one response header describes every applicable allowance.

### Why another X page says 200, not 100

The manage-posts guide still states **200 posting requests per 15 minutes per user**, plus **300 requests per 3 hours**
combining post creation and reposts. The central rate-limit table instead says **100 per 15 minutes** for creation.
Neither page explains that discrepancy or gives a plan mapping that resolves it.

Don't build around the larger number. Use 100 per 15 minutes as the conservative published reference, account for the
separately documented 300-per-three-hour allowance, and monitor the actual counters. Staying under one window doesn't
prove the others have room. Enterprise's custom terms may differ; this table is not its contract.

### Threads and media have more than one request

A five-part thread needs five `POST /2/tweets` requests: the root and four replies. It is not one write because your UI
shows one review item. A retry should continue from the first unsent part, not recreate the root.

Media endpoints have their own counters. X's table lists:

| Endpoint | Per user | Per app |
|---|---|---|
| `POST /2/media/upload`: one-step upload | 500 per 15 minutes | 50,000 per 24 hours |
| `POST /2/media/upload/initialize` | 1,875 per 15 minutes | 180,000 per 24 hours |
| `POST /2/media/upload/:id/append` | 1,875 per 15 minutes | 180,000 per 24 hours |
| `POST /2/media/upload/:id/finalize` | 1,875 per 15 minutes | 180,000 per 24 hours |
| `GET /2/media/upload`: processing status | 1,000 per 15 minutes | 100,000 per 24 hours |
| `POST /2/media/metadata`: alt text | 500 per 15 minutes | 50,000 per 24 hours |

Each appended segment is a request to the append endpoint; status polling consumes the status endpoint's allowance.
A successful upload doesn't prove there is room to create the post. For the upload sequence and payloads, see
[threads, polls and media with the X API v2](/docs/guides/x-api-threads-polls-media).

## Reading a Twitter API 429 and its reset headers

X's response-code reference describes 429 as **rate limit or usage cap exceeded**, with problem types
`rate-limit-exceeded` and `usage-capped`. Its rate-limit page also shows an error body with code `88` and message
`Rate limit exceeded`. Don't require that one body shape to recognize every 429; read the HTTP status and the returned
problem details.

| Response header | What it tells you |
|---|---|
| `x-rate-limit-limit` | Maximum requests in the reported window |
| `x-rate-limit-remaining` | Requests left in that window |
| `x-rate-limit-reset` | Unix timestamp, in **seconds**, when that window resets |

Read these headers on successful responses too. When remaining reaches zero, wait for that window's reset instead of
sending another write just to discover it is exhausted. The reset is an absolute time, not a number of seconds to sleep.
In JavaScript, convert it to milliseconds before comparing it with `Date.now()`.

If your clock is 90 seconds before the reported reset, wait at least those 90 seconds; don't retry once a second.
That is an illustrative calculation, not a fixed X retry delay. A reset for one reported counter is not proof that a
longer user, app or usage allowance has reset too.

## How to back off without duplicating a post

X recommends waiting until `x-rate-limit-reset` and using exponential backoff if needed. In your own integration:

1. **Keep the rejected work queued.** Record the status, endpoint, account, reset and problem type, without logging
   credentials or private post content. Coordinate workers using the same applicable counter, rather than having every
   worker retry independently.
2. **Honor a valid future reset first.** Wait until it has passed. Add a little randomized delay so a whole queue doesn't
   wake at the exact same instant; jitter should never move the retry before the reset.
3. **If the reset is missing, invalid or already past and 429 continues, back off progressively.** Increase the delay
   between attempts exponentially, with jitter, instead of immediately looping. Re-read the headers on each response.
   These are integration recommendations, not a claim that X specifies one universal delay sequence.
4. **Distinguish a usage cap from a short-window limit.** For `usage-capped`, inspect the Console's usage allowance and
   reset period. Repeating a request after a 15-minute boundary won't fix a different exhausted allowance. For credits
   or spending, follow the [pricing guide](/docs/guides/x-api-pricing), not a rate-limit retry loop.
5. **Retry only the unsent operation.** Preserve successful post IDs and a thread's reply chain. A timeout or 5xx is not
   the same as a definite 429 refusal: if X may have accepted a write before the connection failed, reconcile the outcome
   before sending it again. Don't rotate accounts, apps or tokens to get around a limit.

Spread legitimate queued work through the window and use the remaining count as feedback. Don't assume windows reset
at midnight or on a fixed wall-clock quarter hour; use the timestamp X returns. Reconnecting an account fixes some
sign-in failures, not exhausted rate counters.

## Posting through Flaghand

The backoff steps above are advice for an integration you build, not a claim that Flaghand implements that exact retry
policy. Flaghand's current retry and held-job behavior, plus the separate 401, 403, credit and media failures, are in
[X API posting errors, explained](/docs/guides/x-api-posting-errors). Waiting for a reset doesn't approve a new post or
automatically restart a held job.

In Flaghand, an agent drafts, checks and requests approval; only the owner can approve the exact plan with a passkey.
No API-token scope lets an agent approve, schedule or post. [Get access to Flaghand](/schedule).

[All guides](/docs/guides.md)
