Skip to main content
Qoder Cloud Agents pushes Agent execution status to clients over Server-Sent Events (SSE). Open one connection and you receive every event as it happens — no polling required.

Connection URL

GET https://api.qoder.com/api/v1/cloud/sessions/{session_id}/events/stream
Request headers:
Authorization: Bearer $QODER_PAT
Accept: text/event-stream

SSE format

Each event consists of three lines separated by a blank line:
id: evt_01abc123
event: agent.message
data: {"content":[{"type":"text","text":"Hello, I'm ready to help."}]}

FieldDescription
idUnique event ID, used for resume-on-reconnect
eventEvent type, which determines the structure of data
dataJSON-encoded event payload

Event catalog

Event typeMeaningWhen it firesdata shape
user.messageA user message was sentAfter the client POSTs the event{"content": [{"type":"text","text":"..."}]}
user.interruptThe user interrupted executionWhen the client sends an interrupt{}
user.define_outcomeThe user defined an expected outcomeWhen the client sets a goal{"outcome": "..."}
session.status_runningThe Session started runningAfter a message is received and processing begins{"status": "running"}
span.model_request_startModel request startedWhen the Agent calls the LLM{"model": "...", "span_id": "..."}
agent.thinkingThe Agent is thinkingDuring model inference{"thinking": "..."}
agent.messageThe Agent repliedWhen the model emits text{"content": [{"type":"text","text":"..."}]}
agent.tool_useThe Agent initiated a built-in tool callWhen the model decides to call a built-in toolSee “Tool calls” below
agent.tool_resultBuilt-in tool execution resultAfter tool execution completesSee “Tool calls” below
agent.custom_tool_useThe Agent requested a client-side custom toolWhen the model decides to call a custom toolSee “Tool calls” below
session.status_idleThe Session went idleWhen a turn completesSee “status_idle full schema” below
span.model_request_endModel request endedWhen the LLM call finishes{"span_id": "...", "usage": {...}}
session.errorA Session error occurredOn a runtime exception{"error": "...", "code": "..."}
terminatedThe Session terminatedWhen the Session closes or times out{"reason": "..."}
Each event’s data JSON also includes these common fields beyond those listed above: id, type, turn_id (omitted for user.define_outcome), session_id, created_at, processed_at, schema_version.

Typical event lifecycle

A complete conversation turn fires events in this order:
user.message                  ← user input
session.status_running    ← execution begins
span.model_request_start      ← model request starts
agent.thinking                ← reasoning (optional)
agent.message                 ← reply content (may emit multiple delta chunks)
session.status_idle           ← Complete, or paused for requires_action
span.model_request_end        ← Model request tail

Tool calls

Built-in tool calls use agent.tool_use. If the call is allowed and executed by the platform, it later has a matching agent.tool_result in the same turn_id. agent.tool_use key fields:
{
  "type": "agent.tool_use",
  "id": "evt_1d4c9b3c873b...",
  "name": "Bash",
  "input": { "command": "...", "description": "..." },
  "evaluated_permission": "allow",
  "turn_id": "turn_...",
  "session_id": "sess_..."
}
The id of this event uses the evt_ prefix like other events. When evaluated_permission is ask, use this event id as user.tool_confirmation.tool_use_id. agent.tool_result key fields:
{
  "type": "agent.tool_result",
  "id": "evt_<32hex>",
  "name": "Bash",
  "content": [{ "type": "text", "text": "..." }],
  "tool_use_id": "toolu_bdrk_01Kj...",
  "turn_id": "turn_...",
  "is_error": false
}
Client-side custom tools use agent.custom_tool_use and are resolved by your application:
{
  "type": "agent.custom_tool_use",
  "id": "evt_1f2e3d4c5b6a...",
  "name": "lookup_order",
  "input": { "order_id": "ord_123" },
  "turn_id": "turn_...",
  "session_id": "sess_..."
}
Use the custom tool event id as user.custom_tool_result.custom_tool_use_id. Sorting advice: Sort by created_at, then by type (tool_use before tool_result), then by id. Do not rely solely on id ordering — the platform-generated tool_result id may sort lexically before the tool_use id.

status_idle full schema

