BookbagBookbag

API v2 streaming format

How API v2 streams chat responses: bare data: lines carrying text-delta parts, a terminal finish, and a final data: [DONE]. With a parsing example.

View as Markdown

When you call chat with streaming enabled (the default), the response is text/event-stream. Unlike the v1 stream, v2 uses the AI-SDK data-stream convention: each line is a bare data: carrying a JSON object (no event: names), and the stream ends with a literal data: [DONE].

Frame types

typeFieldsMeaning
text-deltatextA chunk of the assistant's reply. Concatenate text across frames to build the full answer.
errorerrorTextA failure occurred mid-stream. The stream still terminates with [DONE].
finishfinishReason, metadataTerminal metadata frame: finishReason is stop (or tool-calls when the agent is awaiting a tool result), and metadata carries messageId, conversationId, userId, userMessageId, and usage.credits.

After the finish frame, the server writes one last line — the literal string [DONE] — and closes the connection.

Raw stream

data: {"type":"text-delta","text":"Your "}

data: {"type":"text-delta","text":"order shipped yesterday."}

data: {"type":"finish","finishReason":"stop","metadata":{"userMessageId":"msg_788","conversationId":"456","userId":"user_8821","messageId":"msg_789","usage":{"credits":1}}}

data: [DONE]

Parsing the stream

Split on newlines, strip the data: prefix, stop at [DONE], and JSON-parse everything else:

const res = await fetch(
  "https://app.bookbag.ai/api/v2/agents/123/chat",
  {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.BOOKBAG_API_KEY}`,
      "Content-Type": "application/json",
    },
    body: JSON.stringify({ message: "Where is my order?", userId: "user_8821" }),
  }
);

const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
let answer = "";

for (;;) {
  const { value, done } = await reader.read();
  if (done) break;
  buffer += decoder.decode(value, { stream: true });
  const lines = buffer.split("\n");
  buffer = lines.pop();
  for (const line of lines) {
    if (!line.startsWith("data: ")) continue;
    const payload = line.slice(6);
    if (payload === "[DONE]") continue;
    const frame = JSON.parse(payload);
    if (frame.type === "text-delta") answer += frame.text;
    else if (frame.type === "finish") console.log("done", frame.metadata);
    else if (frame.type === "error") throw new Error(frame.errorText);
  }
}
console.log(answer);
tip

Buffer across reads. A single network chunk may contain a partial line; keep the trailing fragment in buffer and only parse complete lines.

Prefer JSON?

Set "stream": false in the request body to get a single JSON response instead. See Chat with an agent (v2).