Appearance
Errors & Rate Limits
Error format
Every error response has this shape:
json
{
"error": {
"code": "NOT_FRIENDS",
"message": "You can only message friends.",
"details": { "userId": "01JC…" },
"requestId": "req_7f3a9c"
}
}Use code in your app logic. message is for developers, not for end users. Include requestId when you report a problem to us.
Error codes
| HTTP | code | Meaning | Typical app behaviour |
|---|---|---|---|
| 400 | VALIDATION_FAILED | Invalid body or query. details.fields lists each problem. | Show field errors. Do not retry. |
| 401 | CLIENT_KEY_INVALID | Missing or unknown X-Client-Key. | Configuration bug. |
| 401 | UNAUTHENTICATED | Access token missing, invalid, or expired. | Refresh token once, then retry. |
| 401 | OTP_INVALID | Wrong or expired code. | "Wrong code, try again." |
| 403 | FORBIDDEN | Not allowed (for example, non-admin edits a group). | Hide the action. |
| 403 | PROFILE_INCOMPLETE | Profile setup not finished. | Show profile setup. |
| 403 | NOT_FRIENDS | Direct chat with someone who is not (or no longer) a friend. | Mark message failed. Explain. |
| 404 | NOT_FOUND, USER_NOT_FOUND, CONVERSATION_NOT_FOUND, MESSAGE_NOT_FOUND, FRIEND_REQUEST_NOT_FOUND | Missing or not visible to the caller. | Remove from local store if appropriate. |
| 409 | USERNAME_TAKEN | Username already used. | Show inline error. |
| 409 | ALREADY_FRIENDS | Friend request to an existing friend. | Refresh friendship state. |
| 409 | FRIEND_REQUEST_EXISTS | A pending request already exists from the caller. | Refresh friendship state. |
| 409 | CONFLICT | Other state conflict. | Refresh, then let the user retry. |
| 413 | PAYLOAD_TOO_LARGE | Media exceeds the size limit. | Ask for a smaller file. |
| 422 | EDIT_WINDOW_EXPIRED | Edit or delete-for-everyone too late. | Hide the action after the window. |
| 429 | RATE_LIMITED | Too many requests. Retry-After header in seconds. | Wait and retry. |
| 500 | INTERNAL | Server error. | Retry with backoff. |
| 503 | UNAVAILABLE | Temporary outage or deploy. | Retry with backoff. |
Retry rule: retry automatically on network errors, 429, 500, and 503. Never retry other 4xx without a change.
Rate limits
Limits are per workspace user unless noted. Every response includes:
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287
X-RateLimit-Reset: 1792723340| Scope | Limit |
|---|---|
POST /auth/otp | 3 per phone per 10 minutes |
POST /auth/verify | 5 attempts per phone per 10 minutes |
| Message send (WebSocket + REST, combined) | 30 per 10 seconds |
typing.start | 1 per second per conversation |
GET /users/search | 30 per minute |
POST /friend-requests | 30 per hour |
/developer/* | 120 per minute per workspace |
| Everything else | 300 per minute |
WebSocket frames over a limit get an error reply with code RATE_LIMITED and details.retryAfter in seconds. Repeated abuse closes the socket with code 4008.