Skip to content

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.