curl --request GET \
--url https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body"event: snapshot\ndata: {\"chatUID\":\"550e8400-e29b-41d4-a716-446655440000\",\"status\":\"processing\",\"chatHistory\":[{\"role\":\"user\",\"content\":{\"message\":\"Analyze this data\"}}],\"streamingMessage\":{\"role\":\"assistant\",\"content\":{\"message\":\"Based on the an\"}},\"lastUpdatedAt\":1705312200000}\n\n: keep-alive\n\nevent: snapshot\ndata: {\"chatUID\":\"550e8400-e29b-41d4-a716-446655440000\",\"status\":\"completed\",\"chatHistory\":[{\"role\":\"user\",\"content\":{\"message\":\"Analyze this data\"}},{\"role\":\"assistant\",\"content\":{\"message\":\"Based on the analysis...\"}}],\"lastUpdatedAt\":1705312214000}\n\n"Follow real-time chat history (SSE)
The same real-time state as GET …/realtime, pushed over one server-sent events connection instead of polled: the current state is sent right away, then a new snapshot event each time it changes — the assistant’s reply growing in streamingMessage, tool calls, status transitions. Send messages with POST …/messages?async=true; opening the stream never runs the model.
Each frame is event: snapshot followed by data: <the same JSON object the realtime endpoint returns>. Comment frames (: keep-alive) arrive every 15 s of silence. The server closes the connection when the status is terminal (completed, error, limit_exceeded) with nothing queued, or after 60 s in any case: if the last snapshot is not terminal, open it again — every frame carries the full state, so nothing is lost. An event: reconnect frame precedes a close caused by a server error.
EventSource cannot send an Authorization header; use fetch and read the body. This is what @devicai/ui does with streaming enabled.
curl --request GET \
--url https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream \
--header 'Authorization: Bearer <token>'import requests
url = "https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream"
headers = {"Authorization": "Bearer <token>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {Authorization: 'Bearer <token>'}};
fetch('https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"Authorization: Bearer <token>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("Authorization", "Bearer <token>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream")
.header("Authorization", "Bearer <token>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.devic.ai/v1/assistants/{identifier}/chats/{chatUid}/stream")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["Authorization"] = 'Bearer <token>'
response = http.request(request)
puts response.read_body"event: snapshot\ndata: {\"chatUID\":\"550e8400-e29b-41d4-a716-446655440000\",\"status\":\"processing\",\"chatHistory\":[{\"role\":\"user\",\"content\":{\"message\":\"Analyze this data\"}}],\"streamingMessage\":{\"role\":\"assistant\",\"content\":{\"message\":\"Based on the an\"}},\"lastUpdatedAt\":1705312200000}\n\n: keep-alive\n\nevent: snapshot\ndata: {\"chatUID\":\"550e8400-e29b-41d4-a716-446655440000\",\"status\":\"completed\",\"chatHistory\":[{\"role\":\"user\",\"content\":{\"message\":\"Analyze this data\"}},{\"role\":\"assistant\",\"content\":{\"message\":\"Based on the analysis...\"}}],\"lastUpdatedAt\":1705312214000}\n\n"Reading the stream
EventSource cannot send an Authorization header, so open the stream with fetch and split the body on blank lines. Reopen it while the last snapshot is not terminal: the server closes every connection after 60 seconds. Ask for ?partial=1 so the reply being written arrives as delta frames (the appended text) instead of a whole snapshot per write.
async function followChat(identifier, chatUid, onSnapshot) {
while (true) {
const res = await fetch(
`https://api.devic.ai/api/v1/assistants/${identifier}/chats/${chatUid}/stream?partial=1`,
{ headers: { Authorization: 'Bearer devic-xxx', Accept: 'text/event-stream' } },
);
if (!res.headers.get('content-type')?.includes('text/event-stream')) {
throw new Error(`stream unavailable: ${res.status}`); // fall back to …/realtime
}
const reader = res.body.getReader();
const decoder = new TextDecoder();
let buffer = '';
let last;
for (;;) {
const { value, done } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
let end;
while ((end = buffer.indexOf('\n\n')) !== -1) {
const frame = buffer.slice(0, end);
buffer = buffer.slice(end + 2);
const data = frame.split('\n').filter((l) => l.startsWith('data:')).map((l) => l.slice(5).trim()).join('');
if (frame.includes('event: snapshot')) last = JSON.parse(data);
else if (frame.includes('event: partial') && last) last = { ...last, ...JSON.parse(data) };
else if (frame.includes('event: delta') && last?.streamingMessage) {
const m = last.streamingMessage;
last = { ...last, streamingMessage: { ...m, content: { ...m.content, message: (m.content?.message ?? '') + JSON.parse(data).append } } };
} else continue; // keep-alive, reconnect
onSnapshot(last);
}
}
if (last && ['completed', 'error', 'limit_exceeded'].includes(last.status)) return last;
}
}
status is processing, streamingMessage holds the assistant’s reply so far when the model streams (OpenAI, Anthropic, Gemini) and the assistant has no guardrails enabled. Other providers and guarded assistants deliver the text in one piece; status changes and tool calls still arrive as they happen.GET …/realtime instead only when you cannot hold a connection open, such as a serverless function with a short timeout.Authorizations
Use JWT token for authentication
Path Parameters
The unique identifier of the assistant specialization
The unique identifier of the chat conversation
Query Parameters
1 or true: while only the reply being written changes, send event: delta frames with the appended text ({"append":"…"}) or event: partial frames with the whole streamingMessage, instead of a full snapshot. About half the bytes of polling, against 6-7 times without it. Older APIs ignore it.
1, true Response
A text/event-stream body. snapshot events carry a RealtimeChatHistoryDto (plus streamingMessage while the status is processing).
The response is of type string.
"event: snapshot\ndata: {\"chatUID\":\"550e8400-e29b-41d4-a716-446655440000\",\"status\":\"processing\",\"chatHistory\":[{\"role\":\"user\",\"content\":{\"message\":\"Analyze this data\"}}],\"streamingMessage\":{\"role\":\"assistant\",\"content\":{\"message\":\"Based on the an\"}},\"lastUpdatedAt\":1705312200000}\n\n: keep-alive\n\nevent: snapshot\ndata: {\"chatUID\":\"550e8400-e29b-41d4-a716-446655440000\",\"status\":\"completed\",\"chatHistory\":[{\"role\":\"user\",\"content\":{\"message\":\"Analyze this data\"}},{\"role\":\"assistant\",\"content\":{\"message\":\"Based on the analysis...\"}}],\"lastUpdatedAt\":1705312214000}\n\n"