Skip to content

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.

Terminal
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

stream.mjs (Node 18+)
// 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
// 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

stream.py (httpx)
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

conversation.py
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

retry.mjs
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.

support@sentientone.ai