Errors
Every failed call answers with the same JSON body and a stable code. Branch on the code and the status, and show the message to people only. This page explains the body, what each status class means, and which errors are worth retrying.
The error body
Section titled “The error body”{ "status_code": 422, "error": "conversation_archived", "message": "Conversation is not active", "path": "/api/v1/tenants/fitness/applications/fitness-app/conversations/c-1/messages", "timestamp": "2026-10-14T09:30:00.000Z"}| Field | Meaning |
|---|---|
status_code |
The HTTP status, repeated. |
error |
A stable, machine-readable snake_case code. This is what your code branches on. |
message |
Text for a human. It can change, so never match on it. Request validation can return an array of messages, one per problem. |
path |
The path of the request, without the query string. |
timestamp |
When the error happened, in UTC. Quote it and the path when you contact support. |
Messages never contain tokens or verification details.
What each status means
Section titled “What each status means”| Status | Meaning | Examples | Retry? |
|---|---|---|---|
| 400 | The request is wrong | invalid_request, body_too_long, invalid_cursor |
No. Fix the request. |
| 401 | The token is missing, invalid or expired | invalid_token |
Once, with a fresh token. |
| 403 | The caller may not do this | missing_capability, not_conversation_member, client_context_mismatch |
No. Change permissions, membership or context. |
| 404 | It does not exist, or it belongs to another tenant or application | not_found, application_not_found |
No. Check the ids and the context. |
| 413 | The body is larger than 256 kB | payload_too_large |
No. Send less. |
| 422 | A rule rejected it | target_user_not_eligible, conversation_archived, direct_conversation_immutable |
No. The request is well-formed but not allowed in this state. |
| 429 | You are over a rate limit | rate_limited |
Yes, after Retry-After. |
| 500, 503 | Chat or the Identity Server had a problem | server_error, identity_unavailable |
Yes, with backoff. |
A 404 never reveals whether a resource exists in another tenant. Chat answers the same either way.
The full list, with causes and fixes, is in Error reference.
Rate limits
Section titled “Rate limits”A 429 carries a Retry-After header with the number of seconds to wait. Browsers can read it, also across origins. The limits themselves are on Limits.
Retry rules
Section titled “Retry rules”Retry 429 after the time in Retry-After, and 5xx with exponential backoff. Do not retry other 4xx errors unchanged, because the same request fails the same way. A 401 is the one exception: renew the token and retry once.
Create calls are safe to repeat where it matters: a DIRECT conversation is deduplicated on its two members, so a retried create returns the same conversation.
In the SDKs
Section titled “In the SDKs”ccs-chat-client and ccs-chat-user-client throw ChatApiError with status, code (the error field) and body. Subclasses let you branch by status:
| Status | Class |
|---|---|
| 401 | ChatUnauthorizedError |
| 403 | ChatForbiddenError |
| 404 | ChatNotFoundError |
| 422 | ChatUnprocessableError |
| 429 | ChatRateLimitedError, with retryAfterSeconds |
| Anything else | ChatApiError |
import { ChatApiError, ChatRateLimitedError, ChatUnprocessableError, type ChatClient,} from '@central-code-station/ccs-chat-client';
export async function archiveWhenCoachingEnds(chat: ChatClient, conversationId: string): Promise<void> { try { await chat.conversations.archive(conversationId); } catch (error) { if (error instanceof ChatRateLimitedError) { // 429: wait, then retry. retryAfterSeconds comes from the Retry-After header. await new Promise((resolve) => setTimeout(resolve, (error.retryAfterSeconds ?? 1) * 1000)); return archiveWhenCoachingEnds(chat, conversationId); } if (error instanceof ChatUnprocessableError) { console.warn(`Rule rejected: ${error.code}`); // for example direct_conversation_immutable return; } if (error instanceof ChatApiError && error.status >= 500) throw error; // retry later with backoff throw error; // other 4xx: fix the request, do not retry }}