WebSocket Protocol
UG Labs uses a WebSocket-based protocol for real-time, bidirectional communication between your application and our AI services.
Connection URL
wss://pug.stg.uglabs.app/interact
Message Structure
All messages sent and received through the WebSocket are JSON objects with a common structure:
interface BaseMessage {
kind: string; // Message type
uid: string; // Unique identifier for correlation
[key: string]: any; // Additional fields based on message type
}
Connection Flow
1. Establish WebSocket Connection
const ws = new WebSocket('wss://pug.stg.uglabs.app/interact');
ws.onopen = () => {
console.log('WebSocket connected');
};
2. Authentication
Before WebSocket authentication, you need to obtain an access token:
REST Authentication (performed once):
const response = await fetch('https://pug.stg.uglabs.app/api/auth/login', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
api_key: 'your-api-key',
federated_id: 'user-123' // Optional
})
});
const { access_token } = await response.json();
Then authenticate the WebSocket connection:
ws.send(JSON.stringify({
kind: 'authenticate',
uid: crypto.randomUUID(),
access_token: access_token
}));
Response:
{
"kind": "authenticated",
"uid": "matching-uid",
"success": true
}
3. Set Configuration
Configure the conversation behavior:
ws.send(JSON.stringify({
kind: 'set_configuration',
uid: crypto.randomUUID(),
prompt: 'You are a helpful assistant.',
utilities: {
// Optional utilities
}
}));
Response:
{
"kind": "configured",
"uid": "matching-uid"
}
4. Interact
Send messages and receive responses:
ws.send(JSON.stringify({
kind: 'interact',
uid: crypto.randomUUID(),
text: 'Hello! How are you?',
audio_output: false
}));
Streaming Response:
{
"kind": "text",
"uid": "matching-uid",
"text": "Hello! I'm doing well, thanks for asking.",
"done": false
}
{
"kind": "text",
"uid": "matching-uid",
"text": " How can I help you today?",
"done": true
}
Message Types
Request Messages
- authenticate - Authenticate the WebSocket connection
- set_configuration - Configure conversation parameters
- interact - Send a text message or process audio input
- add_audio - Stream audio chunks for voice input
- generate_image - Request image generation
Response Messages
- authenticated - Confirmation of successful authentication
- configured - Confirmation of configuration
- text - Streaming text response
- audio - Audio data chunks (base64 encoded)
- data - Structured data from utilities
- image - Generated image (base64 encoded)
- error - Error message
See Message Types for detailed documentation of each message type.
UID Correlation
Each request includes a unique uid field. Responses related to that request will include the same uid, allowing you to correlate requests and responses:
const requestUid = crypto.randomUUID();
ws.send(JSON.stringify({
kind: 'interact',
uid: requestUid,
text: 'Hello'
}));
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.uid === requestUid) {
// This response is for our request
console.log('Response:', message);
}
};
Streaming Responses
Text responses are streamed in chunks for real-time display:
let fullResponse = '';
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.kind === 'text') {
fullResponse += message.text;
if (message.done) {
console.log('Complete response:', fullResponse);
fullResponse = '';
}
}
};
Audio Streaming
Audio input is sent in chunks:
// Send multiple audio chunks
for (const chunk of audioChunks) {
ws.send(JSON.stringify({
kind: 'add_audio',
uid: crypto.randomUUID(),
audio: base64AudioData,
config: {
sample_rate: 48000,
channels: 1,
encoding: 'opus'
}
}));
}
// Then trigger processing
ws.send(JSON.stringify({
kind: 'interact',
uid: crypto.randomUUID(),
audio_output: true
}));
Audio output is also streamed in chunks for real-time playback.
Error Handling
Errors are returned as error messages:
{
"kind": "error",
"uid": "matching-uid",
"error": "Invalid API key",
"code": "AUTHENTICATION_FAILED"
}
Handle errors appropriately:
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.kind === 'error') {
console.error('Error:', message.error);
// Handle the error
}
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = (event) => {
console.log('WebSocket closed:', event.code, event.reason);
};
Connection Management
Heartbeat/Keep-Alive
The WebSocket connection is kept alive automatically. No manual heartbeat is required.
Reconnection
If the connection is lost, you'll need to:
- Establish a new WebSocket connection
- Re-authenticate
- Re-configure if needed
- Resume interaction
Example reconnection logic:
let ws;
let reconnectAttempts = 0;
const MAX_RECONNECT_ATTEMPTS = 5;
function connect() {
ws = new WebSocket('wss://pug.stg.uglabs.app/interact');
ws.onopen = () => {
console.log('Connected');
reconnectAttempts = 0;
authenticate();
};
ws.onclose = (event) => {
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
reconnectAttempts++;
console.log(`Reconnecting (attempt ${reconnectAttempts})...`);
setTimeout(connect, 1000 * reconnectAttempts);
}
};
}
connect();
Best Practices
- Always include unique UIDs - Use
crypto.randomUUID()for generating unique identifiers - Handle streaming data - Process text/audio chunks as they arrive for real-time experience
- Implement error handling - Always handle error messages and connection failures
- Clean up resources - Close WebSocket connections when done
- Respect rate limits - Don't send messages too rapidly
- Store access tokens securely - Never expose API keys in client-side code
Example: Complete Conversation
Here's a complete example of a conversation flow:
const ws = new WebSocket('wss://pug.stg.uglabs.app/interact');
// Step 1: Get access token (do this once, reuse token)
const authResponse = await fetch('https://pug.stg.uglabs.app/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
api_key: 'your-api-key'
})
});
const { access_token } = await authResponse.json();
// Step 2: Authenticate WebSocket
ws.onopen = () => {
ws.send(JSON.stringify({
kind: 'authenticate',
uid: crypto.randomUUID(),
access_token: access_token
}));
};
// Step 3: Handle authentication success
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.kind === 'authenticated') {
// Step 4: Configure conversation
ws.send(JSON.stringify({
kind: 'set_configuration',
uid: crypto.randomUUID(),
prompt: 'You are a helpful assistant.'
}));
}
if (message.kind === 'configured') {
// Step 5: Start interacting
ws.send(JSON.stringify({
kind: 'interact',
uid: crypto.randomUUID(),
text: 'Hello!',
audio_output: false
}));
}
if (message.kind === 'text') {
// Step 6: Receive response
console.log('Assistant:', message.text);
}
};
Try It Yourself
Use our Interactive API Tester to experiment with the WebSocket protocol in real-time.