curl --request GET \
--url https://api.hq.zone/v1/api/conversations/{id}/stream \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.hq.zone/v1/api/conversations/{id}/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.hq.zone/v1/api/conversations/{id}/stream', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hq.zone/v1/api/conversations/{id}/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.hq.zone/v1/api/conversations/{id}/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.hq.zone/v1/api/conversations/{id}/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hq.zone/v1/api/conversations/{id}/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyStream conversation events
Opens a Server-Sent Events stream of a conversation’s live turn activity, emitting named events (agent token, tool, and final event kinds, plus stale when the client has lagged and done when the turn closes) each carrying a JSON data payload and an incrementing id sequence. Resume after a disconnect with the standard Last-Event-ID header or the since_seq query parameter; the optional scope parameter narrows the detail level (and is clamped to the caller’s token scope), and view=admin lets an admin attach to a conversation they do not participate in. The caller must have access to the conversation, otherwise 404 is returned.
curl --request GET \
--url https://api.hq.zone/v1/api/conversations/{id}/stream \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.hq.zone/v1/api/conversations/{id}/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.hq.zone/v1/api/conversations/{id}/stream', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.hq.zone/v1/api/conversations/{id}/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.hq.zone/v1/api/conversations/{id}/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.hq.zone/v1/api/conversations/{id}/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.hq.zone/v1/api/conversations/{id}/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_bodyServer-Sent Events
This endpoint is a long-lived Server-Sent Events (SSE) stream, not a one-shot JSON response. Open it with an SSE client and keep it open — it emits one event per agent or platform update until you disconnect.GET /v1/api/conversations/{id}/stream
Authorization: Bearer hq_pat_...
Accept: text/event-stream
: ping / : idle) is sent periodically as a keepalive so proxies don’t drop the connection — ignore it.
Event shape
Each event has anevent: name, a monotonic id: cursor, and a single-line JSON data: payload:
event: text_delta
id: 7Z9aQ2
data: {"text":"Looking up the financials"}
Common events
event: | data | Meaning |
|---|---|---|
text_delta | { text } | A chunk of the assistant’s reply — append it. |
status | { message } | Progress caption. |
agent_active | { hint } | Drive a typing indicator. hint is diagnostic — don’t render it as user-facing text. |
tool_call | { id, name, scope, args_summary, args_digest } | The agent invoked a tool. |
tool_result | { id, is_error, summary } | The matching tool call finished. |
token_usage | { model, input_tokens, output_tokens, cost_usd, … } | Usage and cost for the turn. |
final | { text, duration_ms } | Terminal for the turn — the reply is complete. |
error | { message, recoverable } | Terminal for the turn — it failed. |
final/error end a turn, not the stream — the next message produces the next batch. New event: names may be added over time; ignore ones you don’t recognize (standard SSE behavior).
Reconnecting
id: is an opaque, incrementing sequence. On reconnect, send the last one you saw as the Last-Event-ID header and the server replays the events you missed from the conversation journal. (EventSource sets Last-Event-ID automatically on auto-reconnect; on a fresh attach where you can’t set the header, pass ?since_seq=<id> instead.) An optional ?scope= query parameter narrows which events you receive.
POST to Post a message (returns immediately) and read the result off this stream.Authorizations
Personal Access Token. Send as Authorization: Bearer hq_pat_....
Path Parameters
Conversation id
Query Parameters
Self-downgrade only. user strips diagnostic + accounting
fields; admin / integrator and absent default to the
trusted full vocabulary. Unknown values do NOT elevate -- see
[crate::stream_scope::Scope::from_query]. Auth-derived
scope replaces this in #729.
Explicit "I've already seen up to seq N" cursor for fresh
attaches. The standard EventSource API doesn't let JS set
Last-Event-ID on the initial request -- only on automatic
reconnects -- so Studio passes its journal high-water mark
here to avoid a race where events emitted between the journal
fetch and the SSE subscribe vanish into the gap.
?view=admin opts a tenant admin into attaching to a
conv stream they don't participate in. Non-admins are
silently downgraded. Audited per-attach so admin tail
reads of someone else's conv are observable.
Response
Server-Sent Events for the conversation's turn. Each frame is a named event (the agent token/tool/final event kinds, plus stale when the client lagged and done when the turn closes) carrying a JSON data payload and an id: sequence. Resume after a drop with the standard Last-Event-ID header, or pass ?since_seq= on a fresh attach. Native EventSource (cookie auth) and fetch-stream (Bearer PAT) both work.