Ai streaming

Stream chat response deltas (Server-Sent Events)

GET/ai/streams/{streamId}/events

Server-sent-events counterpart to GET /ai/streams/{streamId}. Instead of returning a snapshot the caller re-polls, this holds the connection open and pushes the assistant's response as it is generated. The server polls the DB-backed stream (~300ms) and flushes each new chunk until the message completes.

Requires the ai key scope and a linked service user. The streamId must belong to the linked user — one key can never read another key's stream (404 otherwise).

Usage

Open this on its own connection after issuing the message POST on a separate connection (PHP-FPM blocks per request):

  1. POST /ai/streams/init {chatRef}streamId
  2. POST /ai/chats/{ref}/messages {message, streamId} (on connection A)
  3. GET /ai/streams/{streamId}/events (on connection B) — consume the event stream below

Standard text/event-stream semantics — use any SSE client (EventSource, curl -N, an SSE library). The connection is hard-capped server-side (see Bounds) and closes itself on completion.

Event types

event:data: payloadMeaning
delta{ "content": "<text>" }Text appended since the previous event — concatenate onto what you have.
delta{ "content": "<full text>", "replace": true }Rare: earlier content was rewritten (e.g. a follow-ups block streamed in then got stripped). Replace your buffer with content rather than appending.
doneStreamDoneEventTerminal. Stream completed (or was aborted); connection closes. Carries the fully-cleaned final content plus results / followUpQuestions.
errorStreamErrorEventTerminal. The stream ended in an error state, the row vanished, or the connection hit its time cap.

Lines beginning : (e.g. : keepalive) are SSE comments emitted when the stream is idle to defeat proxy buffering — ignore them.

Bounds

A connection lives for exactly one chat response (seconds, not a subscription). It is hard-capped at 120s; if the underlying stream has not completed by then the server emits a terminal error (status: "timeout") and closes. A vanished client is detected each poll and frees the worker within one poll interval. Per-key rate limits cap concurrent streams per integration.

Example

event: deltadata: {"content":"Once upon a time, a publisher asked"}event: deltadata: {"content":" for server-sent events."}: keepaliveevent: donedata: {"status":"complete","content":"Once upon a time, a publisher asked for server-sent events.","reasoning":null,"results":[],"followUpQuestions":[]}
X-Api-Key<token>

Issued to you when your integration is provisioned. Determines which catalogue resources are visible and which AI tools are available.

In: header

Path Parameters

streamId*string

The stream id from POST /ai/streams/init.

Response Body

text/event-stream

application/json

application/json

application/json

curl -X GET "https://example.com/ai/streams/string/events"
"event: delta\ndata: {\"content\":\"Hello\"}\n\nevent: delta\ndata: {\"content\":\" world\"}\n\nevent: done\ndata: {\"status\":\"complete\",\"content\":\"Hello world\",\"reasoning\":null,\"results\":[],\"followUpQuestions\":[]}\n"
{  "success": false,  "status": "fail",  "message": "string",  "error": {    "message": "API key is required",    "code": "NO_API_KEY"  }}
{  "success": false,  "status": "fail",  "message": "string",  "error": {    "message": "API key is required",    "code": "NO_API_KEY"  }}
{  "success": false,  "status": "fail",  "message": "string",  "error": {    "message": "API key is required",    "code": "NO_API_KEY"  }}