Skip to main content

Overview

The Events API provides a Server-Sent Events (SSE) stream for real-time updates from Mission Control. This persistent connection delivers database mutations, system events, and state changes as they happen.
SSE is a one-way communication channel from server to client. Clients connect via EventSource and receive JSON-encoded events. Connection includes automatic heartbeat to prevent proxy timeouts.

Establish SSE Connection

GET /api/events

Open a persistent SSE connection to receive real-time events.

Response Headers

  • Content-Type: text/event-stream
  • Cache-Control: no-cache, no-transform
  • Connection: keep-alive
  • X-Accel-Buffering: no

Event Format

All events are JSON-encoded and follow this structure:
type
string
Event type identifier
data
object
Event payload (varies by type)
timestamp
integer
Unix timestamp (milliseconds)
const eventSource = new EventSource('/api/events', {
  withCredentials: true
});

eventSource.onmessage = (event) => {
  const data = JSON.parse(event.data);
  console.log('Event received:', data.type, data.data);
};

eventSource.onerror = (error) => {
  console.error('SSE connection error:', error);
  // EventSource will automatically reconnect
};

// Close connection when done
// eventSource.close();

Event Types

Connection Events

connected

Sent immediately upon establishing SSE connection.
{
  "type": "connected",
  "data": null,
  "timestamp": 1709823456789
}

heartbeat

Sent every 30 seconds to keep connection alive through proxies.
: heartbeat

Entity Events

agent:created

New agent provisioned.
{
  "type": "agent:created",
  "data": {
    "id": 8,
    "name": "DataAnalyst",
    "role": "analyst",
    "status": "offline",
    "created_at": 1709823456
  },
  "timestamp": 1709823456789
}

agent:updated

Agent configuration or status changed.
{
  "type": "agent:updated",
  "data": {
    "id": 8,
    "name": "DataAnalyst",
    "status": "online",
    "changes": ["status"]
  },
  "timestamp": 1709823567890
}

agent:deleted

Agent removed from system.
{
  "type": "agent:deleted",
  "data": {
    "id": 8,
    "name": "DataAnalyst"
  },
  "timestamp": 1709823678901
}

task:created

New task created.
{
  "type": "task:created",
  "data": {
    "id": 42,
    "title": "Analyze Q4 metrics",
    "status": "inbox",
    "priority": "high",
    "created_by": "admin"
  },
  "timestamp": 1709823789012
}

task:updated

Task modified (status, assignment, etc.).
{
  "type": "task:updated",
  "data": {
    "id": 42,
    "title": "Analyze Q4 metrics",
    "status": "in_progress",
    "assigned_to": "DataAnalyst",
    "changes": ["status", "assigned_to"]
  },
  "timestamp": 1709823890123
}

task:deleted

Task removed.
{
  "type": "task:deleted",
  "data": {
    "id": 42,
    "title": "Analyze Q4 metrics"
  },
  "timestamp": 1709823901234
}

notification:created

New notification for an agent.
{
  "type": "notification:created",
  "data": {
    "id": 234,
    "recipient": "DataAnalyst",
    "type": "task_assigned",
    "title": "New Task Assigned",
    "priority": "high"
  },
  "timestamp": 1709824012345
}

activity:created

New activity logged.
{
  "type": "activity:created",
  "data": {
    "id": 1523,
    "type": "task.status_changed",
    "actor": "DataAnalyst",
    "entity_type": "task",
    "entity_id": 42
  },
  "timestamp": 1709824123456
}

System Events

alert:triggered

Alert rule condition met.
{
  "type": "alert:triggered",
  "data": {
    "rule_id": 5,
    "rule_name": "Agent Offline Alert",
    "entity_type": "agent",
    "entity_id": 8,
    "message": "Agent DataAnalyst has been offline for 10 minutes"
  },
  "timestamp": 1709824234567
}

webhook:delivered

Webhook successfully delivered.
{
  "type": "webhook:delivered",
  "data": {
    "webhook_id": 3,
    "event": "task.created",
    "status_code": 200,
    "response_time_ms": 145
  },
  "timestamp": 1709824345678
}

