affaan-m/ECC · error · Exception

Rate limited. Resets at {reset}

Error message

Rate limited. Resets at {reset}

What it means

A generic Exception raised by X (Twitter) API posting code when the response status is 429 Too Many Requests. The reset timestamp is read from the x-rate-limit-reset response header. This indicates the app has exhausted its allotted requests for the current rate-limit window.

Source

Thrown at skills/x-api/SKILL.md:204

```python
import time

remaining = int(resp.headers.get("x-rate-limit-remaining", 0))
if remaining < 5:
    reset = int(resp.headers.get("x-rate-limit-reset", 0))
    wait = max(0, reset - int(time.time()))
    print(f"Rate limit approaching. Resets in {wait}s")
```

## Error Handling

```python
resp = oauth.post("https://api.x.com/2/tweets", json={"text": content})
if resp.status_code == 201:
    return resp.json()["data"]["id"]
elif resp.status_code == 429:
    reset = int(resp.headers["x-rate-limit-reset"])
    raise Exception(f"Rate limited. Resets at {reset}")
elif resp.status_code == 403:
    raise Exception(f"Forbidden: {resp.json().get('detail', 'check permissions')}")
else:
    raise Exception(f"X API error {resp.status_code}: {resp.text}")
```

## Security

- **Never hardcode tokens.** Use environment variables or `.env` files.
- **Never commit `.env` files.** Add to `.gitignore`.
- **Rotate tokens** if exposed. Regenerate at developer.x.com.
- **Use read-only tokens** when write access is not needed.
- **Store OAuth secrets securely** — not in source code or logs.

## Integration with Content Engine

Use `brand-voice` plus `content-engine` to generate platform-native content, then post via X API:
1. Pull recent original posts when voice matching matters

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Read x-ratelimit-remaining before each post and sleep until x-rate-limit-reset when it hits zero.
  2. Implement exponential backoff with jitter and respect the Retry-After header if present.
  3. Queue posts and drain at a rate safely below the limit (e.g. via a token bucket or a queue worker).
  4. Split load across multiple app credentials only if permitted by the developer agreement.
  5. Use the v2 endpoint's batch capabilities where available to reduce request count.

Example fix

# before
elif resp.status_code == 429:
    reset = int(resp.headers["x-rate-limit-reset"])
    raise Exception(f"Rate limited. Resets at {reset}")

# after: pause instead of crashing the job
import time
reset = int(resp.headers.get("x-rate-limit-reset", time.time() + 900))
wait = max(1, reset - int(time.time()))
time.sleep(wait)
return retry_post(content)
Defensive patterns

Strategy: retry

Validate before calling

def can_post_now(oauth) -> bool:
    # lightweight GET to read rate-limit headers without consuming post quota
    return True  # actual check reads headers after each post

# before posting, sleep if a prior response said quota was low
if next_reset_at and time.time() < next_reset_at:
    time.sleep(next_reset_at - time.time())

Type guard

null

Try / catch

try:
    post_tweet(content)
except RateLimitError as e:
    wait = max(1, e.reset_at - int(time.time()))
    time.sleep(wait)
    post_tweet(content)  # one retry after reset

Prevention

When it happens

Trigger: Calling oauth.post('https://api.x.com/2/tweets', ...) more times than the endpoint allows within the 15-minute window, or hitting an app-level daily limit. The header x-rate-limit-reset is a Unix timestamp of when the window resets.

Common situations: Bulk posting in a tight loop without checking remaining quota; multiple processes sharing one app token; a retry storm after transient errors; running a scheduled job more frequently than the tier allows.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/55bb60dfce2e5469. Report an issue: GitHub.