API reference
Streaming events
Every server-sent event the chat stream can send, in order, with each field, plus how to parse the stream safely.
Reference3 min readUpdated 23 October 2026
The chat stream uses plain server-sent events: each event is one line starting with data: followed by a JSON object, then a blank line. There are no event: or id: lines, so read the type field to tell events apart.
Order
meta, always first.sources, at most once, before anydelta, only when knowledge was used.tool_callandtool_resultpairs, each time the agent calls a tool.deltaevents with the answer text.doneon success, orerroron failure. Exactly one of them, always last.
Events
| type | Fields | Notes |
|---|---|---|
meta | conversation_id, trace_id | The trace id is a 32-character hex OpenTelemetry id |
sources | sources: list of index, id, title, url, filename, source_type, score, snippet | index starts at 1. snippet is up to 240 characters. url is set for web pages, filename for documents |
tool_call | id, name, input | The agent is calling an MCP tool with these arguments |
tool_result | id, name, output | What the tool returned (the model sees up to 2,500 characters of it) |
delta | content | The next piece of the answer. A cached answer arrives as a single delta |
done | conversation_id, trace_id, usage; optional cached, timing | usage has input_tokens, output_tokens, total_tokens, cost_usd. cached: true on an answer-cache hit; timing when you sent X-Perf-Timing: 1 |
error | code, message, request_id | The reply failed after the stream started. See Errors |
data: {"type": "meta", "conversation_id": "9b1c…", "trace_id": "4f2a9c1e…"}data: {"type": "sources", "sources": [{"index": 1, "id": "…", "title": "Shipping policy", "url": "https://example.com/shipping", "filename": null, "source_type": "web", "score": 0.82, "snippet": "Orders leave our warehouse…"}]}data: {"type": "tool_call", "id": "call_1", "name": "get_order", "input": {"order_id": "10482"}}data: {"type": "tool_result", "id": "call_1", "name": "get_order", "output": "{\"status\": \"shipped\"}"}data: {"type": "delta", "content": "Order 10482 left our warehouse"}data: {"type": "delta", "content": " on Tuesday."}data: {"type": "done", "conversation_id": "9b1c…", "trace_id": "4f2a9c1e…", "usage": {"input_tokens": 1830, "output_tokens": 64, "total_tokens": 1894, "cost_usd": 0}}Parsing it
- Network chunks don't line up with events. Buffer the text, split on a blank line (
\n\n), and keep the last, possibly half-received, piece for the next chunk. - Ignore lines that don't start with
data:, so a future addition can't break your parser. - Ignore event types you don't know. New types may be added; existing fields won't change meaning.
- Treat the stream as failed if it ends without
doneorerror(for example a dropped connection), and show the user a retry. cost_usdis an estimate from the token prices set on the agent, and is0when none are set. Your model provider's bill is the source of truth.
// Node 18+ (fetch and web streams built in)const res = await fetch("https://api.sentientone.ai/v1/chat/stream", { method: "POST", headers: { "Content-Type": "application/json", "X-Api-Key": process.env.SENTIENTONE_API_KEY, "X-Agent-Id": process.env.AGENT_ID, "X-Request-Id": crypto.randomUUID(), }, body: JSON.stringify({ message: "Where is order 10482?" }),})if (!res.ok) { // Errors before the stream starts: an HTTP status and one JSON envelope const { error } = await res.json() throw new Error(error.code + ": " + error.message + " (ref " + error.request_id + ")")}const reader = res.body.getReader()const decoder = new TextDecoder()let buffer = ""let conversationId = nullwhile (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const events = buffer.split("\n\n") buffer = events.pop() // keep a half-received event for the next chunk for (const raw of events) { if (!raw.startsWith("data: ")) continue const event = JSON.parse(raw.slice(6)) switch (event.type) { case "meta": conversationId = event.conversation_id; break case "sources": console.log("sources", event.sources.map((s) => s.title)); break case "tool_call": console.log("calling", event.name, event.input); break case "delta": process.stdout.write(event.content); break case "done": console.log("\nusage", event.usage); break case "error": throw new Error(event.code + ": " + event.message) } }}Citations in the text
Agents with knowledge are told to end their answer with Source: lines: a markdown link for web pages, or the file name for documents. Q&A entries are never cited. If you'd rather render your own citations, use the sources event. See Sources and citations.
Still stuck?
Email us with what you tried and any request id. A person replies.