Rate limits

OpenTweet enforces two kinds of limit on the REST API and MCP server: how many requests you can send per minute, and how many posts you can publish per day. Both scale with your plan. Go over either and the API returns HTTP 429.

Requests per minute

Every authenticated request to https://opentweet.io/api/v1 counts toward a per-minute budget tied to your subscription.

PlanRequests per minutePosts per day
Pro6020
Advanced300100
Agency600300

Need more headroom? Compare plans on the pricing page.

Daily post limits

Separately from the per-minute budget, there is a cap on how many posts publish per day. Pro allows 20 per day, Advanced 100 per day, and Agency 300 per day (see the table above). A thread counts toward this limit, and both immediate posts (publish_now) and scheduled posts (scheduled_date) count.

HTTP 429 Too Many Requests

When you exceed either limit, the API responds with status 429 Too Many Requests and a JSON body:

json
{
  "error": "Too many requests. Slow down and retry with backoff.",
  "code": "rate_limit_exceeded"
}

Slow down, then retry

A 429 is temporary. Stop sending requests for a moment, then retry the same request. Do not hammer the endpoint, which only extends how long you stay rate limited.

Handle 429 with exponential backoff

The right way to handle a 429 is exponential backoff: wait, retry, and double the wait each time you get another 429. Retry only 429 and 5xx responses. Permanent 4xx errors like 400 or 401 will never succeed on retry, so fix the request instead.

backoff.py
import time
import requests

def post_with_backoff(payload, key, max_retries=5):
    url = "https://opentweet.io/api/v1/posts"
    headers = {"Authorization": f"Bearer {key}", "Content-Type": "application/json"}
    delay = 1.0
    for attempt in range(max_retries):
        resp = requests.post(url, json=payload, headers=headers)
        if resp.status_code < 400:
            return resp.json()
        # Only retry 429 and 5xx. Fix 4xx and do not retry.
        if resp.status_code == 429 or resp.status_code >= 500:
            time.sleep(delay)
            delay *= 2  # exponential backoff: 1s, 2s, 4s, 8s, 16s
            continue
        raise RuntimeError(resp.json())
    raise RuntimeError("Gave up after retries")

No custom rate-limit headers

OpenTweet does not return custom rate-limit headers. Drive your backoff off the HTTP status code: treat any 429 as the signal to wait and retry.

Related