Aibo Platform docs

Streaming replies

POST /v1/chats/{chatId}/messages has two modes:

Request Response
Accept: text/event-stream A server-sent event stream: your stored message, then each agent reply as it is generated
anything else 202 { message, responders } once your message is stored; replies arrive later via GET …/messages and the message.created webhook

Event sequence

text
message.created                  ← the user message you just sent, as stored
  reply.started   (agent A)      ← for each responder, in order
  reply.delta*    (agent A)
  reply.completed (agent A)      ← the final stored agent Message
  reply.started   (agent B)
  reply.delta*    (agent B)
  reply.completed (agent B)
done                             ← stream ends

Responders are serial: one reply completes before the next starts. A DM has one responder; a group has as many as were @mentioned, or one picked by the orchestrator (routing).

Each SSE frame's event: name equals the JSON type:

text
event: reply.delta
data: {"type":"reply.delta","replyId":"rpl_1","text":"Sure — "}

Event shapes

ts
type StreamEvent =
  | { type: 'message.created'; message: Message }           // the user's message
  | { type: 'reply.started'; agentId: string; replyId: string }
  | { type: 'reply.delta'; replyId: string; text: string }   // append to the reply with this replyId
  | { type: 'reply.completed'; message: Message }           // final agent message (persisted)
  | { type: 'done' }
  | { type: 'error'; error: { code: string; message: string; details?: unknown } };

type Message = {
  id: string;
  chatId: string;
  sender: { type: 'user'; externalId: string } | { type: 'agent'; agentId: string } | { type: 'system' };
  kind: 'text' | 'system';
  content: string;
  createdAt: string; // ISO 8601
};

Treat reply.delta text as a preview; when reply.completed arrives, replace the preview with message.content — it is the canonical, stored reply. An error event uses the same { code, message, details? } shape as the error envelope.

With the SDK

messages.stream() returns an async iterable of StreamEvents.

ts
const drafts = new Map<string, string>();

for await (const event of client.messages.stream(chatId, { content: 'Summarize my week' })) {
  switch (event.type) {
    case 'message.created':
      render(event.message);
      break;
    case 'reply.started':
      drafts.set(event.replyId, '');
      showTyping(event.agentId);
      break;
    case 'reply.delta':
      drafts.set(event.replyId, drafts.get(event.replyId) + event.text);
      renderDraft(event.replyId, drafts.get(event.replyId));
      break;
    case 'reply.completed':
      render(event.message); // final version
      break;
    case 'error':
      showError(event.error.code, event.error.message);
      break;
    case 'done':
      break;
  }
}

Raw HTTP

bash
curl -sS -N -X POST "$AIBO_URL/v1/chats/$CHAT_ID/messages" \
  -H "Authorization: Bearer $AIBO_USER_TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Accept: text/event-stream' \
  -H 'Idempotency-Key: 5d0e7c1a-4a8b-4f0e-9c55-3f1b7d2a9e10' \
  -d '{"content":"Summarize my week"}'

EventSource can't send a POST body or an Authorization header, so in browsers read the stream with fetch() and a ReadableStream reader, splitting frames on blank lines.

Idempotency keys

Networks drop. To retry a send safely, include an Idempotency-Key header (≤ 128 chars, e.g. a UUID generated once per message the user typed). Retrying with the same key returns the original result instead of storing the message — and triggering replies — twice.

bash
-H "Idempotency-Key: $(uuidgen)"   # generate once per user message, reuse on retry

Generate the key when the user hits Send, not per HTTP attempt.

React Native and other non-streaming clients

React Native's built-in fetch does not expose a streaming response body, so SSE over fetch won't deliver events incrementally there. Options:

  1. Use a streaming-capable fetch (for example expo/fetch in recent Expo SDKs) or an SSE library that supports POST and custom headers.
  2. Fall back to non-streaming: omit Accept: text/event-stream, take the 202 { message, responders }, show a typing indicator for each responder, and poll GET /v1/chats/{chatId}/messages until a message from each responder has arrived.

Either way, send the same Idempotency-Key on retries.