API reference
Code examples
Copy-ready examples: cURL, a Node stream reader, a Next.js route that keeps your key server-side, and async Python with conversations.
Guide4 min readUpdated 23 October 2026
Each example calls POST /v1/chat/stream with the secret key from an environment variable. Set SENTIENTONE_API_KEY (from API keys in the app) and AGENT_ID (from the agent's Deploy tab) first. The app's API keys page also has TypeScript and Python samples and a Try it panel.
cURL
-N turns off buffering so you see the events as they arrive.
curl -N https://api.sentientone.ai/v1/chat/stream \ -H "Content-Type: application/json" \ -H "X-Api-Key: $SENTIENTONE_API_KEY" \ -H "X-Agent-Id: $AGENT_ID" \ -d '{"message": "Where is order 10482?"}'Node.js: read the stream
// Node 18+ (fetch and web streams built in)const res = await fetch("https://api.sentientone.ai/v1/chat/stream", { method: "POST", headers: { "Content-Type": "application/json", "X-Api-Key": process.env.SENTIENTONE_API_KEY, "X-Agent-Id": process.env.AGENT_ID, "X-Request-Id": crypto.randomUUID(), }, body: JSON.stringify({ message: "Where is order 10482?" }),})if (!res.ok) { // Errors before the stream starts: an HTTP status and one JSON envelope const { error } = await res.json() throw new Error(error.code + ": " + error.message + " (ref " + error.request_id + ")")}const reader = res.body.getReader()const decoder = new TextDecoder()let buffer = ""let conversationId = nullwhile (true) { const { done, value } = await reader.read() if (done) break buffer += decoder.decode(value, { stream: true }) const events = buffer.split("\n\n") buffer = events.pop() // keep a half-received event for the next chunk for (const raw of events) { if (!raw.startsWith("data: ")) continue const event = JSON.parse(raw.slice(6)) switch (event.type) { case "meta": conversationId = event.conversation_id; break case "sources": console.log("sources", event.sources.map((s) => s.title)); break case "tool_call": console.log("calling", event.name, event.input); break case "delta": process.stdout.write(event.content); break case "done": console.log("\nusage", event.usage); break case "error": throw new Error(event.code + ": " + event.message) } }}Next.js: a server route for your own chat UI
A browser must never hold your secret key. Put a small route on your server that adds the key and passes the stream through; your front end calls that route and parses the events exactly as above.
// app/api/chat/route.ts in a Next.js app: keeps the secret key on your serverexport async function POST(req: Request) { const { message, conversationId } = await req.json() const upstream = await fetch("https://api.sentientone.ai/v1/chat/stream", { method: "POST", headers: { "Content-Type": "application/json", "X-Api-Key": process.env.SENTIENTONE_API_KEY!, "X-Agent-Id": process.env.AGENT_ID!, "X-Request-Id": req.headers.get("x-request-id") ?? crypto.randomUUID(), }, body: JSON.stringify({ message, conversation_id: conversationId ?? undefined }), }) // Pass the event stream (or the error envelope) straight through to the browser return new Response(upstream.body, { status: upstream.status, headers: { "Content-Type": upstream.headers.get("Content-Type") ?? "text/event-stream" }, })}Python: one request
import json, os, uuidimport httpxheaders = { "X-Api-Key": os.environ["SENTIENTONE_API_KEY"], "X-Agent-Id": os.environ["AGENT_ID"], "X-Request-Id": uuid.uuid4().hex,}body = {"message": "Where is order 10482?"}# The API ends a reply after 120 seconds, so give the client a little longerwith httpx.stream("POST", "https://api.sentientone.ai/v1/chat/stream", headers=headers, json=body, timeout=130) as res: if res.status_code != 200: res.read() err = res.json()["error"] raise RuntimeError(f"{err['code']}: {err['message']} (ref {err['request_id']})") for line in res.iter_lines(): if not line.startswith("data: "): continue event = json.loads(line[6:]) if event["type"] == "delta": print(event["content"], end="", flush=True) elif event["type"] == "done": conversation_id = event["conversation_id"] # send it back to continue elif event["type"] == "error": raise RuntimeError(f"{event['code']}: {event['message']}")Python: a conversation with asyncio
import asyncio, json, osimport httpxasync def ask(client: httpx.AsyncClient, message: str, conversation_id: str | None = None): body = {"message": message} if conversation_id: body["conversation_id"] = conversation_id answer, conv = [], None async with client.stream("POST", "/v1/chat/stream", json=body) as res: res.raise_for_status() async for line in res.aiter_lines(): if not line.startswith("data: "): continue event = json.loads(line[6:]) if event["type"] == "delta": answer.append(event["content"]) elif event["type"] == "done": conv = event["conversation_id"] elif event["type"] == "error": raise RuntimeError(event["code"]) return "".join(answer), convasync def main(): async with httpx.AsyncClient( base_url="https://api.sentientone.ai", headers={"X-Api-Key": os.environ["SENTIENTONE_API_KEY"], "X-Agent-Id": os.environ["AGENT_ID"]}, timeout=130, ) as client: first, conv = await ask(client, "Do you ship overseas?") follow_up, _ = await ask(client, "How long does it take?", conv) print(first, "\n---\n", follow_up)asyncio.run(main())Retrying on rate limits
async function chatWithRetry(body, attempt = 0) { const res = await fetch("https://api.sentientone.ai/v1/chat/stream", { method: "POST", headers: { "Content-Type": "application/json", "X-Api-Key": process.env.SENTIENTONE_API_KEY, "X-Agent-Id": process.env.AGENT_ID, }, body: JSON.stringify(body), }) if (res.status === 429 && attempt < 3) { // Chat 429s carry X-RateLimit-Reset, the seconds until the window resets (no Retry-After) const reset = Number(res.headers.get("X-RateLimit-Reset")) const waitMs = Number.isFinite(reset) && reset > 0 ? reset * 1000 : 2000 * (attempt + 1) await new Promise((r) => setTimeout(r, Math.min(waitMs, 60_000))) return chatWithRetry(body, attempt + 1) } return res}You're done when
Your terminal prints the answer word by word and a done event with token usage, and the request shows up on the Tracing page.
If something goes wrong
Nothing prints until the end
Something is buffering the stream: use curl -N, and check proxies in between (for example nginx needs buffering off for event streams).
I get 401
Check the variable holds your sk-so- key with no trailing newline. See Authentication.
The stream ends with an error event
Read its code. LLM_AUTH_ERROR means the agent's model key is wrong; see Errors for the rest.
Still stuck?
Email us with what you tried and any request id. A person replies.