{"record":{"id":"55bb60dfce2e5469","repo":"affaan-m/ECC","slug":"rate-limited-resets-at-reset","errorCode":null,"errorMessage":"Rate limited. Resets at {reset}","messagePattern":"Rate limited\\. Resets at (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"skills/x-api/SKILL.md","lineNumber":204,"sourceCode":"```python\nimport time\n\nremaining = int(resp.headers.get(\"x-rate-limit-remaining\", 0))\nif remaining < 5:\n    reset = int(resp.headers.get(\"x-rate-limit-reset\", 0))\n    wait = max(0, reset - int(time.time()))\n    print(f\"Rate limit approaching. Resets in {wait}s\")\n```\n\n## Error Handling\n\n```python\nresp = oauth.post(\"https://api.x.com/2/tweets\", json={\"text\": content})\nif resp.status_code == 201:\n    return resp.json()[\"data\"][\"id\"]\nelif resp.status_code == 429:\n    reset = int(resp.headers[\"x-rate-limit-reset\"])\n    raise Exception(f\"Rate limited. Resets at {reset}\")\nelif resp.status_code == 403:\n    raise Exception(f\"Forbidden: {resp.json().get('detail', 'check permissions')}\")\nelse:\n    raise Exception(f\"X API error {resp.status_code}: {resp.text}\")\n```\n\n## Security\n\n- **Never hardcode tokens.** Use environment variables or `.env` files.\n- **Never commit `.env` files.** Add to `.gitignore`.\n- **Rotate tokens** if exposed. Regenerate at developer.x.com.\n- **Use read-only tokens** when write access is not needed.\n- **Store OAuth secrets securely** — not in source code or logs.\n\n## Integration with Content Engine\n\nUse `brand-voice` plus `content-engine` to generate platform-native content, then post via X API:\n1. Pull recent original posts when voice matching matters","sourceCodeStart":186,"sourceCodeEnd":222,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/skills/x-api/SKILL.md#L186-L222","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read x-ratelimit-remaining before each post and sleep until x-rate-limit-reset when it hits zero.","Implement exponential backoff with jitter and respect the Retry-After header if present.","Queue posts and drain at a rate safely below the limit (e.g. via a token bucket or a queue worker).","Split load across multiple app credentials only if permitted by the developer agreement.","Use the v2 endpoint's batch capabilities where available to reduce request count."],"exampleFix":"# before\nelif resp.status_code == 429:\n    reset = int(resp.headers[\"x-rate-limit-reset\"])\n    raise Exception(f\"Rate limited. Resets at {reset}\")\n\n# after: pause instead of crashing the job\nimport time\nreset = int(resp.headers.get(\"x-rate-limit-reset\", time.time() + 900))\nwait = max(1, reset - int(time.time()))\ntime.sleep(wait)\nreturn retry_post(content)","handlingStrategy":"retry","validationCode":"def can_post_now(oauth) -> bool:\n    # lightweight GET to read rate-limit headers without consuming post quota\n    return True  # actual check reads headers after each post\n\n# before posting, sleep if a prior response said quota was low\nif next_reset_at and time.time() < next_reset_at:\n    time.sleep(next_reset_at - time.time())","typeGuard":"null","tryCatchPattern":"try:\n    post_tweet(content)\nexcept RateLimitError as e:\n    wait = max(1, e.reset_at - int(time.time()))\n    time.sleep(wait)\n    post_tweet(content)  # one retry after reset","preventionTips":["Track x-rate-limit-remaining after each call and pause at zero.","Queue posts and drain below the limit.","Use exponential backoff with jitter for retries."],"tags":["python","x-api","rate-limit","oauth","network"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}