Poll a stream
/ai/streams/{streamId}Returns the current accumulated content. Safe to poll. Once
isComplete is true, stop polling and either inspect content
here or GET /ai/chats/{ref} for the saved assistant message.
Authorization
apiKey Issued to you when your integration is provisioned. Determines which catalogue resources are visible and which AI tools are available.
In: header
Path Parameters
Response Body
application/json
application/json
curl -X GET "https://example.com/ai/streams/string"{ "data": { "streamId": "string", "chatRef": 0, "content": "string", "isComplete": true, "status": "streaming", "toolStatus": "string", "reasoning": "string", "results": [ { "title": "string", "preview": "string", "resourceId": 0, "score": 0, "rawScore": 0, "matchSource": "metadata", "hits": 0 } ] }}{ "success": false, "status": "fail", "message": "string", "error": { "message": "API key is required", "code": "NO_API_KEY" }}Stream chat response deltas (Server-Sent Events) GET
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:` payload | Meaning | |----------|-----------------|---------| | `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. | | `done` | `StreamDoneEvent` | Terminal. Stream completed (or was aborted); connection closes. Carries the fully-cleaned final content plus `results` / `followUpQuestions`. | | `error` | `StreamErrorEvent` | Terminal. 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 ```text event: delta data: {"content":"Once upon a time, a publisher asked"} event: delta data: {"content":" for server-sent events."} : keepalive event: done data: {"status":"complete","content":"Once upon a time, a publisher asked for server-sent events.","reasoning":null,"results":[],"followUpQuestions":[]} ```
Initialise a stream POST
Creates a stream bound to a chat. Pass the returned `streamId` on `POST /ai/chats/{ref}/messages` and poll `GET /ai/streams/{streamId}` from a separate connection. Note: the message POST is synchronous and won't release until the AI finishes. Polling **must** happen on a different connection (a separate thread / async task in your client).