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.
Base URL
Section titled “Base URL”All routes live under one versioned base URL:
https://chat.centralcodestation.com/api/v1This 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.
Authentication
Section titled “Authentication”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).
Naming and formats
Section titled “Naming and formats”| 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.
expiresis not a timestamp string. The realtime token givesexpiresin epoch milliseconds.nullversus omitted. A field that can have no value is always present andnull, such asavatar_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. OnPATCH, 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 itsmessagelists 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.
The context path
Section titled “The context path”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}/conversationstenant_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.
Audiences
Section titled “Audiences”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.
Pagination
Section titled “Pagination”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"}limitis 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 withinvalid_limit.- Pass
before=<next_cursor>to get the next, older page. Omitbeforefor the newest items. next_cursorisnullon 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.
Errors
Section titled “Errors”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.
Rate limits
Section titled “Rate limits”| 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.
Versioning
Section titled “Versioning”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,PATCHandDELETE, with the request headersContent-TypeandAuthorization. The VAPID key route allowsGETonly. - The
Retry-Afterheader 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 OpenAPI document
Section titled “The OpenAPI document”The whole contract, including these extensions, is one file you can download and generate clients from. See OpenAPI document.