system:status_changed

System-wide status change.
{
  "type": "system:status_changed",
  "data": {
    "component": "gateway",
    "status": "degraded",
    "message": "High latency detected"
  },
  "timestamp": 1709824456789
}

Connection Management

Automatic Reconnection

EventSource automatically reconnects on connection loss:
const eventSource = new EventSource('/api/events');

let reconnectAttempts = 0;

eventSource.onerror = (error) => {
  reconnectAttempts++;
  console.log(`Connection lost. Reconnect attempt ${reconnectAttempts}`);
  
  if (reconnectAttempts > 5) {
    console.error('Too many reconnect attempts, giving up');
    eventSource.close();
  }
};

eventSource.onopen = () => {
  reconnectAttempts = 0;
  console.log('SSE connection established');
};

Graceful Shutdown

// Close connection cleanly
eventSource.close();

// Or on page unload
window.addEventListener('beforeunload', () => {
  eventSource.close();
});

Event Filtering

Client-Side Filtering

const eventSource = new EventSource('/api/events');

eventSource.onmessage = (event) => {
  const { type, data } = JSON.parse(event.data);
  
  // Only handle task events
  if (type.startsWith('task:')) {
    handleTaskEvent(type, data);
  }
  
  // Ignore heartbeats
  if (type === 'heartbeat') return;
  
  // Handle high-priority notifications
  if (type === 'notification:created' && data.priority === 'high') {
    showNotification(data);
  }
};

Use Cases

Live Dashboard Updates

const eventSource = new EventSource('/api/events');

eventSource.onmessage = (event) => {
  const { type, data } = JSON.parse(event.data);
  
  switch (type) {
    case 'agent:updated':
      updateAgentStatus(data.id, data.status);
      break;
    
    case 'task:updated':
      refreshTaskBoard();
      break;
    
    case 'alert:triggered':
      showAlertBanner(data.message);
      break;
  }
};

Activity Feed

const activityFeed = [];

eventSource.onmessage = (event) => {
  const { type, data, timestamp } = JSON.parse(event.data);
  
  if (type === 'activity:created') {
    activityFeed.unshift({
      ...data,
      timestamp
    });
    
    // Keep only last 100
    if (activityFeed.length > 100) {
      activityFeed.pop();
    }
    
    renderActivityFeed();
  }
};

Real-Time Notifications

eventSource.onmessage = (event) => {
  const { type, data } = JSON.parse(event.data);
  
  if (type === 'notification:created' && data.recipient === currentAgent) {
    // Show browser notification
    if (Notification.permission === 'granted') {
      new Notification(data.title, {
        body: data.message,
        icon: '/icon.png'
      });
    }
    
    // Update UI
    incrementUnreadCount();
  }
};

Performance Considerations

  • Connection Pooling: SSE connections are long-lived. Limit to 1 per client.
  • Heartbeat: 30s heartbeat prevents proxy timeouts.
  • Buffering: Response buffering is disabled (X-Accel-Buffering: no).
  • Browser Limits: Most browsers limit SSE connections to 6 per domain.

Error Handling

const eventSource = new EventSource('/api/events');

eventSource.onerror = (error) => {
  if (eventSource.readyState === EventSource.CLOSED) {
    console.error('Connection closed by server');
  } else if (eventSource.readyState === EventSource.CONNECTING) {
    console.log('Reconnecting...');
  }
};

eventSource.onmessage = (event) => {
  try {
    const data = JSON.parse(event.data);
    handleEvent(data);
  } catch (error) {
    console.error('Failed to parse event:', error);
  }
};

Authentication

SSE connections require valid authentication:
// Session cookie is automatically sent
const eventSource = new EventSource('/api/events', {
  withCredentials: true
});

// For cross-origin requests
const eventSource = new EventSource('https://other-domain.com/api/events', {
  withCredentials: true
});

Error Responses

Status CodeDescription
401Unauthorized - Invalid or missing session
403Forbidden - Insufficient permissions (viewer role required)
If authentication fails, the connection will close immediately with an error event.