LatteStream®
Quick Start
Getting Started
Building Custom SDKs
SDKs & Libraries
JavaScript/TypeScript
Node.js / Bun / Deno
Python
Go
PHP
API Reference
WebSocket API
REST API
Webhooks
Authentication
Server-side HTTP API for triggering events and managing channels
The LatteStream REST API enables server-side applications to trigger events, generate client tokens, authorize channels, and query channel information. All REST API endpoints require authentication with your private API key.
https://{cluster}.lattestream.com
Clusters: eu1
All REST API requests require authentication using your private API key (lsk_*).
Authentication Method: API key in request body
{ "api_key": "lsk_your_private_key", ... }
Security Warning: Never expose your private API key in client-side code. REST API endpoints should only be called from your server.
Trigger an event on one or more channels.
Endpoint:
POST /events
Request Headers:
Content-Type: application/json
Request Body:
{ "channels": ["channel-1", "channel-2"], "event": "my-event", "data": { "message": "Hello, world!", "timestamp": 1674123456789 } }
Parameters:
channels (required): Array of channel names (max 10 channels)event (required): Event name (max 200 characters)data (optional): Event payload (max 32KB, will be JSON stringified)Response:
{ "channels": ["channel-1", "channel-2"], "event": "my-event", "triggered": 5 }
Response Fields:
channels: Array of channels event was triggered onevent: Event nametriggered: Number of clients that received the eventExample (Node.js):
const response = await fetch('https://eu1.lattestream.com/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channels: ['chat-room-1'], event: 'new-message', data: { user: 'Alice', message: 'Hello!', timestamp: Date.now(), }, }), }); const result = await response.json(); console.log(`Triggered to ${result.triggered} clients`);
Example (Python):
import requests response = requests.post('https://eu1.lattestream.com/events', json={ 'channels': ['chat-room-1'], 'event': 'new-message', 'data': { 'user': 'Alice', 'message': 'Hello!', 'timestamp': int(time.time() * 1000) } }) result = response.json() print(f"Triggered to {result['triggered']} clients")
Example (Go):
package main import ( "bytes" "encoding/json" "net/http" ) type TriggerRequest struct { Channels []string `json:"channels"` Event string `json:"event"` Data map[string]interface{} `json:"data"` } func triggerEvent() { payload := TriggerRequest{ Channels: []string{"chat-room-1"}, Event: "new-message", Data: map[string]interface{}{ "user": "Alice", "message": "Hello!", }, } body, _ := json.Marshal(payload) resp, _ := http.Post( "https://eu1.lattestream.com/events", "application/json", bytes.NewBuffer(body), ) defer resp.Body.Close() }
Error Response:
{ "error": "Invalid channel name", "code": "INVALID_CHANNEL" }
Trigger multiple events in a single request for better performance.
Endpoint:
POST /batch_events
Request Body:
{ "batch": [ { "channel": "channel-1", "name": "event-1", "data": { "message": "First event" } }, { "channel": "channel-2", "name": "event-2", "data": { "message": "Second event" } }, { "channel": "channel-3", "name": "event-3", "data": { "message": "Third event" } } ] }
Parameters:
batch (required): Array of events (max 10 events per batch)
channel (required): Channel namename (required): Event namedata (optional): Event payloadResponse:
{ "batch_id": 1674123456789123456, "total_events": 3, "total_triggered": 42 }
Response Fields:
batch_id: Unique identifier for this batch (timestamp-based)total_events: Number of events in the batchtotal_triggered: Total number of clients that received eventsExample:
const response = await fetch('https://eu1.lattestream.com/batch_events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ batch: [ { channel: 'user-123', name: 'notification', data: { type: 'message', text: 'You have a new message' }, }, { channel: 'user-456', name: 'notification', data: { type: 'mention', text: 'You were mentioned' }, }, ], }), }); const result = await response.json(); console.log( `Batch ${result.batch_id}: ${result.total_triggered} clients notified` );
Use Cases:
Generate a JWT token for client-side authentication.
Endpoint:
POST /apps/token
Request Body:
{ "api_key": "lsk_your_private_key", "socket_id": "user_123", "permissions": ["read", "write"], "expires_in": 1800 }
Parameters:
api_key (required): Your private API keysocket_id (required): Client identifier (e.g., user ID)permissions (optional): Array of permissionsexpires_in (optional): Token lifetime in seconds (max 86400 = 24 hours)Response:
{ "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", "token_type": "Bearer", "expires_in": 1800, "tenant_id": "123" }
Example:
app.post('/api/get-lattestream-token', async (req, res) => { const userId = req.user.id; // From your auth system const response = await fetch('https://eu1.lattestream.com/apps/token', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ api_key: process.env.LATTESTREAM_PRIVATE_KEY, socket_id: userId, expires_in: 3600, // 1 hour }), }); const { access_token } = await response.json(); res.json({ token: access_token }); });
See Authentication Guide for more details on JWT tokens.
Authorize a client to access a private or presence channel.
Endpoint:
POST /auth
Request Body:
{ "socket_id": "123.456", "channel_name": "private-user-123" }
Parameters:
socket_id (required): Client's socket ID (from connection_established)channel_name (required): Channel to authorizeResponse (Private Channel):
{ "auth": "lspc_encrypted_token" }
Response (Presence Channel):
{ "auth": "lspc_encrypted_token", "channel_data": { "user_id": "user123", "user_info": { "name": "Alice", "avatar": "https://..." } } }
Example:
This endpoint is typically implemented in your application server, not called directly. See Authentication Guide for complete implementation.
const { LatteStreamServer } = require('@lattestream/server'); const server = new LatteStreamServer('lsk_your_private_key'); app.post('/auth', (req, res) => { const { socket_id, channel_name } = req.body; const user = req.session.user; if (!user) { return res.status(401).json({ error: 'Unauthorized' }); } // For presence channels if (channel_name.startsWith('presence-')) { const auth = server.authorizeChannel(socket_id, channel_name, { user_id: user.id, user_info: { name: user.name, avatar: user.avatar, }, }); return res.json(auth); } // For private channels const auth = server.authorizeChannel(socket_id, channel_name); res.json(auth); });
Get a list of all active channels with subscriber information.
Endpoint:
GET /channels
Response:
{ "channels": [ { "channel": "chat-room-1", "member_count": 5, "members": [ { "user_id": "user123", "user_info": { "name": "Alice" }, "socket_id": "123.456" }, { "user_id": "user456", "user_info": { "name": "Bob" }, "socket_id": "123.789" } ] }, { "channel": "presence-lobby", "member_count": 12, "members": [...] } ], "total_channels": 2 }
Response Fields:
channels: Array of active channels
channel: Channel namemember_count: Number of subscribersmembers: Array of members (presence channels only)total_channels: Total number of active channelsExample:
const response = await fetch('https://eu1.lattestream.com/channels'); const { channels, total_channels } = await response.json(); console.log(`${total_channels} active channels`); channels.forEach((ch) => { console.log(`${ch.channel}: ${ch.member_count} members`); });
Use Cases:
REST API endpoints have rate limits to ensure fair usage and system stability.
All API responses include rate limit headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1674123456
Headers:
X-RateLimit-Limit: Maximum requests per windowX-RateLimit-Remaining: Remaining requests in current windowX-RateLimit-Reset: Unix timestamp when the limit resets| Endpoint | Rate Limit | Burst Limit | Window |
|---|---|---|---|
/events | 1,000/min | 100/sec | 1 minute |
/batch_events | 500/min | 50/sec | 1 minute |
/apps/token | 2,000/min | 200/sec | 1 minute |
/channels | 500/min | 50/sec | 1 minute |
{ "error": "Rate limit exceeded", "code": "RATE_LIMIT_EXCEEDED", "retry_after": 30 }
Status Code: 429 Too Many Requests
Handling Rate Limits:
async function triggerWithRetry(channel, event, data, maxRetries = 3) { for (let attempt = 0; attempt < maxRetries; attempt++) { const response = await fetch('https://eu1.lattestream.com/events', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ channels: [channel], event, data }), }); if (response.status === 429) { const retryAfter = parseInt(response.headers.get('Retry-After') || '30'); console.log(`Rate limited, retrying after ${retryAfter}s`); await sleep(retryAfter * 1000); continue; } return await response.json(); } throw new Error('Max retries exceeded'); }
{ "error": "Error message", "code": "ERROR_CODE", "details": { "field": "Additional error details" } }
| Code | Description | Common Causes |
|---|---|---|
200 | OK | Request successful |
400 | Bad Request | Invalid JSON, missing required parameters |
401 | Unauthorized | Invalid or missing API key |
403 | Forbidden | Insufficient permissions |
404 | Not Found | Invalid endpoint or resource not found |
413 | Payload Too Large | Message exceeds 32KB limit |
429 | Too Many Requests | Rate limit exceeded |
500 | Internal Server Error | Service issue (contact support) |
503 | Service Unavailable | Service temporarily unavailable |
| Code | Description | Solution |
|---|---|---|
INVALID_API_KEY | API key is invalid | Check your private API key |
INVALID_CHANNEL | Channel name is invalid | Follow channel naming rules |
INVALID_EVENT | Event name is invalid | Check event name format |
PAYLOAD_TOO_LARGE | Message exceeds size limit | Reduce message size (<32KB) |
RATE_LIMIT_EXCEEDED | Too many requests | Implement backoff and retry |
TOO_MANY_CHANNELS | Too many channels in request | Max 10 channels per request |
TOO_MANY_EVENTS | Too many events in batch | Max 10 events per batch |
const { LatteStreamServer } = require('@lattestream/server'); const server = new LatteStreamServer('lsk_your_private_key'); // Trigger event await server.trigger('chat-room-1', 'new-message', { user: 'Alice', message: 'Hello!', timestamp: Date.now(), }); // Trigger to multiple channels await server.trigger(['channel-1', 'channel-2'], 'broadcast', { announcement: 'Server maintenance in 5 minutes', }); // Batch events await server.batchTrigger([ { channel: 'user-123', event: 'notification', data: { type: 'message' } }, { channel: 'user-456', event: 'notification', data: { type: 'mention' } }, ]); // Authorize channel const auth = server.authorizeChannel('123.456', 'private-user-123'); // Authorize presence channel const presenceAuth = server.authorizeChannel('123.456', 'presence-room-1', { user_id: 'user123', user_info: { name: 'Alice', avatar: 'https://...' }, });
from lattestream import LatteStreamServer server = LatteStreamServer('lsk_your_private_key') # Trigger event server.trigger('chat-room-1', 'new-message', { 'user': 'Alice', 'message': 'Hello!', 'timestamp': int(time.time() * 1000) }) # Trigger to multiple channels server.trigger(['channel-1', 'channel-2'], 'broadcast', { 'announcement': 'Server maintenance in 5 minutes' }) # Authorize channel auth = server.authorize_channel('123.456', 'private-user-123')
<?php require 'vendor/autoload.php'; use LatteStream\LatteStreamServer; $server = new LatteStreamServer('lsk_your_private_key'); // Trigger event $server->trigger('chat-room-1', 'new-message', [ 'user' => 'Alice', 'message' => 'Hello!', 'timestamp' => time() * 1000 ]); // Authorize channel $auth = $server->authorizeChannel('123.456', 'private-user-123');
Next Steps: Explore Webhooks for server-side event notifications or check out the Getting Started guide.