WebSockets
Friday Dev provides WebSocket connections for real-time updates.
Connection
Endpoint
ws://localhost:3000/ws
wss://your-domain.com/ws
Authentication
Include API key as query parameter:
ws://localhost:3000/ws?token=YOUR_API_KEY
Or send after connection:
{
"type": "auth",
"token": "YOUR_API_KEY"
}
Connection Example
JavaScript
const ws = new WebSocket('ws://localhost:3000/ws?token=YOUR_API_KEY');
ws.onopen = () => {
console.log('Connected to Friday Dev');
// Subscribe to task updates
ws.send(JSON.stringify({
type: 'subscribe',
channel: 'tasks'
}));
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
console.log('Received:', message);
};
ws.onclose = () => {
console.log('Disconnected');
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
Python
import websockets
import asyncio
import json
async def connect():
uri = "ws://localhost:3000/ws?token=YOUR_API_KEY"
async with websockets.connect(uri) as ws:
# Subscribe to tasks
await ws.send(json.dumps({
"type": "subscribe",
"channel": "tasks"
}))
# Listen for messages
async for message in ws:
data = json.loads(message)
print(f"Received: {data}")
asyncio.run(connect())
Message Format
All messages use JSON:
{
"type": "event_type",
"channel": "channel_name",
"data": { ... },
"timestamp": "2024-01-15T16:00:00Z"
}
Channels
tasks
Task-related events:
// Subscribe
{ "type": "subscribe", "channel": "tasks" }
// Events received:
{
"type": "task.created",
"channel": "tasks",
"data": {
"id": 123,
"title": "New task",
"status": "backlog"
}
}
{
"type": "task.updated",
"channel": "tasks",
"data": {
"id": 123,
"changes": {
"status": "in_progress"
}
}
}
{
"type": "task.deleted",
"channel": "tasks",
"data": {
"id": 123
}
}
runs
Agent run events:
// Subscribe
{ "type": "subscribe", "channel": "runs" }
// Events received:
{
"type": "run.started",
"channel": "runs",
"data": {
"run_id": "run_xyz",
"task_id": 123,
"agent": "friday"
}
}
{
"type": "run.progress",
"channel": "runs",
"data": {
"run_id": "run_xyz",
"step": 3,
"total_steps": 5,
"message": "Writing code..."
}
}
{
"type": "run.completed",
"channel": "runs",
"data": {
"run_id": "run_xyz",
"status": "success",
"output": { ... }
}
}
{
"type": "run.failed",
"channel": "runs",
"data": {
"run_id": "run_xyz",
"error": "Rate limit exceeded"
}
}
logs
Real-time agent logs:
// Subscribe to specific run
{
"type": "subscribe",
"channel": "logs",
"run_id": "run_xyz"
}
// Events received:
{
"type": "log",
"channel": "logs",
"data": {
"run_id": "run_xyz",
"level": "info",
"message": "Reading task description...",
"timestamp": "2024-01-15T16:00:01Z"
}
}
projects
Project events:
// Subscribe
{ "type": "subscribe", "channel": "projects" }
// Events:
{
"type": "project.created",
"channel": "projects",
"data": { ... }
}
Subscribing
Subscribe to Channel
{
"type": "subscribe",
"channel": "tasks"
}
Subscribe with Filter
{
"type": "subscribe",
"channel": "tasks",
"filter": {
"project_id": "proj_abc123"
}
}
Subscribe to Specific Resource
{
"type": "subscribe",
"channel": "logs",
"run_id": "run_xyz"
}
Unsubscribe
{
"type": "unsubscribe",
"channel": "tasks"
}
Client Messages
Ping/Pong
Keep connection alive:
// Send
{ "type": "ping" }
// Receive
{ "type": "pong" }
Request Data
Request current state:
// Send
{
"type": "request",
"resource": "tasks",
"params": {
"status": "in_progress"
}
}
// Receive
{
"type": "response",
"resource": "tasks",
"data": [ ... ]
}
Error Handling
Error Messages
{
"type": "error",
"code": "UNAUTHORIZED",
"message": "Invalid or expired token"
}
Error Codes
| Code | Description |
|---|---|
UNAUTHORIZED | Auth failed |
INVALID_MESSAGE | Malformed message |
UNKNOWN_CHANNEL | Channel doesn't exist |
SUBSCRIPTION_FAILED | Can't subscribe |
RATE_LIMITED | Too many messages |
Reconnection
Handle disconnections gracefully:
class FridayDevWebSocket {
constructor(url, token) {
this.url = `${url}?token=${token}`;
this.subscriptions = new Set();
this.connect();
}
connect() {
this.ws = new WebSocket(this.url);
this.ws.onopen = () => {
console.log('Connected');
// Resubscribe after reconnect
this.subscriptions.forEach(channel => {
this.subscribe(channel);
});
};
this.ws.onclose = () => {
console.log('Disconnected, reconnecting...');
setTimeout(() => this.connect(), 1000);
};
this.ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
this.handleMessage(msg);
};
}
subscribe(channel) {
this.subscriptions.add(channel);
this.ws.send(JSON.stringify({
type: 'subscribe',
channel
}));
}
handleMessage(msg) {
// Handle different message types
switch (msg.type) {
case 'task.created':
console.log('New task:', msg.data);
break;
case 'run.progress':
console.log('Progress:', msg.data);
break;
// ...
}
}
}
Rate Limits
- Messages per second: 10
- Subscriptions per connection: 50
- Connections per client: 5
Best Practices
1. Handle All Events
ws.onmessage = (event) => {
const msg = JSON.parse(event.data);
switch (msg.type) {
case 'task.created':
case 'task.updated':
case 'task.deleted':
updateTaskList(msg.data);
break;
case 'run.progress':
updateProgress(msg.data);
break;
case 'error':
handleError(msg);
break;
default:
console.log('Unknown message type:', msg.type);
}
};
2. Implement Heartbeat
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ type: 'ping' }));
}
}, 30000);
3. Clean Up
window.addEventListener('beforeunload', () => {
ws.close();
});
Next Steps
- REST API - HTTP endpoints
- Authentication - Auth details
- CLI Reference - Command line usage