Skip to content

API conventions

This page lists the rules every Chat API operation follows, so the operation pages do not repeat them. Read it once before you call the API without an SDK, or before you generate a client.

All routes live under one versioned base URL:

https://chat.centralcodestation.com/api/v1

This site writes routes relative to the base URL, for example /tenants/{tenant_id}/presence. The generated operation pages and the OpenAPI document write the full path, starting with /api/v1. SDKs take the base URL, including /api/v1, as chatApiUrl.

Requests and responses are JSON. Send Content-Type: application/json on every request with a body. A request body can be at most 256 kB; a larger one fails with payload_too_large.

Every route except the VAPID key needs a bearer token issued by the Identity Server for the Chat API.

Authorization: Bearer <token>
User token Machine token
Who it represents A signed-in user of your app Your backend client
How you get it Token exchange of the user’s own token client_credentials grant
Scope chat:access chat:sync, or granular chat:* scopes
What it may do What the user’s Chat permissions allow in the application What its scopes grant; see Scopes
Lifetime At most 10 minutes, with no refresh token Until its own expiry; cache it and request a new one before then

Chat accepts a token only when its aud claim is chat-api. When you request a token you pass resource= with the Chat API’s resource identifier, not the audience. See Identity and tokens for both values, and Machine scopes for what each scope allows.

A missing, expired or wrong-audience token fails with invalid_token (401). A token without the scope a route needs fails with insufficient_scope (403).

Rule Example
Routes are kebab-case. /notifications/realtime-token, /admin/role-permissions
Path parameters, query parameters and every JSON field are snake_case, in requests and responses, including nested objects. tenant_id, next_cursor, ?before=…&limit=…
Enum values are UPPER_CASE, except presence statuses (online, offline). DIRECT, ARCHIVED, OWNER
Timestamps are ISO-8601 strings in UTC. 2026-10-14T09:30:00.000Z
Error codes, scopes and capabilities are lower-case with _, : or .. rate_limited, chat:sync, messages.send

Know these exceptions and details:

  • Ids are opaque. Conversation, message and member ids are UUIDs that Chat generates. Tenant, application and user ids come from the Identity Server and can be any string. Never parse an id or assume its format.
  • expires is not a timestamp string. The realtime token gives expires in epoch milliseconds.
  • null versus omitted. A field that can have no value is always present and null, such as avatar_url. Only a few response fields are optional and can be missing; the data types mark them. In a request, leave out what you do not want to set. On PATCH, only the fields you send change.
  • Send only documented fields. A request with a field the route does not accept fails with invalid_request, and its message lists every problem.
  • Read leniently. Ignore response fields you do not know, and handle enum values and error codes you do not know, because additions are not breaking.

Successful responses use 200 with a body, 201 for a created resource, and 204 with no body.

Chat works in one context, a tenant plus an application. You name it in the URL, and only there. Chat does not take the context from the token, from a header or from the body, and it checks the context against the Identity Server on every call.

/tenants/{tenant_id}/applications/{application_id}/conversations

tenant_id is the Identity Server tenant id and application_id the Identity Server id of a Chat-enabled tenant application. There are three kinds of route:

Kind Path starts with Holds
Application routes /tenants/{tenant_id}/applications/{application_id}/ Conversations, messages, user lookup, the realtime token and the caller’s capabilities in the application.
Tenant routes /tenants/{tenant_id}/ The tenant’s applications, the caller’s tenant-wide capabilities, presence, push device registration and administration.
Account routes /me, /me/tenants The signed-in user’s own tenants.

The Chat API’s own GET /notifications/vapid-public-key sits outside every context.

A token works only in the context of the client it was issued to. A machine token and a token of a tenant application’s own client are bound to that tenant and application. Another context fails with client_context_mismatch (403). A user who is not an active member of the tenant gets not_tenant_member, and one without access to the application gets no_application_access. An application that does not exist, or has Chat switched off, answers application_not_found (404). See Tenants and applications.

Each operation says who may call it. Three values exist.

Audience Means
user A signed-in user’s token with chat:access.
machine A backend client’s token with a scope that grants the operation.
public No token at all. Only the VAPID key route is public.

Many operations accept both user and machine. Operations that act as a person, such as reactions, read state, typing and presence, accept users only; a machine token fails on them with principal_not_allowed or user_required.

