API
One endpoint. Every agent, every event.
Every agent you build answers on the same streaming endpoint. Send a message, read server-sent events: the sources it found, the tools it called, the tokens as they arrive and the usage at the end.
200 free credits when you sign up. No card needed.
Quickstart
Three steps from sign-up to a streamed answer.
- 1Create an agent and save a model provider key for it on the LLM keys page.
- 2Open API keys in the app: copy your secret key (sk-so-…) and the agent's id. The page also has a request tester and ready-made snippets.
- 3Call the endpoint from your server. Keep the secret key out of browsers and apps; for a website, use the widget's publishable key instead.
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?"}'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({ message: "Where is order 10482?" }),})if (!res.ok) { const { error } = await res.json() // { code, message, request_id } throw new Error(`${error.code}: ${error.message} (ref ${error.request_id})`)}const reader = res.body.getReader()const decoder = new TextDecoder()let buffer = ""while (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() for (const e of events) { if (!e.startsWith("data: ")) continue const event = JSON.parse(e.slice(6)) if (event.type === "delta") process.stdout.write(event.content) if (event.type === "error") throw new Error(event.code) }}import json, osimport httpxheaders = { "X-Api-Key": os.environ["SENTIENTONE_API_KEY"], "X-Agent-Id": os.environ["AGENT_ID"],}body = {"message": "Where is order 10482?"}with httpx.stream("POST", "https://api.sentientone.ai/v1/chat/stream", headers=headers, json=body, timeout=130) as res: res.raise_for_status() 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 continueAuthentication
Headers on every request.
X-Api-Key | Your secret key, sk-so- followed by 58 hex characters. Required |
|---|---|
X-Agent-Id | The agent's UUID. Required (or agent_id in the body) |
X-Llm-Key | Optional. A model provider key for this request only, in place of the agent's saved key |
X-Request-Id | Optional. Your correlation id; it's carried through logs and traces and returned to you |
Secret keys are for servers. Browsers and websites use the widget and its publishable pk-so- key; a secret key sent on the widget path is refused.
Request
POST https://api.sentientone.ai/v1/chat/stream with a JSON body.
message | The user's message, up to 32,000 characters. Required |
|---|---|
conversation_id | Continue an earlier conversation (from the meta or done event). Omit to start a new one |
agent_id | Instead of the X-Agent-Id header |
prompt | Replaces the agent's system prompt for this request only, for testing a change |
allow_unpublished | true lets an inactive agent answer (for testing). Defaults to false |
Streamed events
The response is text/event-stream. Each event is one data: line holding a JSON object with a type.
meta | First. conversation_id and trace_id |
|---|---|
sources | The knowledge passages given to the model: index, id, title, url, filename, source_type, score, snippet |
tool_call | The agent is calling an MCP tool: id, name, input |
tool_result | What the tool returned: id, name, output |
delta | The next piece of the answer: content |
done | Last on success. conversation_id, trace_id, usage (input, output and total tokens, estimated cost), and cached: true when the answer came from the answer cache |
error | Last on failure. code, message, request_id |
data: {"type": "meta", "conversation_id": "9b1c…", "trace_id": "4f2a…"}data: {"type": "sources", "sources": [{"index": 1, "title": "Shipping policy", "url": "https://…", "score": 0.82}]}data: {"type": "tool_call", "id": "call_1", "name": "get_order", "input": {"order_id": "10482"}}data: {"type": "tool_result", "id": "call_1", "name": "get_order", "output": "…"}data: {"type": "delta", "content": "Order 10482 left our warehouse"}data: {"type": "delta", "content": " on Tuesday and…"}data: {"type": "done", "conversation_id": "9b1c…", "trace_id": "4f2a…", "usage": {"input_tokens": 1830, "output_tokens": 64, "total_tokens": 1894}}Conversations
SentientOne keeps the history. Send the conversation_id back and the agent sees the earlier turns; you don't resend them.
{ "message": "And when will it arrive?", "conversation_id": "9b1c…"}Every conversation, from every channel, is on the agent's Conversations tab.
Errors
Before the stream starts, errors are HTTP status codes with one JSON envelope. Once it has started, a failure arrives as an error event with the same fields.
HTTP/1.1 402 Payment RequiredX-Request-Id: 284662c8227a417d9f66755385919e0e{ "error": { "code": "OUT_OF_CREDITS", "message": "You're out of credits. Buy credits to continue.", "request_id": "284662c8227a417d9f66755385919e0e" }}| 400 | MISSING_AGENT_ID, MISSING_MESSAGE |
|---|---|
| 401 | MISSING_API_KEY, INVALID_API_KEY |
| 402 | OUT_OF_CREDITS, with your balance. Top up and retry |
| 403 | AGENT_INACTIVE: the agent is switched off |
| 404 | AGENT_NOT_FOUND |
| 422 | VALIDATION_ERROR: the body doesn't match the schema |
| 429 | RATE_LIMIT_EXCEEDED: wait for the window in X-RateLimit-Reset |
| In the stream | CONVERSATION_NOT_FOUND, TIMEOUT (after 120 s), LLM_AUTH_ERROR, LLM_RATE_LIMITED, LLM_UNAVAILABLE, LLM_ERROR, INTERNAL_ERROR |
A reply that fails after its credit was spent is refunded. Quote the request_id to support and we can find the request and everything it touched.
Rate limits and headers
Each agent has a requests-per-minute limit (60 by default for new agents), counted in a fixed one-minute window. Every response says where you stand.
X-RateLimit-Limit | Requests allowed per minute for this agent |
|---|---|
X-RateLimit-Remaining | Left in the current window |
X-RateLimit-Reset | When the window resets |
X-Trace-Id | The trace for this request in Tracing |
X-Request-Id | Your correlation id, or one made for you |
Request ids and traces
Send your own X-Request-Id (or X-Correlation-Id) and it follows the request through the API, its logs, its error reports and on to the model provider, your MCP servers and workflow webhooks. Without one, the API makes one and returns it.
- The trace_id in meta and done (and the X-Trace-Id header) opens the request in Tracing: retrieval, model rounds, tool calls, tokens and cost.
- See tracing and analytics for what each trace holds.
Workflows API
Start a live run of a published workflow with POST /v1/workflows/run: the same secret key, workflow_id, input and mode: "live". It streams each step as server-sent events, or returns the finished run as JSON with "stream": false.
curl https://api.sentientone.ai/v1/workflows/run \ -H "Content-Type: application/json" \ -H "X-Api-Key: $SENTIENTONE_API_KEY" \ -d '{"workflow_id": "…", "mode": "live", "stream": false, "input": {"message": "My invoice is wrong"}}'30 runs a minute and 5 at once per account. Webhook triggers and the rest are on the Workflows page.
Reference
| OpenAPI | api.sentientone.ai/docs |
|---|---|
| Guides | Help Centre: API |
| Health | api.sentientone.ai/health |
| For AI assistants | llms-full.txt |
Get a key and send your first request
Sign up and you get 200 free credits, no card needed. Create an agent, add your knowledge, try it in the Playground, then call it from your code or put it on your website.