API reference
API reference
This reference describes the chat and voice protocol implemented by the current chatserver and desktop client. Authentication is required by the current server but remains under active development, so its proof format is intentionally not specified here. REST channel management, moderation, and user management endpoints are not implemented.
HTTP endpoints
Use the server’s configured host and port as the base URL. The default port is 8081.
Health and readiness
GET /healthz
GET /readyz
Both endpoints return 200 OK with a short plain-text response (ok or ready).
Voice bootstrap
GET /voice/bootstrap
OPTIONS /voice/bootstrap
The bootstrap response tells the client which server peer receives WebRTC signaling and which ICE servers to use:
{
"peer_id": "voice-sfu",
"ice_servers": [
{"urls": ["stun:stun.l.google.com:19302"]}
]
}
The peer_id is always voice-sfu in the current server implementation. A TURN entry includes username and credential when configured. GET requires the current session proof; OPTIONS returns 204 No Content for CORS preflight. The endpoint currently allows cross-origin browser or webview requests. Authentication proof details are omitted while that flow is being revised.
WebSocket connection
ws://host:8081/ws?name=<display_name>&room=<room_id>
wss://host/ws?name=<display_name>&room=<room_id>
Both name and room are required and must be non-empty. Missing parameters result in 400 Bad Request before the connection is upgraded. The server assigns each accepted connection a member ID such as member-1. The current server requires its session proof during the WebSocket upgrade; proof details are omitted while authentication is under development. Any non-empty room ID is accepted because there is not yet a server-side room/channel catalog.
The client should preserve ws/wss when constructing the URL from an http/https server address. Connections use WebSocket keepalives; clients should reconnect after an unexpected disconnect.
Message envelopes
Client messages use a type field with event-specific fields. Server events use a type and payload:
{
"type": "chat_message",
"content": "hello world"
}
{
"type": "chat_message",
"payload": {
"room_id": "general",
"sender_id": "member-1",
"sender": "alice",
"content": "hello world",
"timestamp": "2026-09-06T10:00:00Z"
}
}
Chat and room events
Send a message
{"type":"chat_message","content":"hello world"}
Content is trimmed and must not be empty. The server appends the message to its configured repository, then broadcasts it to every member of the current room, including the sender. The default repository is in-memory. A repository write failure is logged but does not currently prevent the broadcast.
Switch rooms
{"type":"switch_room","room_id":"design-lab"}
The server leaves the previous room, broadcasts the updated member list there, moves the connection, and sends the new room’s member list and replay. If the client is in voice, it leaves the old voice channel before switching rooms.
Replay and member lists
On connection and room switch, the server sends a message_replay event with up to CHATSERVER_REPLAY_MESSAGE_LIMIT messages (default 50). The selected repository determines ordering. The current in-memory repository returns entries from the beginning of room history rather than the newest entries; latest-message replay is tracked for correction.
{
"type": "member_list",
"payload": {
"room_id": "general",
"members": [{"id":"member-1","display_name":"alice"}]
}
}
Member lists are full snapshots of the current room, not voice-channel rosters. They are broadcast when members join, leave, disconnect, or switch rooms.
Voice events
Voice uses a server-relay/SFU topology. Each participant establishes one peer connection with the server. Clients target voice-sfu for offer, answer, and ICE signaling; clients must not create a mesh of peer-to-peer connections.
Join and leave
{"type":"voice_join","voice_channel_id":"standup"}
{"type":"voice_leave"}
A participant can be in one voice channel at a time. Joining another channel first leaves the current channel. The server broadcasts voice_state and a voice_members snapshot to the room.
Signaling
{"type":"voice_offer","target_peer_id":"voice-sfu","sdp":"v=0..."}
{"type":"voice_answer","target_peer_id":"voice-sfu","sdp":"v=0..."}
{"type":"voice_ice_candidate","target_peer_id":"voice-sfu","candidate":"candidate:..."}
The current desktop client targets voice-sfu, and the server processes those messages through Pion. The server retains legacy direct-target relay handling, but it is not part of the desktop client voice flow. Signaling from clients that are not in a voice channel is rejected.
Voice activity
{
"type":"voice_activity",
"voice_level":0.35,
"voice_peak":0.72,
"voice_active":true
}
The server broadcasts activity to members of the same voice channel with room_id, voice_channel_id, client_id, display_name, voice_level, voice_peak, voice_active, and timestamp.
Errors and isolation
Errors use this envelope:
{
"type":"error",
"payload":{"code":"bad_payload","message":"invalid JSON payload"}
}
Errors include malformed JSON, unsupported event types, empty content, missing room or voice-channel IDs, missing signaling targets, and signaling while not joined to voice. The current server accepts any non-empty room or voice-channel ID; it does not validate IDs against a catalog. The code field currently uses implementation strings and is not yet a stable enum. Room isolation is enforced for messages, member lists, voice state, voice activity, and signaling.
Reserved events
room_schema is reserved by the client for a future server-driven channel layout. The chatserver does not emit it.
Configuration
See Server configuration and Voice configuration for environment variables, ICE setup, and deployment guidance.