The OpenAPI document carries three extensions on every operation, which the generated operation pages are built from:

Extension Holds How to read it
x-audience ["user"], ["machine"], ["user", "machine"] or ["public"]. Who can call it.
x-required-scopes { "user": [...], "machine": [...] }. The scope a user token needs, and the machine scopes that grant the operation. A machine token needs any one of the listed scopes.
x-required-capabilities { "all": [...] } or { "any": [...] }. Absent when the route needs only membership. The Chat permissions a user needs in the application: all of them, or at least one. A machine token gets its permissions from its scope, so for machines the scope is what counts.

A user who lacks a permission gets missing_capability; the error message names it. Each operation page lists the permissions and scopes it needs. See Capabilities and Scopes.

Each operation also carries x-error-codes, the list of error codes it can return. See Errors.

Lists that can grow, such as conversations, messages and members, use cursor paging.

GET /tenants/fitness/applications/fitness-app/conversations/{conversation_id}/messages?limit=20
{
"items": [ … ],
"next_cursor": "7a1b2c3d-4e5f-4a6b-8c7d-9e0f1a2b3c4d"
}
  • limit is a positive integer. The default is 50 items and the maximum 100 items. A larger value is capped; a value that is not a positive integer fails with invalid_limit.
  • Pass before=<next_cursor> to get the next, older page. Omit before for the newest items.
  • next_cursor is null on the last page.
  • Send a cursor back exactly as you received it. Chat rejects one it did not issue with invalid_cursor.

There is no page number or offset. See Pagination and the Page type. Lists that are bounded, such as the tenant’s applications, return { "items": [ … ] } without a cursor.

Every error uses the same body, whatever the status:

{
"status_code": 403,
"error": "missing_capability",
"message": "Missing Chat capability messages.send",
"path": "/api/v1/tenants/fitness/applications/fitness-app/conversations/3f7c2a9e-5b1d-4e8a-9c63-1a2b3c4d5e6f/messages",
"timestamp": "2026-10-14T09:30:00.000Z"
}
Field Type Meaning
status_code number The HTTP status.
error string A stable, machine-readable code. Branch on this.
message string or array of strings A human-readable explanation. An array lists every validation problem. Do not parse it.
path string The request path, without the query string.
timestamp string When the error happened, ISO-8601 UTC.

Branch on error, never on message. Codes are stable; a new one can appear, so keep a fallback for codes you do not know. The error catalog lists every code with its status and what to do, and Errors explains retry semantics. Every operation can also return this default error body.

Caller Limit
Backend client, all routes 600 requests per 60 s per client
Sending a message 30 messages per 10 s per user
User lookup 20 lookups per 10 s per user

An over-limit request fails with rate_limited (429) and a Retry-After header holding the number of seconds to wait. Wait that long, then retry. The limits page has the full table, including sizes.

The version is in the path: /api/v1. Chat can add to version 1 without changing the path.

Not breaking: shipped in /api/v1 Breaking: would need a new version
A new operation or route Removing or renaming a route, field, query parameter or enum value
A new optional request field Changing a field’s type, format or meaning
A new response field Making an optional request field required
A new enum value in a response Rejecting input that was accepted before
A new error code Changing the status or error code for an existing condition
A new event type or event field Requiring a scope or permission an operation did not require

Your client must therefore ignore unknown fields and tolerate unknown enum values and error codes. Chat lists API changes on the platform changelog.

Browsers can call the Chat API from the origins you register on your application’s client in the Identity Server, in its allowed_origins. Chat has no setting of its own to change. See Register web origins.

  • A registered origin may call application routes of its own application, plus the presence and push-device routes of its tenant. The VAPID key route is readable from any origin.
  • /me, /me/tenants, the tenant’s application list, the tenant-wide capabilities and the administration routes are for Chat’s own web app only. Call them from a backend, not from your own page.
  • Requests from registered origins carry no cookies. Authenticate with the bearer token only.
  • A registered origin may use GET, POST, PUT, PATCH and DELETE, with the request headers Content-Type and Authorization. The VAPID key route allows GET only.
  • The Retry-After header is readable from the browser.

An origin that is not registered gets no CORS headers, and the browser blocks the response. See Security and isolation.

The whole contract, including these extensions, is one file you can download and generate clients from. See OpenAPI document.