SSE Protocol
Server-Sent Events streaming protocol reference
SSE Protocol
DorkOS uses Server-Sent Events (SSE) for two purposes: real-time message streaming and cross-client session synchronization.
Message Streaming
When a client sends a message, the server responds with an SSE stream:
POST /api/sessions/:id/messages
Content-Type: application/json
{ "content": "Hello, Claude", "cwd": "/path/to/project", "clientMessageId": "optional-uuid" }The optional clientMessageId enables server-echo ID reconciliation — the server includes JSONL-assigned message IDs in the done event so the client can remap streaming message IDs to their persistent equivalents.
Response (Content-Type: text/event-stream):
data: {"type":"text_delta","delta":"Hello"}
data: {"type":"text_delta","delta":"! How can"}
data: {"type":"text_delta","delta":" I help?"}
data: {"type":"done"}The endpoint always returns a text/event-stream response. The web client uses direct SSE streaming regardless of whether DORKOS_RELAY_ENABLED is set.
Event Types
Text Events
Prop
Type
Thinking Events
Prop
Type
Tool Events
Prop
Type
Interactive Events
Prop
Type
Control Events
Prop
Type
Responding to Interactive Events
When approval_required is received, approve or deny before the stream continues:
# Approve
POST /api/sessions/:id/approve
Content-Type: application/json
{ "toolCallId": "tc_123" }
# Deny
POST /api/sessions/:id/deny
Content-Type: application/json
{ "toolCallId": "tc_123" }Failing to respond to an approval_required event blocks the agent. The SSE stream stays open
until the tool call is resolved.
Both /approve, /deny, and /submit-answers may return 409 with { code: "INTERACTION_ALREADY_RESOLVED" } if the SDK resolved the interaction before the request arrived. Treat this as success.
When question_prompt is received, submit the user's answer:
POST /api/sessions/:id/submit-answers
Content-Type: application/json
{ "toolCallId": "tc_123", "answers": { "0": "option_a" } }When elicitation_prompt is received, submit the completed form:
POST /api/sessions/:id/submit-elicitation
Content-Type: application/json
{ "elicitationId": "eli_123", "action": "submit", "content": { "api_key": "sk-..." } }To cancel (agent treats as user declining):
POST /api/sessions/:id/submit-elicitation
Content-Type: application/json
{ "elicitationId": "eli_123", "action": "cancel" }Elicitation has a server-side timeout (timeoutMs from the event). If you do not respond within
that window, the agent receives an empty result and continues or fails gracefully.
Session Sync Protocol
Subscribe to real-time session changes via a persistent SSE connection:
GET /api/sessions/:id/streamSync Events
Prop
Type
When a sync_update is received, re-fetch message history. GET /api/sessions/:id/messages
supports ETag caching (If-None-Match / 304) for efficient polling.
The sync stream carries only sync_connected and sync_update events.
System Events Stream
For relay activity, tunnel status, and extension events, subscribe to the unified system events stream:
GET /api/eventsThis single persistent connection multiplexes all background events. Events are tagged with a type field using namespace prefixes:
| Prefix | Events |
|---|---|
relay.* | Relay adapter activity, message delivery |
tunnel.* | Tunnel start/stop, URL changes |
ext:{id}:* | Extension-emitted events (namespaced per extension ID) |
The DorkOS client uses a single GET /api/events connection for all background state, replacing
the older per-resource SSE endpoints (/api/relay/stream, /api/tunnel/stream, etc.). Those
endpoints no longer exist. External clients should use GET /api/events.
Session Write Coordination
POST /api/sessions/:id/messages prevents concurrent writes via client IDs.
Prop
Type
- If another client holds the write lock, the server returns
409with{ error: "Session locked", code: "SESSION_LOCKED", lockedBy, lockedAt } - Locks auto-expire after 5 minutes
- Locks are released when SSE connections close
Connection Lifecycle
Open the SSE connection
Send POST /api/sessions/:id/messages with message content. The response is Content-Type: text/event-stream.
Process streamed events
The server streams events as they arrive from the Claude Agent SDK. Update your UI in real time.
Handle interactive events
If approval_required or question_prompt arrive, respond via the appropriate endpoint before the stream continues.
Stream completes
On done, the message stream ends. Send another message or close the connection.
Subscribe for ongoing sync
For real-time updates from other clients or CLI writes, open a persistent session sync connection:
GET /api/sessions/:id/streamFor relay, tunnel, and extension events, use the unified system stream:
GET /api/events