Chat
This page describes channel chat between players (global, world, clan, party and direct). Read it if you want to send messages with a player session, read history and receive live messages.
What it does
The chat module (chat) provides channel-based text chat. Messages are written to a durable archive; live delivery is done separately over a WebSocket. The same message can be sent over REST or over the WebSocket, and both apply the same rules (mute, rate limit, profanity filter, replay protection).
It works with the player’s own session. Chat endpoints cannot be called with an API key.
If the module is disabled, the endpoints return 404 with {"code":"module_disabled","module":"chat"}; the chat socket first sends an error frame (code: "module_disabled") and then closes. Enable the module on the Game settings › Modules page.
Where it lives in the panel
| What you want to do | Panel page |
|---|---|
| Enable or disable the module | Game settings › Modules |
| Read the chat of the city (scene) plugin, delete a message, mute a player | City chat |
Note
Panel help pages are not translated yet; the panel links above point to the Turkish source. The panel’s “City chat” screen manages the chat of the scene plugin; for the channel chat on this page there is no panel screen yet for reading, deleting or muting messages.
Data model (in plain words)
-
Channel: Distinguished by
kind:kindreference_idWho can join globalnone Everyone in the environment worldrequired (world/map UUID) Players who know the reference clanrequired (clan UUID) Members of the clan partyrequired (party UUID) Members of the party directnone; direct_player_idis requiredOnly the two players A channel of the same kind and the same reference is created once; asking again returns the existing channel. In a
directchannel the other player cannot be yourself. -
Message:
id,channel_id,sender_player_id,client_message_id,body,sent_at_ms. -
client_message_id: A replay key generated by the client (cannot be empty, at most 200 characters). A second send with the same value in the same channel does not create a new message; it returns the existing message withduplicate: true. -
Mute: Muting a player in the scope
all,global,world,clan,partyordirect. A send by a muted player returns403.
Calls your game makes
All chat endpoints are called with a player session: the session cookie or an opaque session token (Authorization: Bearer <session token>), plus the environment header:
x-crx-env: <environment uuid>
On these endpoints the x-csrf-token double-submit and the Origin check are not applied; the session cookie or session token is enough (unlike the store purchase endpoint). Session rules: Sessions and CSRF.
| Method and path | What it does |
|---|---|
POST /v1/chat/channels | Creates a channel or returns the existing one |
GET /v1/chat/channels | Lists the channels the player is authorized for |
POST /v1/chat/channels/{channel_id}/messages | Sends a message |
GET /v1/chat/channels/{channel_id}/messages | Reads the archive backwards (before_ms, limit 1–100, default 50) |
GET /v1/chat/ws | Live chat WebSocket |
Note
The TypeScript and Unity SDKs have no ready-made client for chat; call the endpoints with raw REST and WebSocket. See TypeScript SDK.
Creating a channel
POST /v1/chat/channels
x-crx-env: <environment uuid>
Content-Type: application/json
{ "kind": "clan", "reference_id": "0192f0aa-…" }
{ "channel": { "id": "0192f2bb-…", "environment_id": "…", "kind": "clan", "reference_id": "0192f0aa-…", "participant_a": null, "participant_b": null, "created_at_ms": 1790000000000 } }
A player who is not a member of the clan or party is rejected when creating or reading that channel.
Sending a message
POST /v1/chat/channels/0192f2bb-…/messages
x-crx-env: <environment uuid>
Content-Type: application/json
{ "client_message_id": "c-0001", "body": "Hello!" }
{ "message": { "id": "…", "channel_id": "0192f2bb-…", "sender_player_id": "…", "client_message_id": "c-0001", "body": "Hello!", "sent_at_ms": 1790000000123 }, "duplicate": false }
The rules, in order:
400ifclient_message_idis empty or longer than 200 characters.- The body must be 1–2000 Unicode characters and contain at least one non-whitespace character; otherwise
400. 403if the player is muted for that channel.- Rate limit: at most 20 messages per player per 10 seconds; exceeding it returns
429. - Profanity filter: if the message contains a banned word (a whole word separated by whitespace, case-insensitive),
422is returned. - The message is written to the archive, then delivered to live subscribers. Even if delivery fails, the archived message is not deleted.
Reading the archive
GET /v1/chat/channels/{channel_id}/messages?before_ms=1790000000000&limit=50 returns { "messages": [ … ] }. before_ms asks for messages older than this Unix millisecond timestamp; for the next page, pass the sent_at_ms of the oldest message.
Live chat (WebSocket)
When connecting to GET /v1/chat/ws, x-crx-env and the session identity (cookie or Authorization: Bearer) are sent as headers. The upgrade is not performed without authentication (401); 404 if the module is disabled.
Warning
The browser
WebSocketAPI cannot send custom headers (x-crx-env); therefore the chat socket cannot be connected directly from a browser. Browser games can poll with REST; native clients (for example Unity desktop/mobile) can connect by adding headers. A query parameter or subprotocol instead of a header is not supported yet.
Client frames (JSON; the type field is lower camelCase, the other fields are snake_case):
type | Fields | Meaning |
|---|---|---|
subscribe | channel_id | Subscribe to the channel’s live stream |
unsubscribe | — | Drop the subscription |
send | channel_id, client_message_id, body | Send a message, as in REST |
ping | nonce | Liveness probe |
Server frames:
type | Fields |
|---|---|
subscribed | channel_id |
unsubscribed | — |
message | message, duplicate |
pong | nonce |
error | code (invalid_frame, invalid_payload, forbidden_channel, unavailable, message_rejected, module_disabled) |
A socket is subscribed to a single channel at a time. A frame with an unrecognized field gets invalid_frame. If the module is disabled later, the open socket is closed too. For the general realtime surface, see Realtime: overview.
Versioning and replay safety
- Message sending has no
Idempotency-Keyheader; replay safety is provided byclient_message_id. After a network error, resend with the same value: the message appears once (duplicate: true). If-Matchis not used.
Events
A chat event is not delivered as a webhook; chat is not among the webhook event types. Use the WebSocket for the live stream.
Limits
- Message body 1–2000 characters;
client_message_idat most 200 characters. - Send rate: 20 messages per player per 10 seconds.
- An archive page is 1–100 messages.
- A WebSocket can be subscribed to a single channel at a time.
Common errors
| Symptom | Cause | Fix |
|---|---|---|
401 | No session, expired, or x-crx-env missing | Refresh the session; add the header |
403 (send) | The player is muted or has no permission in the channel | Inform the user |
400 | Empty or too long body / client_message_id | Fix the input |
422 | The profanity filter rejected the message | Change the message |
429 | 20 messages per 10 seconds exceeded | Wait and retry |
error: forbidden_channel (WS) | No permission for the channel | Create the channel with REST first / verify the permission |
Working example
Writing to a channel over REST with a session cookie (cookies.txt) and reading the history:
curl -X POST "$API/v1/chat/channels/$CHANNEL/messages" \
-H "x-crx-env: $ENV" \
-H "Content-Type: application/json" -b cookies.txt \
-d '{"client_message_id":"c-0001","body":"Hello!"}'
curl "$API/v1/chat/channels/$CHANNEL/messages?limit=20" \
-H "x-crx-env: $ENV" -b cookies.txt