Skip to content

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.

{
"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.

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.

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 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.

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
archive-with-retry.ts
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
}
}