Six endpoints stay open and push events: one text conversation, one knowledge-base build, and the four CoPilot rails. They are the same machine underneath — subscribe, receive a snapshot of where things stand, then receive what happens next — so learning one teaches you all six. 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:
Three things follow from that:
  • The JSON repeats the frame’s name in its own event field. Narrow on that one value — the event: 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.parse of the response body, or response.json(), will hang and then fail.
The response also carries 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.
If the stream fails to open — a 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.