# 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.

When you call [chat](/docs/api/v2/chat) with streaming enabled (the default), the response is `text/event-stream`. Unlike the [v1 stream](/docs/api/v1/chat), 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

| type | Fields | Meaning |
| --- | --- | --- |
| text-delta | text | A chunk of the assistant's reply. Concatenate `text` across frames to build the full answer. |
| error | errorText | A failure occurred mid-stream. The stream still terminates with `[DONE]`. |
| finish | finishReason, metadata | Terminal 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

```text
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:

```javascript
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)](/docs/api/v2/chat).
