Chat with an agent (v1)
POST /api/v1/chat — the public, SSE-streamed chat runtime. Send a message to an agent and stream back tokens, citations, and a done frame.
View as MarkdownPOST /api/v1/chat
The public chat runtime. This is the endpoint the deployed website widget uses. It accepts a single message for an agent and streams the reply over Server-Sent Events (SSE). For new server-to-server integrations prefer API v2 chat, which has a richer streaming format, rate-limit headers, and machine error codes.
This endpoint is keyed by agentId in the body and is reachable by the public widget runtime. When calling it server-side, send your Authorization: Bearer bk_... key as well.
Request body
| Field | Type | Required | Notes |
|---|---|---|---|
| agentId | integer | Yes | Positive integer. The agent to chat with. |
| message | string | Yes | 1–8000 characters. |
| conversationId | integer | No | Continue an existing conversation. Omit to start a new one. |
| model | string | No | Per-request model override (max 120 chars). Falls back to the agent's saved model. |
SSE response
On success the response is text/event-stream. Each frame is a named SSE event with a JSON data payload. Expect token frames as the answer streams, optional citations, a terminal done, and error if something fails mid-stream.
curl -N https://app.bookbag.ai/api/v1/chat \
-H "Authorization: Bearer $BOOKBAG_API_KEY" \
-H "Content-Type: application/json" \
-d '{ "agentId": 123, "message": "Where is my order?" }'const res = await fetch("https://app.bookbag.ai/api/v1/chat", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.BOOKBAG_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({ agentId: 123, message: "Where is my order?" }),
});
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = "";
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const frames = buffer.split("\n\n");
buffer = frames.pop();
for (const frame of frames) {
const ev = /^event: (.*)$/m.exec(frame)?.[1];
const data = /^data: (.*)$/m.exec(frame)?.[1];
if (ev === "token") process.stdout.write(JSON.parse(data).content);
}
}: connected
event: token
data: {"content":"Your "}
event: token
data: {"content":"order shipped yesterday."}
event: citations
data: {"citations":[{"title":"Shipping policy","url":"https://acme.com/shipping"}]}
event: done
data: {"conversationId":456,"messageId":789}Status codes
| Status | Meaning |
|---|---|
| 200 | Stream opened. Frames follow as text/event-stream. |
| 400 | Validation failed (e.g. missing message). Body: { success:false, error:{ code:"validation_error", message, details } }. |
| 404 | Agent not found or archived. Body: { success:false, error:{ code:"agent_not_found", message } }. |
Errors before the stream opens are JSON. After the stream opens, a failure arrives as an event: error frame. See Errors.