Errors and rate limits
One error shape, stable codes to switch on, and the limits that keep an X account safe.
Error shape
Every failure, on every endpoint, is the same two fields. error is a stable code a program can switch on. message is a sentence for the person reading the log, and it may change.
Response · 403
{
"error": "insufficient_scope",
"message": "This key does not have the posts:publish permission. Create a key with it in Settings."
}Codes
| Status | Code | Meaning |
|---|---|---|
| 400 | invalid_json | The body is not valid JSON. |
| 400 | invalid_segments | Empty post, too many posts in the thread, a post over the length cap, or an attachment that could not be used. |
| 400 | invalid_time | scheduledAt is missing, malformed, or less than two minutes away. |
| 400 | invalid_status | A status the endpoint does not accept. |
| 400 | invalid_community | communityId is not an X Community id. |
| 400 | invalid_range | range is not 7d, 30d, 90d or all. |
| 400 | no_slots | queueNext asked for a slot, but the workspace has no posting schedule or it is full. |
| 400 | queue_full | 200 pending posts. Publish or clear some first. |
| 400 | nothing_to_change | The PATCH body had no field the endpoint knows. |
| 400 | needs_reconnect | The X connection cannot publish: missing write permission or expired. Reconnect in Settings. |
| 401 | unauthorized | No bearer token, or a key that is unknown, revoked or expired. |
| 403 | insufficient_scope | The key lacks the permission this endpoint needs. |
| 403 | inactive | The workspace has no active subscription or credits. Drafts still work; queuing, media and publishing do not. |
| 404 | not_found | No such post in this workspace. |
| 409 | publishing | The post is being published right now. Read it back in a minute. |
| 409 | already_published | The post is already on X. |
| 409 | partially_published | Part of the thread is on X. Write a fresh post instead of requeuing this one. |
| 409 | on_x | The post is on X (or on its way) and cannot be deleted from here. |
| 429 | rate_limited | Too many requests on this key this minute, or X is rate limiting posts. Wait and retry. |
| 429 | daily_limit | 25 posts already went out today. Schedule it for tomorrow. |
| 502 | duplicate, community, media_missing, media_upload, ambiguous | X refused or failed the publish. See below. |
| 500 | server_error | Something failed on the squidX side. Safe to retry reads; read a post back before retrying a write. |
Rate limits
Each key may make 120 requests a minute. Every response carries x-ratelimit-limit and x-ratelimit-remaining; past the limit the API answers 429 rate_limited with retry-after: 60. The MCP endpoint counts against the same budget.
Two more limits protect the X account and apply however the post was created:
- 25 posts a day per workspace, counted from midnight in the workspace timezone. The queue keeps the rest for tomorrow; a publish-now call answers
429 daily_limit. - 200 pending posts (drafts plus queued). Past it, creating answers
400 queue_full.
Publishing failures
A publish call talks to X inside the request, so its failures are X's answers. The rule that matters: squidX never retries a write, and neither should you.
duplicate,community,media_upload,media_missing,rate_limited: X refused before anything went out. The post is exactly where it was. Fix the cause and call again.ambiguous: a timeout, a network error or a 5xx after the call started. The post may be live. It is parked asfailedwithlastErrorset. Read it back, checkpublishedIds, and look at the profile before doing anything else.
Threads
A thread publishes one post at a time. On a failure halfway,
publishedIds lists the segments that made it, and the post refuses to be requeued: the first half is live and a retry would post it twice.