Operations
Every error code your client may observe, on HTTP and over the WebSocket.
Errors happen on two surfaces: the HTTP endpoints (/v1/token mint and the /v1 WebSocket upgrade) and the WebSocket itself after the connection is open. Each surface has its own shape.
HTTP errors
All HTTP errors are JSON with a single error field. They happen before any WebSocket exists.
| HTTP | Body | Surface | When |
|---|---|---|---|
401 | {"error":"invalid_key"} | POST /v1/token | API key missing or unknown |
401 | {"error":"missing_token"} | GET /v1 upgrade | No token query parameter |
401 | {"error":"invalid_token"} | GET /v1 upgrade | Token unknown or expired |
403 | {"error":"forbidden_ip"} | Both | Source IP is not in your allowlist. See IP allowlist |
405 | {"error":"method_not_allowed"} | POST /v1/token | Request was not POST |
429 | {"error":"rate_limited"} | POST /v1/token | Too many mint attempts. Includes Retry-After: 60 header. See Rate limits |
WebSocket events
Once the connection is open, errors arrive as JSON messages with a stable code and a short human-readable message. Branch on code in your handler; treat message as for-humans-only.
{ "type": "error", "code": "bad_request", "message": "invalid message" }
| Code | What it means | Real message strings |
|---|---|---|
bad_request | Your message was not valid JSON, was over 16 KB, or used an unknown shape | "invalid message", "message too large", "empty users", "bad request" |
invalid_username | A username in your request didn’t pass validation | "invalid", "invalid username" |
user_not_found | The X account isn’t accessible | "account not found or not accessible" |
rate_limited | A rate limit was hit on this operation | "rate limited, retry later" |
too_many_subscriptions | The operation would exceed your maxSubs cap | "max 5000" (the actual cap is interpolated) |
x_internal | Something went wrong on our side; details are intentionally generic | "internal error" |
managed_only | Your account’s handle set is curated externally; subscribe and unsubscribe operations aren’t accepted | "subscriptions are externally managed" |
WebSocket close codes
When the server itself closes the WebSocket, it uses a standard close code. See Close codes for the full table.
What to do
401on mint or upgrade: check your API key and that your mint flow is correct. See Authentication.403 forbidden_ip: your source IP is locked out. Verify your server’s external IP and confirm it’s in your allowlist.429: wait the seconds inRetry-Afterbefore retrying. Don’t tight-loop the mint endpoint.- WebSocket
errorwithcode: "x_internal": transient on our side. The connection stays open; you may receive normal events again. If it persists, contact us. - Persistent failures: contact us with your account email and the timestamps of the failures.