WebSocket API
The WebSocket API gives you a persistent, bidirectional connection for real-time messaging, typing indicators, and streaming AI responses. Use it alongside the REST API for a fully interactive experience.
Connection
Open a WebSocket connection to the endpoint below. Authenticate by passing your access token as a query parameter, or by sending an auth message immediately after connecting.
wss://api.replr.ai/v1/wsOption 1 — Query parameter: Append your token to the URL.
wss://api.replr.ai/v1/ws?token=YOUR_ACCESS_TOKENOption 2 — Auth message: Connect without a token and send an auth message as the first frame.
// Alternative: authenticate via the first message instead of query param
const ws = new WebSocket("wss://api.replr.ai/v1/ws");
ws.addEventListener("open", () => {
ws.send(JSON.stringify({ type: "auth", token: "YOUR_ACCESS_TOKEN" }));
});Message Format
All messages are JSON-encoded text frames.
{
"type": "message",
"payload": {
"conversation_id": "conv_abc123",
"content": "Hello!"
}
}{
"type": "message",
"payload": {
"role": "assistant",
"content": "Hey there! How can I help?",
"created_at": "2026-03-09T12:00:00Z"
},
"error": null
}Client Events
Events you send to the server.
| Type | Description |
|---|---|
subscribe | Subscribe to real-time updates for a conversation. |
unsubscribe | Stop receiving updates for a conversation. |
message | Send a message to a conversation. |
typing | Broadcast a typing indicator to other participants. |
ping | Keep the connection alive. No payload required. |
Server Events
Events you receive from the server.
| Type | Description |
|---|---|
message | A new message was created in a subscribed conversation. Includes role, content, and created_at. |
typing | Another participant in the conversation is typing. |
stream_start | An AI response has started generating. |
stream_delta | A chunk of the AI response. The payload contains a content delta string. |
stream_end | The AI response is complete. The payload contains the full message object. |
error | An error occurred. The payload includes a code and human-readable message. |
pong | Response to a client ping. No payload. |
Streaming AI Responses
When a REPLR generates a response, you receive three events in sequence: stream_start, one or more stream_delta chunks, and a final stream_end with the complete message. Concatenate the deltas for a live typing effect.
// stream_start
{"type": "stream_start", "payload": {"conversation_id": "conv_abc123"}}
// stream_delta (repeated)
{"type": "stream_delta", "payload": {"content": "Sure, "}}
{"type": "stream_delta", "payload": {"content": "I can "}}
{"type": "stream_delta", "payload": {"content": "help with that!"}}
// stream_end
{"type": "stream_end", "payload": {"role": "assistant", "content": "Sure, I can help with that!", "created_at": "2026-03-09T12:00:01Z"}}Full Example
Connect, subscribe to a conversation, send a message, and handle all incoming events.
const ws = new WebSocket("wss://api.replr.ai/v1/ws?token=YOUR_ACCESS_TOKEN");
ws.addEventListener("open", () => {
console.log("Connected");
// Subscribe to a conversation
ws.send(JSON.stringify({
type: "subscribe",
payload: { conversation_id: "conv_abc123" },
}));
});
ws.addEventListener("message", (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case "message":
console.log(`[${msg.payload.role}] ${msg.payload.content}`);
break;
case "stream_delta":
process.stdout.write(msg.payload.content);
break;
case "stream_end":
console.log("\nAI response complete:", msg.payload.content);
break;
case "error":
console.error("Error:", msg.payload.message);
break;
case "pong":
break;
}
});
// Send a message
ws.send(JSON.stringify({
type: "message",
payload: {
conversation_id: "conv_abc123",
content: "Hello, what can you help me with?",
},
}));
// Keep alive — send a ping every 30 seconds
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: "ping" }));
}
}, 30_000);Reconnection
The server will close idle connections after 5 minutes without a ping. Send a ping at least every 30 seconds to keep the connection open. If the connection drops, reconnect with exponential backoff to avoid overwhelming the server.
Tip: Start with a 1-second delay and double it on each retry, capping at 30 seconds. Reset the counter after a successful connection.
function connectWithBackoff(url, maxRetries = 10) {
let attempt = 0;
function connect() {
const ws = new WebSocket(url);
ws.addEventListener("open", () => {
console.log("Connected");
attempt = 0; // reset on success
});
ws.addEventListener("close", (event) => {
if (attempt < maxRetries) {
const delay = Math.min(1000 * 2 ** attempt, 30_000);
console.log(`Reconnecting in ${delay}ms (attempt ${attempt + 1})`);
setTimeout(connect, delay);
attempt++;
}
});
return ws;
}
return connect();
}
const ws = connectWithBackoff(
"wss://api.replr.ai/v1/ws?token=YOUR_ACCESS_TOKEN"
);Error Handling
When something goes wrong, the server sends an error event. Common error codes:
| Code | Meaning |
|---|---|
auth_failed | Invalid or expired token. Re-authenticate and reconnect. |
not_subscribed | You sent a message to a conversation you haven't subscribed to. |
conversation_not_found | The conversation ID does not exist or you lack access. |
rate_limited | Too many messages in a short period. Back off and retry. |
internal_error | Something went wrong on the server. Retry with backoff. |
// Example error event
{
"type": "error",
"payload": {
"code": "not_subscribed",
"message": "You must subscribe to conversation conv_abc123 before sending messages."
},
"error": true
}