WebSocket API
ClaudeControl uses WebSocket for real-time event streaming between the server and connected clients.
Connecting
ws://host:portIf PIN authentication is enabled, include the token as a query parameter:
ws://host:port?token=<pin-hash>The token is the SHA-256 hash of the PIN, returned by POST /api/auth/login.
Connections without a valid token when a PIN is set will be closed with code 4001 Unauthorized.
Heartbeat
The server sends a WebSocket ping frame every 30 seconds. Clients must respond with a pong (handled automatically by most WebSocket libraries). If no pong is received within 10 seconds, the connection is terminated.
Events from server
All events are JSON objects with a type field. Events scoped to a workspace include a workspaceId field.
Agent lifecycle
| Event | Description | Key fields |
|---|---|---|
agent_created | New agent spawned | agent, workspaceId, spawnedBy |
agent_updated | Agent config changed | agentId, agent, workspaceId |
agent_removed | Agent closed/deleted | agentId, workspaceId |
agent_done | Agent finished processing | agentId, workspaceId, role, cost |
agent_error | Agent encountered an error | agentId, workspaceId, error |
agent_session | SDK session ID assigned | agentId, sessionId |
Agent output
| Event | Description | Key fields |
|---|---|---|
agent_text | Agent produced text output | agentId, workspaceId, text |
agent_tool | Agent invoked a tool | agentId, workspaceId, tool, input |
agent_tool_result | Tool execution result | agentId, workspaceId, tool, result |
agent_working | Agent status changed to working | agentId, workspaceId |
agent_user_message | User message was logged | agentId, workspaceId |
agent_log_updated | Full log was updated (e.g. entry hidden) | agentId, workspaceId, log |
agent_todos | Agent todo list updated | agentId, workspaceId, todos |
Orchestration
| Event | Description | Key fields |
|---|---|---|
delegation_update | Delegation status changed | agentId, workspaceId, delegationId, status, toRole |
agent_spawn_request | Agent requested multi-agent spawn | workspaceId, request |
spawn_request_update | Spawn request resolved | workspaceId, request |
Permissions
| Event | Description | Key fields |
|---|---|---|
permission_request | Agent needs user approval for a tool call | requestId, agentId, workspaceId, tool, input |
Workspace
| Event | Description | Key fields |
|---|---|---|
workspace_reset | Workspace was reset | workspaceId |
Messages from client
Clients can send JSON messages to the server:
permission_response
Respond to a permission request:
{
"type": "permission_response",
"requestId": "req_abc123",
"approved": true,
"updatedInput": { "key": "modified_value" }
}| Field | Type | Required | Description |
|---|---|---|---|
requestId | string | yes | ID from the permission_request event |
approved | boolean | yes | Whether to allow the tool call |
updatedInput | object | no | Modified tool input (optional override) |
subscribe
Filter events to a specific workspace. Without subscribing, the client receives all events.
{
"type": "subscribe",
"workspaceId": "ws_abc123"
}unsubscribe
Stop filtering for a workspace:
{
"type": "unsubscribe",
"workspaceId": "ws_abc123"
}Example: connecting with JavaScript
const ws = new WebSocket('ws://localhost:22609?token=YOUR_TOKEN');
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
switch (data.type) {
case 'agent_text':
console.log(`[${data.agentId}] ${data.text}`);
break;
case 'agent_done':
console.log(`Agent ${data.agentId} finished (cost: $${data.cost})`);
break;
case 'permission_request':
// Auto-approve example
ws.send(JSON.stringify({
type: 'permission_response',
requestId: data.requestId,
approved: true,
}));
break;
}
};
// Subscribe to a specific workspace
ws.onopen = () => {
ws.send(JSON.stringify({
type: 'subscribe',
workspaceId: 'your-workspace-id',
}));
};