API Reference
All API endpoints require authentication via a Bearer token, unless noted otherwise.
Authorization: Bearer <your-jwt-token>Base URL
Section titled “Base URL”Default: http://localhost:3000
Health
Section titled “Health”GET /health
Section titled “GET /health”Health check endpoint. No authentication required.
Response: 200 OK
OKAuthentication
Section titled “Authentication”POST /auth/dev-login
Section titled “POST /auth/dev-login”Development mode login. Only available when auth.authMode is "dev".
Request:
{ "email": "user@example.com"}Response: 200 OK
{ "user": { "id": "abc123", "userName": "user", "email": "user@example.com", "isAdmin": false }, "token": "eyJhbG..."}POST /auth/login
Section titled “POST /auth/login”Request a magic link. Sends an email with a login link. Only available when auth.authMode is "magic-link".
Request:
{ "email": "user@example.com"}Response: 200 OK
{ "success": true, "message": "If a matching account was found, a magic link has been sent."}POST /auth/verify
Section titled “POST /auth/verify”Verify a magic link token and create a session. Only available when auth.authMode is "magic-link". The token is passed via the Authorization: Bearer <token> header.
Request:
{ "email": "user@example.com"}Response: 200 OK
{ "accessToken": "eyJhbG...", "refreshToken": "eyJhbG..."}POST /auth/password-login
Section titled “POST /auth/password-login”Sign in with email and password. Only available when auth.authMode is "password".
Request:
{ "email": "user@example.com", "password": "your-password"}Response: 200 OK
{ "accessToken": "eyJhbG...", "refreshToken": "eyJhbG...", "exp": 900, "tokenType": "Bearer"}Errors:
401 Unauthorized- Invalid credentials
POST /auth/request-password-reset
Section titled “POST /auth/request-password-reset”Request a password reset email. Only available when auth.authMode is "password". Always returns success to prevent email enumeration.
Request:
{ "email": "user@example.com"}Response: 200 OK
{ "success": true, "message": "If a matching account was found, a password reset link has been sent."}POST /auth/setup-password
Section titled “POST /auth/setup-password”Set a password after clicking an invite or password reset link. Only available when auth.authMode is "password". The token can be passed in the request body or via the Authorization: Bearer <token> header.
Request:
{ "email": "user@example.com", "token": "invite-link-token", "password": "your-new-password"}Response: 200 OK
{ "accessToken": "eyJhbG...", "refreshToken": "eyJhbG...", "exp": 900, "tokenType": "Bearer"}Errors:
400 Bad Request- Invalid token or password too short (minimum 8 characters)
POST /auth/register
Section titled “POST /auth/register”Self-register with email and password. Only available when auth.authMode is "password" and isInviteRequired is false.
Request:
{ "email": "user@example.com", "password": "your-password"}Response: 200 OK
{ "accessToken": "eyJhbG...", "refreshToken": "eyJhbG...", "exp": 900, "tokenType": "Bearer"}Errors:
400 Bad Request- User already exists or password too short
GET /auth/oauth/providers
Section titled “GET /auth/oauth/providers”List available OAuth providers. Only present when OAuth is configured. No authentication required.
Response: 200 OK
{ "providers": [ { "id": "google", "name": "Google", "loginUrl": "/auth/oauth/google" } ], "count": 1}GET /auth/oauth/:providerId
Section titled “GET /auth/oauth/:providerId”Initiate an OAuth flow. Redirects the browser to the provider’s authorization page. No authentication required.
Response: 302 Found (redirect to OAuth provider)
Errors:
429 Too Many Requests- Rate limit exceeded
GET /auth/oauth/:providerId/callback
Section titled “GET /auth/oauth/:providerId/callback”OAuth callback handler. Called by the provider after user authentication. Validates the CSRF state, exchanges the authorization code for tokens, and redirects the user back to the application with tokens as query parameters. No authentication required.
Query parameters (set by the OAuth provider):
code- Authorization codestate- CSRF state token
Response: 302 Found (redirect to application URL with access_token, refresh_token, and expires_in query parameters)
Errors (redirected as query parameter oauth_error):
- Missing or invalid state/code
- User not found and invites are required
- Rate limit exceeded
POST /auth/refresh
Section titled “POST /auth/refresh”Refresh an expired access token.
Request:
{ "refreshToken": "eyJhbG..."}Response: 200 OK
{ "accessToken": "eyJhbG...", "refreshToken": "eyJhbG..."}POST /auth/logout
Section titled “POST /auth/logout”End the current session.
Request:
{ "refreshToken": "eyJhbG..."}Response: 200 OK
GET /auth/me
Section titled “GET /auth/me”Get the current authenticated user.
Response: 200 OK
{ "user": { "id": "abc123", "userName": "user", "email": "user@example.com", "isAdmin": false }}GET /auth/sessions
Section titled “GET /auth/sessions”List active sessions for the current user.
Response: 200 OK
DELETE /auth/sessions
Section titled “DELETE /auth/sessions”Revoke sessions for the current user.
Response: 200 OK
GET /users
Section titled “GET /users”List all users.
Response: 200 OK
{ "users": [ { "id": "user456", "userName": "user", "email": "user@example.com", "isAdmin": false, "createdAt": 1706234567890 } ]}POST /users/add
Section titled “POST /users/add”Add a new user. Requires admin.
Request:
{ "email": "newuser@example.com", "role": "user", "password": "optional-initial-password"}Set role to "admin" to create an admin user (requires the requesting user to be an admin).
The password field is optional. When provided in password auth mode, the user’s password is set immediately. When omitted and email is configured, an invite email with a setup link is sent. When omitted and email is not configured, the user is created without a password.
Response: 200 OK
{ "success": true, "userId": "user789"}POST /users/:id/reset-password
Section titled “POST /users/:id/reset-password”Reset a user’s password. Requires admin.
Request:
{ "password": "new-password-min-8-chars"}Response: 200 OK
{ "success": true}Errors:
400 Bad Request- Password too short (minimum 8 characters) or user not found403 Forbidden- Non-admin user
DELETE /users/:id
Section titled “DELETE /users/:id”Remove a user. Requires admin. Cannot remove the last admin.
Response: 200 OK
{ "success": true}POST /users/exit
Section titled “POST /users/exit”Self-remove the current user from the server. Admins can only exit if there is at least one other admin.
Response: 200 OK
{ "success": true, "message": "You have exited the server"}PUT /users/me
Section titled “PUT /users/me”Update the current user’s display name.
Request:
{ "userName": "New Display Name"}Response: 200 OK
{ "user": { "id": "abc123", "userName": "New Display Name", "email": "user@example.com", "isAdmin": false }}Channels
Section titled “Channels”GET /channels
Section titled “GET /channels”List all channels.
Response: 200 OK
{ "channels": [ { "id": "abc123", "name": "general", "createdAt": 1706234567890 }, { "id": "def456", "name": "random", "createdAt": 1706234600000 } ]}POST /channels
Section titled “POST /channels”Create a new channel. Requires admin.
Request:
{ "name": "new-channel"}Response: 200 OK
{ "channel": { "id": "ghi789", "name": "new-channel", "createdAt": 1706234700000 }}PUT /channels/:channelId
Section titled “PUT /channels/:channelId”Update a channel. Requires admin.
Request:
{ "name": "renamed-channel"}Response: 200 OK
{ "channel": { "id": "abc123", "name": "renamed-channel", "updatedAt": 1706234800000 }}DELETE /channels/:channelId
Section titled “DELETE /channels/:channelId”Delete a channel. Requires admin. Deleting a channel also deletes all its messages and webhooks.
Response: 200 OK
{ "success": true}Messages
Section titled “Messages”GET /channels/:channelId/messages
Section titled “GET /channels/:channelId/messages”Get messages in a channel.
Query parameters:
limit(optional): Maximum messages to return (default: 50)before(optional): Get messages before this message ID
Response: 200 OK
{ "messages": [ { "id": "msg123", "channelId": "abc123", "author": { "id": "user456", "userName": "user" }, "content": "Hello, world!", "createdAt": 1706234567890, "updatedAt": null, "reactions": { "👍": ["user456", "user789"] } } ]}POST /channels/:channelId/messages
Section titled “POST /channels/:channelId/messages”Send a message to a channel.
Request:
{ "content": "Hello, world!"}Response: 200 OK
{ "message": { "id": "msg123", "channelId": "abc123", "author": { "id": "user456", "userName": "user" }, "content": "Hello, world!", "createdAt": 1706234567890 }}PUT /messages/:messageId
Section titled “PUT /messages/:messageId”Edit a message. Users can only edit their own messages.
Request:
{ "content": "Updated message"}Response: 200 OK
{ "message": { "id": "msg123", "content": "Updated message", "updatedAt": 1706234800000 }}DELETE /messages/:messageId
Section titled “DELETE /messages/:messageId”Delete a message. Users can delete their own messages. Admins can delete any message.
Response: 200 OK
{ "success": true}Reactions
Section titled “Reactions”POST /messages/:messageId/reactions
Section titled “POST /messages/:messageId/reactions”Add a reaction to a message.
Request:
{ "reaction": "👍"}Response: 200 OK
{ "message": { "id": "msg123", "reactions": { "👍": ["user456"] } }}DELETE /messages/:messageId/reactions
Section titled “DELETE /messages/:messageId/reactions”Remove a reaction from a message.
Request:
{ "reaction": "👍"}Response: 200 OK
{ "message": { "id": "msg123", "reactions": {} }}Images
Section titled “Images”POST /channels/:channelId/messages/image
Section titled “POST /channels/:channelId/messages/image”Upload an image. Images are sent as base64-encoded JSON (not multipart/form-data).
Request:
{ "filename": "photo.jpg", "image": "<base64-encoded-image-data>", "thumbnail": "<base64-encoded-thumbnail>"}image: Base64-encoded image data (max 10MB, formats: JPG, PNG, WebP, SVG)thumbnail(optional): Base64-encoded thumbnail for previews
Response: 200 OK
{ "success": true, "filename": "1706234567890-abc123def456.jpg"}GET /channels/:channelId/messages/image/:filename
Section titled “GET /channels/:channelId/messages/image/:filename”Download an image.
Query parameters:
size(optional): Set to"thumb"to get the thumbnail version
Response: 200 OK
Returns the image data with appropriate Content-Type header.
Threads
Section titled “Threads”Threaded replies allow sub-conversations on any channel message. Thread replies are regular messages linked to a parent message.
GET /messages/:messageId/thread
Section titled “GET /messages/:messageId/thread”Get all replies in a thread.
Query parameters:
limit(optional): Maximum replies to return (default: 50)before(optional): Get replies before this message ID
Response: 200 OK
{ "replies": [ { "id": "reply123", "threadId": "msg123", "channelId": "abc123", "author": { "id": "user456", "userName": "user" }, "content": "Great point!", "createdAt": 1706234567890 } ]}POST /messages/:messageId/thread
Section titled “POST /messages/:messageId/thread”Create a reply in a thread. The parent message must be a top-level channel message (not itself a reply).
Request:
{ "content": "Great point!"}Response: 200 OK
{ "reply": { "id": "reply123", "threadId": "msg123", "channelId": "abc123", "author": { "id": "user456", "userName": "user" }, "content": "Great point!", "createdAt": 1706234567890 }, "threadMeta": { "replyCount": 1, "lastReplyAt": 1706234567890, "lastReplyBy": { "id": "user456", "userName": "user" }, "participants": ["user456"] }}PUT /messages/:messageId/thread/:replyId
Section titled “PUT /messages/:messageId/thread/:replyId”Update a thread reply. Users can only edit their own replies.
Request:
{ "content": "Updated reply"}Response: 200 OK
{ "message": { "id": "reply123", "content": "Updated reply", "updatedAt": 1706234800000 }}DELETE /messages/:messageId/thread/:replyId
Section titled “DELETE /messages/:messageId/thread/:replyId”Delete a thread reply. Users can delete their own replies. Admins can delete any reply. The parent message’s threadMeta is recalculated automatically.
Response: 200 OK
{ "success": true}POST /messages/:messageId/thread/image
Section titled “POST /messages/:messageId/thread/image”Upload an image for a thread reply. Same base64 JSON format as channel image uploads.
Request:
{ "filename": "photo.jpg", "image": "<base64-encoded-image-data>", "thumbnail": "<base64-encoded-thumbnail>"}Response: 200 OK
{ "success": true, "filename": "1706234567890-abc123def456.jpg"}Conversations (Direct Messages)
Section titled “Conversations (Direct Messages)”GET /conversations
Section titled “GET /conversations”List all conversations for the current user.
Response: 200 OK
{ "conversations": [ { "id": "conv123", "participants": ["user456", "user789"], "lastMessageAt": 1706234567890, "otherUser": { "id": "user789", "userName": "other-user" } } ]}POST /conversations
Section titled “POST /conversations”Create or get a conversation with another user.
Request:
{ "targetUserId": "user789"}Response: 200 OK
{ "conversation": { "id": "conv123", "participants": ["user456", "user789"], "otherUser": { "id": "user789", "userName": "other-user" } }, "isNew": true}GET /conversations/:conversationId/messages
Section titled “GET /conversations/:conversationId/messages”Get messages in a conversation. Only participants can access.
Query parameters:
limit(optional): Maximum messages to return (default: 50)before(optional): Get messages before this message ID
Response: 200 OK
{ "messages": [ { "id": "msg123", "channelId": "conv123", "author": { "id": "user456", "userName": "user" }, "content": "Hey!", "createdAt": 1706234567890 } ]}POST /conversations/:conversationId/messages
Section titled “POST /conversations/:conversationId/messages”Send a direct message.
Request:
{ "content": "Hey!"}Response: 200 OK
{ "message": { "id": "msg123", "channelId": "conv123", "author": { "id": "user456", "userName": "user" }, "content": "Hey!", "createdAt": 1706234567890 }}PUT /conversations/:conversationId/messages/:messageId
Section titled “PUT /conversations/:conversationId/messages/:messageId”Edit a direct message. Users can only edit their own messages.
Request:
{ "content": "Updated message"}Response: 200 OK
{ "message": { "id": "msg123", "content": "Updated message", "updatedAt": 1706234800000 }}DELETE /conversations/:conversationId/messages/:messageId
Section titled “DELETE /conversations/:conversationId/messages/:messageId”Delete a direct message.
Response: 200 OK
{ "success": true}POST /conversations/:conversationId/messages/image
Section titled “POST /conversations/:conversationId/messages/image”Upload an image in a conversation. Same base64 JSON format as channel image uploads.
Response: 200 OK
{ "success": true, "filename": "1706234567890-abc123def456.jpg"}GET /conversations/:conversationId/messages/image/:filename
Section titled “GET /conversations/:conversationId/messages/image/:filename”Download an image from a conversation. Only participants can access.
Query parameters:
size(optional): Set to"thumb"to get the thumbnail version
Response: 200 OK
Returns the image data with appropriate Content-Type header.
Server Settings
Section titled “Server Settings”GET /server/settings
Section titled “GET /server/settings”Get server settings.
Response: 200 OK
{ "name": "My Team Chat"}PUT /server/settings
Section titled “PUT /server/settings”Update server settings. Requires admin.
Request:
{ "name": "New Server Name"}Response: 200 OK
{ "name": "New Server Name"}Webhooks
Section titled “Webhooks”Webhooks allow external services to post messages to channels programmatically. Each webhook is scoped to a single channel and uses a static Bearer token for authentication.
GET /webhooks
Section titled “GET /webhooks”List all webhooks. Requires admin. Tokens are stripped from the response.
Response: 200 OK
{ "webhooks": [ { "id": "wh123", "name": "GitHub Bot", "channelId": "abc123", "createdAt": 1706234567890, "createdBy": "user456" } ]}POST /webhooks
Section titled “POST /webhooks”Create a new webhook. Requires admin. The token is returned only once at creation.
Request:
{ "name": "GitHub Bot", "channelId": "abc123"}Response: 200 OK
{ "webhook": { "id": "wh123", "name": "GitHub Bot", "channelId": "abc123", "token": "a1b2c3d4e5f6...", "createdAt": 1706234567890, "createdBy": "user456" }}DELETE /webhooks/:webhookId
Section titled “DELETE /webhooks/:webhookId”Delete a webhook. Requires admin.
Response: 200 OK
{ "success": true}POST /webhooks/:webhookId/messages
Section titled “POST /webhooks/:webhookId/messages”Post a message to a channel via webhook. Uses webhook token authentication instead of JWT.
Headers:
Authorization: Bearer <webhook-token>Request:
{ "content": "Build #42 passed successfully!"}Response: 200 OK
{ "message": { "id": "msg123", "channelId": "abc123", "content": "Build #42 passed successfully!", "author": { "id": "webhook:wh123", "userName": "GitHub Bot", "isBot": true }, "createdAt": 1706234567890 }}Webhook messages appear in the channel with a “BOT” badge next to the author name.
Real-time Events
Section titled “Real-time Events”GET /events
Section titled “GET /events”Server-Sent Events endpoint for real-time updates. Authentication can be provided via query parameter (?token=<jwt>) or Authorization header.
Connection:
GET /events?token=<your-jwt-token>Accept: text/event-streamOn connection, you receive a CONNECTED event:
data: {"type":"CONNECTED","payload":{"message":"SSE connection established","timestamp":"...","userId":"abc123","connectionId":"..."}}Events:
All events are sent as data: lines (no event: header). Each event has a type and payload:
data: {"type":"NEW_MESSAGE","payload":{"id":"msg123","channelId":"abc123","author":{"id":"user456","userName":"user"},"content":"Hello!","createdAt":1706234567890}}
data: {"type":"UPDATE_MESSAGE","payload":{"id":"msg123","content":"Updated","updatedAt":1706234600000}}
data: {"type":"DELETE_MESSAGE","payload":{"id":"msg123","channelId":"abc123"}}
data: {"type":"NEW_CHANNEL","payload":{"id":"ch123","name":"general","createdAt":1706234567890}}
data: {"type":"UPDATE_CHANNEL","payload":{"id":"ch123","name":"renamed"}}
data: {"type":"DELETE_CHANNEL","payload":{"id":"ch123","name":"old-channel"}}
data: {"type":"NEW_REACTION","payload":{"messageId":"msg123","userId":"user456","reaction":"👍"}}
data: {"type":"DELETE_REACTION","payload":{"messageId":"msg123","userId":"user456","reaction":"👍"}}
data: {"type":"NEW_USER","payload":{"id":"user789","userName":"newuser","email":"new@example.com"}}
data: {"type":"REMOVE_USER","payload":{"id":"user789","email":"removed@example.com"}}
data: {"type":"USER_EXIT","payload":{"id":"user789","userName":"exited-user"}}
data: {"type":"UPDATE_USER","payload":{"id":"user789","userName":"new-display-name"}}
data: {"type":"NEW_CONVERSATION","payload":{"id":"conv123","participants":["user456","user789"],"createdAt":1706234567890}}
data: {"type":"NEW_DM_MESSAGE","payload":{"id":"msg123","channelId":"conv123","author":{"id":"user456","userName":"user"},"content":"Hey!","participants":["user456","user789"]}}
data: {"type":"UPDATE_DM_MESSAGE","payload":{"id":"msg123","content":"Updated","participants":["user456","user789"]}}
data: {"type":"DELETE_DM_MESSAGE","payload":{"id":"msg123","conversationId":"conv123","participants":["user456","user789"]}}
data: {"type":"NEW_THREAD_REPLY","payload":{"parentMessageId":"msg123","channelId":"abc123","reply":{...},"threadMeta":{...}}}
data: {"type":"UPDATE_THREAD_REPLY","payload":{...}}
data: {"type":"DELETE_THREAD_REPLY","payload":{"id":"reply123","threadId":"msg123","channelId":"abc123","threadMeta":{...}}}
data: {"type":"NEW_WEBHOOK","payload":{"id":"wh123","name":"GitHub Bot","channelId":"abc123"}}
data: {"type":"DELETE_WEBHOOK","payload":{"id":"wh123","channelId":"abc123"}}
data: {"type":"UPDATE_SERVER_SETTINGS","payload":{"name":"New Server Name"}}DM events (NEW_DM_MESSAGE, UPDATE_DM_MESSAGE, DELETE_DM_MESSAGE) are only sent to the conversation participants.
Error Responses
Section titled “Error Responses”All errors return a JSON object with an error field:
{ "error": "Error message here"}Common status codes:
400 Bad Request- Invalid request body or parameters401 Unauthorized- Missing or invalid authentication403 Forbidden- Insufficient permissions404 Not Found- Resource not found429 Too Many Requests- Rate limit exceeded500 Internal Server Error- Server error