REST API Reference#
The ChatAPI REST API provides HTTP endpoints for chat operations. All endpoints require authentication and return JSON responses.
Authentication#
Standard Endpoints#
Include these headers with every request:
X-API-Key: your-tenant-api-key
X-User-Id: user-identifier- X-API-Key: Identifies your tenant (organization). API keys are stored as SHA-256 hashes in the database; the plaintext key is returned only once at tenant creation and cannot be retrieved again.
- X-User-Id: Identifies the user performing the action
Admin Endpoints#
Admin endpoints require master API key authentication:
X-Master-Key: your-master-api-key- X-Master-Key: Master key for administrative operations (set via
MASTER_API_KEYenv var) - Used for tenant creation and system administration
Rooms#
List Rooms#
List all rooms the authenticated user belongs to.
GET /roomsResponse:
{
"rooms": [
{
"room_id": "room_abc123",
"type": "dm",
"unique_key": "dm:alice:bob",
"name": null,
"metadata": "{\"listing_id\":\"lst_99\"}",
"last_seq": 42,
"created_at": "2025-12-13T12:00:00Z"
}
]
}Status Codes:
200- Success401- Authentication failed
Create Room#
Create a new chat room (DM, group, or channel).
POST /roomsRequest Body:
{
"type": "dm" | "group" | "channel",
"members": ["user1", "user2"],
"name": "Optional room name",
"metadata": "{\"listing_id\":\"lst_99\",\"order_id\":\"ord_42\"}"
}The metadata field is an optional arbitrary JSON string. It is stored as-is and returned on every room object. Use it to attach app-level context (listing IDs, order IDs, etc.) that your application needs without storing it separately. It is also included in offline webhook payloads.
Response:
{
"room_id": "room_abc123",
"type": "dm",
"unique_key": "dm:user1:user2",
"name": null,
"metadata": "{\"listing_id\":\"lst_99\",\"order_id\":\"ord_42\"}",
"last_seq": 0,
"created_at": "2025-12-13T12:00:00Z"
}Status Codes:
201- Room created successfully400- Invalid request parameters401- Authentication failed409- Room already exists (for DMs)
Get Room#
Retrieve room information.
GET /rooms/{room_id}Response:
{
"room_id": "room_abc123",
"type": "group",
"name": "Team Chat",
"metadata": null,
"last_seq": 42,
"created_at": "2025-12-13T12:00:00Z"
}List Room Members#
Get all members of a room.
GET /rooms/{room_id}/membersResponse:
[
{
"user_id": "user1",
"role": "admin",
"joined_at": "2025-12-13T12:00:00Z"
},
{
"user_id": "user2",
"role": "member",
"joined_at": "2025-12-13T12:05:00Z"
}
]Messages#
Send Message#
Send a message to a room.
POST /rooms/{room_id}/messagesRequest Body:
{
"content": "Hello, world!",
"meta": "{\"type\":\"text\",\"mentions\":[\"user2\"]}"
}The meta field is an optional arbitrary JSON string that is stored with the message and returned when reading messages. Use it for client-defined message metadata such as type hints, mention lists, or reaction data.
Response:
{
"message_id": "msg_abc123",
"seq": 43,
"created_at": "2025-12-13T12:10:00Z"
}Get Messages#
Retrieve messages from a room.
GET /rooms/{room_id}/messages?after_seq=40&limit=50Query Parameters:
after_seq(optional): Get messages after this sequence numberlimit(optional): Maximum messages to return (default: 50, max: 100)
Response:
[
{
"message_id": "msg_abc123",
"sender_id": "user1",
"seq": 41,
"content": "Hello, world!",
"meta": "{\"type\":\"text\",\"mentions\":[\"user2\"]}",
"created_at": "2025-12-13T12:10:00Z"
}
]Delivery & Acknowledgments#
Acknowledge Messages#
Mark messages as delivered up to a specific sequence number.
POST /acksRequest Body:
{
"room_id": "room_abc123",
"seq": 43
}Response:
{
"success": true
}Notifications#
Send Notification#
Send a notification to users or room members.
POST /notifyRequest Body:
{
"topic": "order.shipped",
"payload": {
"order_id": "12345",
"tracking_number": "1Z999AA1234567890"
},
"targets": {
"user_ids": ["user1", "user2"],
"room_id": "room_abc123",
"topic_subscribers": true
}
}Response:
{
"notification_id": "notif_abc123",
"created_at": "2025-12-13T12:15:00Z"
}Notification Subscriptions#
Users subscribe to topics to receive notifications sent with "topic_subscribers": true.
Subscribe to a Topic#
POST /subscriptionsRequest Body:
{
"topic": "order.shipped"
}Response (201):
{
"id": 1,
"subscriber_id": "user1",
"topic": "order.shipped",
"created_at": "2025-12-13T12:00:00Z"
}List Subscriptions#
GET /subscriptionsResponse:
{
"subscriptions": [
{
"id": 1,
"subscriber_id": "user1",
"topic": "order.shipped",
"created_at": "2025-12-13T12:00:00Z"
}
]
}Unsubscribe#
DELETE /subscriptions/{id}Response: 204 No Content
WebSocket Tokens#
Issue WebSocket Token#
Issue a short-lived, single-use authentication token for browser WebSocket connections. Browser clients cannot set custom HTTP headers on WebSocket connections, so this token-based flow is required.
POST /ws/tokenAuthentication: Standard X-API-Key + X-User-Id headers.
Response:
{
"token": "wst_a1b2c3d4e5f6...",
"expires_in": 60
}After obtaining a token, connect to the WebSocket endpoint:
wss://your-chatapi.com/ws?token=wst_a1b2c3d4e5f6...The token is valid for 60 seconds and is consumed on first use. If the connection attempt fails, request a new token before retrying.
Status Codes:
200- Token issued successfully401- Invalid API key or user ID
Health & Monitoring#
Health Check#
Check service health and status.
GET /healthResponse:
{
"status": "ok",
"service": "chatapi",
"uptime": "2h30m45s",
"db_writable": true
}Returns 503 Service Unavailable with "status": "error" if the database is not writable.
Server Metrics#
Returns live server counters. No authentication required.
GET /metricsResponse:
{
"active_connections": 42,
"messages_sent": 18340,
"delivery_attempts": 21005,
"delivery_failures": 12,
"broadcast_drops": 3,
"uptime_seconds": 86400
}Fields:
active_connections— currently open WebSocket connectionsmessages_sent— total messages sent since server startdelivery_attempts— total background delivery attemptsdelivery_failures— deliveries that failed permanently (see dead-letter queue)broadcast_drops— broadcasts dropped due to a full send buffer (slow consumer)uptime_seconds— server uptime in seconds
Admin Endpoints#
Create Tenant#
Create a new tenant with an auto-generated API key. Requires X-Master-Key authentication (not X-API-Key).
POST /admin/tenantsAuthentication:
X-Master-Key: your-master-api-keyRequest Body:
{
"name": "MyCompany"
}Response:
{
"tenant_id": "tenant_abc123",
"name": "MyCompany",
"api_key": "sk_abc123def456ghi789jkl012mno345pqr678stu901vwx",
"created_at": "2025-12-13T12:00:00Z"
}Status Codes:
201- Tenant created successfully400- Invalid request parameters401- Invalid master API key500- Server error
Security Note: The api_key is returned only in this response. It is stored as a SHA-256 hash in the database — the plaintext can never be retrieved again. Copy it immediately and store it securely (e.g. in a secrets manager).
Dead Letters#
View failed message and notification deliveries for the authenticated tenant.
GET /admin/dead-letters?limit=100Authentication: Standard X-API-Key header. The tenant is determined from the API key.
Query Parameters:
limit(optional): Maximum results (default: 100, max: 1000)
Response:
{
"failed_messages": [
{
"message_id": "msg_abc123",
"user_id": "user1",
"room_id": "room_abc123",
"attempts": 5,
"last_attempt_at": "2025-12-13T12:20:00Z",
"error": "connection timeout"
}
],
"failed_notifications": [
{
"notification_id": "notif_abc123",
"topic": "order.shipped",
"attempts": 3,
"last_attempt_at": "2025-12-13T12:25:00Z",
"error": "user offline"
}
]
}Error Responses#
All endpoints may return these error formats:
Validation Error:
{
"success": false,
"error": {
"code": "VALIDATION_ERROR",
"message": "Invalid room type. Must be one of: dm, group, channel",
"field": "type"
}
}Authentication Error:
{
"success": false,
"error": {
"code": "AUTHENTICATION_ERROR",
"message": "Invalid API key"
}
}Rate Limit Exceeded:
{
"success": false,
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Try again in 60 seconds"
}
}Rate Limiting#
ChatAPI implements per-tenant rate limiting:
- Default Limit: 100 requests per second per tenant (configurable via
DEFAULT_RATE_LIMIT) - 429 Status: Returned when the limit is exceeded
Retry-After: 60response header is set on 429 responses
Content Types#
- Request Body:
application/json - Response Body:
application/json - Character Encoding: UTF-8
Time Formats#
All timestamps use ISO 8601 format:
2025-12-13T12:00:00Z
2025-12-13T12:00:00.123456ZPagination#
Endpoints that return lists support pagination:
limit: Maximum items per page (default: 50, max: 100)offset: Number of items to skip (default: 0)
Versioning#
API versioning is handled via URL paths:
- Current version: No prefix (v1 implied)
- Future versions:
/v2/,/v3/, etc.
SDK Examples#
JavaScript (Fetch API)#
const API_KEY = 'your-api-key';
const USER_ID = 'user123';
async function sendMessage(roomId, content) {
const response = await fetch(`/rooms/${roomId}/messages`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
'X-User-Id': USER_ID
},
body: JSON.stringify({ content })
});
return response.json();
}Python (requests)#
import requests
API_KEY = 'your-api-key'
USER_ID = 'user123'
BASE_URL = 'https://your-chatapi.com'
def send_message(room_id, content):
headers = {
'Content-Type': 'application/json',
'X-API-Key': API_KEY,
'X-User-Id': USER_ID
}
data = {'content': content}
response = requests.post(
f'{BASE_URL}/rooms/{room_id}/messages',
json=data,
headers=headers
)
return response.json()Go (net/http)#
package main
import (
"bytes"
"encoding/json"
"net/http"
)
const (
apiKey = "your-api-key"
userID = "user123"
baseURL = "https://your-chatapi.com"
)
func sendMessage(roomID, content string) error {
data := map[string]string{"content": content}
jsonData, _ := json.Marshal(data)
req, _ := http.NewRequest("POST",
baseURL+"/rooms/"+roomID+"/messages",
bytes.NewBuffer(jsonData))
req.Header.Set("Content-Type", "application/json")
req.Header.Set("X-API-Key", apiKey)
req.Header.Set("X-User-Id", userID)
client := &http.Client{}
resp, err := client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()
return nil
}