Examples
Working clients for the streaming chat API.
Each example reads the API key from the environment. Never inline a real key.
curl
Stream to the terminal
curl -N https://app.example.com/api/v1/public/chat \
-H "Authorization: Bearer $DOT_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"chatbotId": "YOUR_CHATBOT_ID",
"messages": [{ "role": "user", "content": "Do you ship to the EU?" }]
}'Node.js
chat.ts
type StreamEvent =
| { type: "text-delta"; delta: string }
| { type: "tool-result"; id: string; result: { conversationId?: string } }
| { type: "usage"; usage: { inputTokens: number; outputTokens: number } }
| { type: "done"; finishReason: string }
| { type: "error"; message: string; code?: string };
export async function ask(chatbotId: string, question: string, conversationId?: string) {
const response = await fetch("https://app.example.com/api/v1/public/chat", {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.DOT_API_KEY}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
chatbotId,
conversationId,
messages: [{ role: "user", content: question }],
}),
});
if (!response.ok) {
const { error } = await response.json();
throw new Error(`${error.code}: ${error.message}`);
}
const reader = response.body!.getReader();
const decoder = new TextDecoder();
let buffer = "";
let answer = "";
let conversation = conversationId;
while (true) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
// Frames are separated by a blank line.
let boundary = buffer.indexOf("\n\n");
while (boundary !== -1) {
const frame = buffer.slice(0, boundary);
buffer = buffer.slice(boundary + 2);
boundary = buffer.indexOf("\n\n");
const data = frame
.split("\n")
.filter((line) => line.startsWith("data:"))
.map((line) => line.slice(5).trim())
.join("");
if (!data) continue;
const event = JSON.parse(data) as StreamEvent;
if (event.type === "text-delta") answer += event.delta;
if (event.type === "tool-result" && event.id === "conversation") {
conversation = event.result.conversationId ?? conversation;
}
if (event.type === "error") throw new Error(event.message);
}
}
return { answer, conversationId: conversation };
}Continuing a conversation
Keep the conversationId from the first event of a stream and send it with the next turn. The thread then appears as one conversation in the inbox, and the model keeps its context.
Proxying from your own frontend
A browser must never hold the API key. Expose your own endpoint that authenticates your user, calls this API server-side, and pipes the stream back. That keeps the credential on your server and lets you apply your own limits per user.