None of them take query parameters. All of them take the same
Authorization: Bearer header as everything else, and all of them 404 before
the stream opens if the resource does not exist in your workspace.
The wire format
text/event-stream, one frame per blank-line-separated block:
- The JSON repeats the frame’s name in its own
eventfield. Narrow on that one value — theevent:line and the body can never disagree, because the line is derived from the body. - A line starting
:is a keep-alive, sent every 15 seconds so an idle proxy does not close the connection. It fires no handler. - The body is never a single JSON document. Read it with an SSE client. A
plain
JSON.parseof the response body, orresponse.json(), will hang and then fail.
Cache-Control: no-cache and X-Accel-Buffering: no,
which is what stops an intermediate proxy holding every frame until the stream
ends.
The three rules that matter
A snapshot arrives first, and again on every reconnect. The first frame of every one of these streams is the whole current state — the conversation and all of its items, the build’s status and progress, the CoPilot’s entire timeline. It is taken after the subscription is established, so nothing can slip into the gap between reading it and starting to listen. Reconnecting is therefore how you resync: drop the connection, open it again, replace your local state with the snapshot. Delivery is best effort. Nothing on the stream is canonical. Events are relayed live and never queued or replayed for a client that was not attached, so a frame published while you were disconnected is simply gone. Every fact one carries is durable somewhere else — read it back from the endpoints named under polling equivalents below.assistant.delta is append, not cumulative. Each frame carries the next
chunk of text and not the text so far. Concatenate them. If you suspect you
missed one, assistant.completed carries text — every delta joined — so a
client can replace what it accumulated rather than try to reconcile it.
The conversation stream puts an SSE
id: line on item.created frames, carrying
the item’s id. Nothing on the server reads Last-Event-ID: a reconnect always
starts from a fresh snapshot rather than resuming from an id. The id is there so
a client can deduplicate an item it already has.Conversation frames
GET /v1/conversations/{conversation_id}/events. This is the stream behind a
text agent — see text conversations for the
lifecycle it describes.
conversation.snapshot
Where the conversation stands. First frame, and again on every reconnect.
item.created
One new item, in exactly the shape the items list returns it, with event added.
item.delivery_updated
An outbound item reached the channel, or failed to.
turn
One turn’s lifecycle. The same payload on all six streams.
turn.failed
The API could not queue the turn at all. It is a separate frame from turn
because no worker ever owned it, so there was no turn to have a lifecycle.
assistant.started
The reply has begun; assistant.delta frames follow. Carries
trigger_item_id.
assistant.delta
assistant.completed
Knowledge-base build frames
GET /v1/knowledge/{kb_id}/events. Advisory throughout: the canonical state of
a build is on the knowledge base itself. See
building a knowledge base.
snapshot
progress
One step of the running build.
status
The build reached a new state.
CoPilot frames
All four rails — agent, tool, knowledge and task — stream the same three frames. They are what the dashboard’s CoPilot panel renders; see AgentCoPilot.snapshot
message
One row on the rail.
turn
Identical to the conversation stream’s turn frame, above.
Reading a stream
AsyncTalqing reads the same way with async for, and the stream is not a
coroutine on either client — you do not await opening it.
The TypeScript stream reconnects on its own after a transport failure, with
exponential backoff from three seconds. Because every reconnect begins with a
fresh snapshot frame, a client that replaces its state on
snapshot recovers
with no extra code. Pass sseMaxRetryAttempts in the options object to bound
that, or signal to stop it. The Python stream does not reconnect: it ends, and
opening it again is your call. A Stream that has finished cannot be reopened —
RuntimeError: this stream is finished; open a new one to watch again.404, a 401, a 403 — the Python client
reads the error body before anything else and raises TalqingAPIError with the
usual status_code and errors. In TypeScript the failure surfaces on the
first iteration of the generator.
Not exposed through MCP
None of these six endpoints are tools on the MCP server. They are not tool-shaped: a tool call returns once, and a stream does not return at all. The CoPilot chat endpoints beside them are excluded for a different reason — an AI builder must not drive another builder agent.Polling equivalents
Nothing is unreachable as a result. Every stream has a read that answers the same question, and those reads are exposed.
The dashboard polls the knowledge list every three seconds while any build is
running, which is a reasonable interval to copy. There is no rate limit, but a
build takes minutes — polling faster than that buys nothing.
What does not stream
There is no event stream for a voice or video call. A live call is a LiveKit room, and the browser layer in@talqing/sdk/browser is how you observe one
while it is happening — see web calls. What a finished
call did is on GET /v1/calls/{session_id}, and the push equivalent is a
webhook, not an SSE stream.