HTTP and WebSocket API
The desktop app uses this API, and so can your own tools and scripts. It is a JSON API under /api/v1.
Stability. The API is not versioned beyond
/v1yet and may change between minor releases while TolkTak is at 0.x. Check the release notes before upgrading a server that your scripts depend on.client/src/renderer/api.tsis the reference client and always matches the server of the same version.
Conventions
- Requests and responses are JSON (
Content-Type: application/json), except file uploads and downloads. - Timestamps are Unix milliseconds. IDs are integers, except file IDs, which are random strings.
- Errors have an HTTP status and a body of the form
{"error": {"code": "invite_invalid", "message": "Invite code is invalid, expired or used up"}}.codeis stable and meant for programs;messageis English text for people. - Rate-limited requests get
429with coderate_limited.
Authentication
POST /api/v1/auth/login
{"username": "alice", "password": "…", "device": "my-script"}
returns {"token": "…", "user": {…}}. Send the token on every other request:
Authorization: Bearer <token>
A token is valid until POST /api/v1/auth/logout. POST /api/v1/auth/register takes username, display_name, password and, in invite mode, invite.
For automation, create a separate account for the script, so its token can be revoked without affecting a person.
Endpoints
| Method and path | Description |
|---|---|
GET /server |
Server name, version, registration mode, LiveKit address. No authentication. |
POST /auth/register, /auth/login, /auth/logout |
Accounts and sessions. |
GET /me, PATCH /me, POST /me/password |
Own profile (display_name, about, status, avatar) and password. |
GET /users |
All users on the server. |
GET /channels |
Channels the user is in: direct messages and group text channels, with the last message and unread count. |
POST /dms |
Open a direct-message channel with user_id. |
GET /channels/{id}/messages |
History. Query: before, after, limit, q (search). |
POST /channels/{id}/messages |
Send: text, nonce (client-generated, makes retries safe), optional reply_to and attachments (file IDs). |
POST /channels/{id}/read |
Mark read up to message_id. |
PATCH /messages/{id}, DELETE /messages/{id} |
Edit or delete own message. |
POST /messages/{id}/reactions |
Toggle the reaction emoji. |
POST, DELETE /messages/{id}/pin; GET /channels/{id}/pins |
Pinned messages. |
POST /files?name=… |
Upload; the body is the raw file. Returns {"file": {"id", …}}. |
GET /files/{id}[/{name}] |
Download. No authentication; see security model. |
GET /groups, POST /groups, PATCH, DELETE /groups/{id} |
Groups. |
POST /groups/{id}/members, DELETE /groups/{id}/members/{uid}, PUT /groups/{id}/members/{uid}/role |
Membership and roles. |
POST /groups/{id}/channels, PATCH, DELETE /text-channels/{id} |
Text channels. |
POST /groups/{id}/rooms, PATCH, DELETE /rooms/{id} |
Voice rooms. |
POST /voice/join, POST /voice/leave |
Get a LiveKit URL and token for room_id (group room) or channel_id (direct call). |
POST /calls/{channel}/ring, /decline, /cancel |
Direct-message calls. |
GET, POST /admin/invites, DELETE /admin/invites/{code} |
Invites. Administrator only. |
Paths in the table are relative to /api/v1. GET /healthz (outside /api/v1) returns ok for health checks, or 503 when the database is unavailable.
Events
Open a WebSocket to /api/v1/ws and send the token as the first frame within 10 seconds:
{"type": "auth", "token": "…"}
The token is never put in the URL, so it does not end up in proxy logs. The server answers with hello (your user, who is online, current voice rooms, server name and version) and then pushes events as {"type": "…", "data": {…}}:
| Event | When |
|---|---|
message.created, message.updated |
A message was sent, edited, deleted, reacted to or pinned. |
channel.created, channel.deleted, channel.read |
Channels appeared or disappeared; read position changed on another device. |
group.updated, group.deleted |
Group settings, members, rooms or text channels changed. |
user.created, user.updated, presence |
Accounts and online status. |
typing |
Someone is typing in a channel. |
voice.room |
Who is in a voice room, with mute, deafen, camera and screen flags. |
call.ring, call.declined, call.cancelled, call.ended |
Direct-message calls. |
ping |
Every 25 seconds. Treat 70 seconds without any frame as a dead connection. |
Events tell you that something changed; after a reconnect, fetch what you missed through REST (GET /channels, then messages?after=…).
A client can send {"type": "typing", "channel_id": …}.
Voice
POST /voice/join returns a LiveKit url, token and room. Connect with any LiveKit SDK to publish or subscribe to audio and video. Tokens are scoped to that one room.
Bot API (planned)
There is no dedicated bot API yet. Today a script can act as a regular user through the API above. Planned for a later version:
- bot accounts created by the administrator, with tokens that do not expire with a login session and are marked as bots in the app;
- per-bot permissions instead of full user rights;
- outgoing webhooks for events, as an alternative to holding a WebSocket open;
- slash commands registered by bots.
Ideas and use cases are welcome in issues.