The session.status_idle event carries stop_reason, usage, turn_id, and session_id in addition to status:
{
  "type": "session.status_idle",
  "id": "evt_840b8dc4b1534e52a2b858bf26b7de00",
  "status": "idle",
  "stop_reason": { "type": "end_turn" },
  "usage": {
    "input_tokens": 23487,
    "output_tokens": 1024,
    "cache_read_input_tokens": 11223,
    "cache_creation_input_tokens": 0
  },
  "turn_id": "turn_...",
  "session_id": "sess_..."
}
When a turn is paused waiting for a tool confirmation or custom tool result, stop_reason.type is requires_action and event_ids points to the events that need a client response:
{
  "type": "session.status_idle",
  "id": "evt_0f1e2d3c4b5a...",
  "status": "idle",
  "stop_reason": {
    "type": "requires_action",
    "event_ids": ["evt_1f2e3d4c5b6a..."]
  },
  "turn_id": "turn_...",
  "session_id": "sess_..."
}
Other stop_reason.type values include end_turn, cancel, max_turns, and error, depending on the scenario.

Reconnect with after_id

A new SSE connection replays all historical events by default. To receive only new events, pass after_id:
GET /sessions/{session_id}/events/stream?after_id=evt_01abc123
The browser’s native EventSource automatically sends Last-Event-ID on reconnect, and the server uses it to deliver incremental events.
For long-running connections, track the last id you received and use after_id to resume after disconnects.

delta_flush_interval_ms

A query parameter that controls how often agent.message deltas are flushed:
GET /sessions/{session_id}/events/stream?delta_flush_interval_ms=100
The default is 50ms. Higher values reduce event frequency and rendering overhead on the client.

curl examples

curl -N \
  -H "Authorization: Bearer $QODER_PAT" \
  -H "Accept: text/event-stream" \
  "https://api.qoder.com/api/v1/cloud/sessions/sess_abc123/events/stream"

JavaScript EventSource example

const url = new URL('https://api.qoder.com/api/v1/cloud/sessions/sess_abc123/events/stream');
// To resume: url.searchParams.set('after_id', lastEventId);

const es = new EventSource(url, {
  headers: { 'Authorization': `Bearer ${QODER_PAT}` }
});

es.addEventListener('agent.message', (e) => {
  const data = JSON.parse(e.data);
  console.log('Agent:', data.content);
});

es.addEventListener('session.status_idle', (e) => {
  const data = JSON.parse(e.data);
  if (data.stop_reason?.type === 'requires_action') {
    console.log('Turn paused; respond to:', data.stop_reason.event_ids);
  } else {
    console.log('Turn complete; awaiting next user input');
  }
});

es.addEventListener('session.error', (e) => {
  const data = JSON.parse(e.data);
  console.error('Error:', data.error);
  es.close();
});

es.onerror = () => {
  console.warn('Connection lost; the browser will reconnect automatically');
};

Client implementation tips

  1. Track the last event ID — update it on every event so you can resume after disconnects.
  2. Concatenate deltasagent.message may arrive in streaming deltas; assemble them into the full text on the client.
  3. Watch for terminated — when received, close the connection and stop reconnecting.
  4. Timeout and reconnect — if 30 seconds pass with no event, close the connection and reconnect to avoid silent stalls.
  5. Filter events — subscribe only to the event types you care about; ignoring span-level events reduces overhead.

FAQ

Q: I’m getting a flood of historical events on connect. How do I avoid this? A: Pass after_id set to the last event ID you processed. The server will only return events after that ID. Q: Will the SSE connection close on its own? A: When the Session terminates, the server emits a terminated event and closes the connection. The idle timeout depends on Session configuration. Q: Can I use WebSockets instead? A: Only SSE is supported today. SSE is lighter for one-way push and natively supports reconnection. Q: If I set delta_flush_interval_ms too high, will events be dropped? A: No. Deltas are batched, not dropped. Final content remains complete.

Next steps

Start a session

Run an agent against an environment.

Permission policies

Control which tool calls run, ask, or deny.

Tools

Equip your agent with built-in, MCP, and custom tools.

Define outcomes

Tell an agent what success looks like.