{"record":{"id":"4013d5aa63ad3972","repo":"affaan-m/ECC","slug":"x-api-error-resp-status-code-resp-text","errorCode":null,"errorMessage":"X API error {resp.status_code}: {resp.text}","messagePattern":"X API error (.+?): (.+?)","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"skills/x-api/SKILL.md","lineNumber":208,"sourceCode":"if 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\n2. Build or reuse a `VOICE PROFILE`\n3. Generate content with `content-engine` in X-native format\n4. Validate length and thread structure\n5. Return the draft for approval unless the user explicitly asked to post now","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/affaan-m/ECC/blob/01e15490f04e29cfefe3896951f43db46994d8ee/skills/x-api/SKILL.md#L190-L226","documentation":"A catch-all generic Exception raised by X (Twitter) API posting code for any response status other than 201, 429, and 403. It includes the raw status code and response body. This branch exists to surface unexpected API responses rather than silently returning None.","triggerScenarios":"Any non-success, non-429, non-403 response: 400 Bad Request (malformed body, text > 280 chars), 401 Unauthorized (bad/expired token), 409/422 for invalid media references, 500/502/503 for X-side outages, or unexpected new status codes from API changes.","commonSituations":"Tweet text exceeds the character limit after URL/tco wrapping; token expired and was not refreshed; media media_id was not yet available when referenced; X is having a service incident; API version changed and the client targets a removed endpoint.","solutions":["Branch on the specific status code: 401 -> refresh token, 400 -> validate payload, 5xx -> retry with backoff.","Validate content length and structure before posting (strip/shorten, count weighted characters).","Refresh OAuth tokens proactively before expiry rather than waiting for a 401.","For 5xx responses, retry with exponential backoff and jitter up to a bounded number of attempts.","Log resp.text in full server-side; do not surface raw X error text to end users as it may leak details."],"exampleFix":"# before\nelse:\n    raise Exception(f\"X API error {resp.status_code}: {resp.text}\")\n\n# after: route by status code\nif resp.status_code == 401:\n    refresh_oauth_token()\n    return retry_post(content)\nif resp.status_code == 400:\n    raise InvalidTweetError(resp.json().get('detail', 'bad request'))\nif 500 <= resp.status_code < 600:\n    raise TransientXError(f\"X {resp.status_code}; retryable\")\nraise XAPIError(f\"X {resp.status_code}: {resp.text}\")","handlingStrategy":"try-catch","validationCode":"def payload_is_postable(content: str) -> bool:\n    return isinstance(content, str) and 0 < len(content) <= 280","typeGuard":"null","tryCatchPattern":"try:\n    post_tweet(content)\nexcept TransientXError:\n    backoff_retry(post_tweet, content, attempts=3)\nexcept InvalidTweetError as e:\n    log.warning(\"rejected: %s\", e)\nexcept XAPIError as e:\n    log.error(\"unexpected X error %s\", e)\n    raise","preventionTips":["Validate content length (counting weighted characters) before posting.","Refresh OAuth tokens before they expire.","Branch on status code instead of one catch-all.","Retry only 5xx with bounded backoff."],"tags":["python","x-api","network","error-handling"],"backgroundTag":null,"analyzedSha":"01e15490f04e29cfefe3896951f43db46994d8ee","analyzedAt":"2026-08-13T00:31:08.655Z","schemaVersion":2},"datasetVersion":"2026-08-13T04:17:16.726Z"}