Chat API
The Chat API sends messages to the NimbleBrain agent and receives responses. Two endpoints are available: a synchronous endpoint that returns the full response, and a streaming endpoint that delivers events over SSE as the agent works.
POST /v1/chat
Section titled “POST /v1/chat”Send a message and receive the complete response when the agent finishes. This endpoint blocks until the agent completes all iterations, including any tool calls.
Request body:
| Field | Type | Required | Description |
|---|---|---|---|
message | string | Yes | The user message to send |
conversationId | string | No | Continue an existing conversation. Omit to start a new one. |
model | string | No | Override the default model for this request |
appContext | object | No | Scope the chat to a specific app (see below) |
The appContext object:
| Field | Type | Description |
|---|---|---|
appName | string | Name of the installed app |
serverName | string | MCP server name backing the app |
Response:
{ "response": "The current weather in San Francisco is 62°F with partly cloudy skies.", "conversationId": "conv_a1b2c3d4", "skillName": null, "toolCalls": [ { "id": "tc_x7y8z9", "name": "weather__get_forecast", "input": { "city": "San Francisco" }, "output": "{\"temp\":62,\"condition\":\"partly cloudy\"}", "ok": true, "ms": 340 } ], "inputTokens": 1250, "outputTokens": 45, "stopReason": "end_turn", "usage": { "inputTokens": 1250, "outputTokens": 45, "cacheCreationTokens": 0, "cacheReadTokens": 980, "costUsd": 0.0042, "model": "claude-sonnet-4-5-20250929", "llmMs": 1820, "iterations": 2 }}Response fields:
| Field | Type | Description |
|---|---|---|
response | string | The agent’s text response |
conversationId | string | The conversation ID (new or existing) |
skillName | string | null | Matched skill name, or null if none matched |
toolCalls | array | Tool calls made during this turn |
inputTokens | number | Total input tokens consumed |
outputTokens | number | Total output tokens generated |
stopReason | string | Why the agent stopped: "end_turn", "token_budget", "max_iterations" |
usage | object | Detailed usage breakdown including cache tokens, cost, and timing |
Error responses:
| Status | Condition |
|---|---|
| 400 | Missing or non-string message field |
| 400 | Invalid JSON body |
| 401 | Not authenticated |
Example:
curl -X POST http://localhost:3000/v1/chat \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-secret-key" \ -d '{ "message": "What is the weather in San Francisco?", "conversationId": "conv_a1b2c3d4" }'{ "response": "The current weather in San Francisco is 62°F with partly cloudy skies.", "conversationId": "conv_a1b2c3d4", "skillName": null, "toolCalls": [ { "id": "tc_x7y8z9", "name": "weather__get_forecast", "input": { "city": "San Francisco" }, "output": "{\"temp\":62,\"condition\":\"partly cloudy\"}", "ok": true, "ms": 340 } ], "inputTokens": 1250, "outputTokens": 45, "stopReason": "end_turn", "usage": { "inputTokens": 1250, "outputTokens": 45, "cacheCreationTokens": 0, "cacheReadTokens": 980, "costUsd": 0.0042, "model": "claude-sonnet-4-5-20250929", "llmMs": 1820, "iterations": 2 }}POST /v1/chat/stream
Section titled “POST /v1/chat/stream”Send a message and receive events over SSE as the agent works. The request body is identical to POST /v1/chat.
The response has Content-Type: text/event-stream. Each event follows the SSE format:
event: <type>data: <json>Stream Events
Section titled “Stream Events”text.delta
Section titled “text.delta”A chunk of the agent’s text response. Concatenate all text.delta payloads to build the full response.
event: text.deltadata: {"runId":"run_abc123","text":"The current weather"}| Field | Type | Description |
|---|---|---|
runId | string | Unique identifier for this agent run |
text | string | Text fragment to append |
tool.start
Section titled “tool.start”The agent is calling a tool. Fired before the tool executes.
event: tool.startdata: {"runId":"run_abc123","name":"weather__get_forecast","id":"tc_x7y8z9","resourceUri":"ui://weather/dashboard"}| Field | Type | Description |
|---|---|---|
runId | string | Unique identifier for this agent run |
name | string | Fully qualified tool name (server__tool) |
id | string | Tool call ID |
resourceUri | string? | UI resource URI, if the tool’s app has one |
tool.done
Section titled “tool.done”A tool call completed.
event: tool.donedata: {"runId":"run_abc123","name":"weather__get_forecast","id":"tc_x7y8z9","ok":true,"ms":340,"result":"{\"temp\":62}"}| Field | Type | Description |
|---|---|---|
runId | string | Unique identifier for this agent run |
name | string | Fully qualified tool name |
id | string | Tool call ID |
ok | boolean | Whether the tool succeeded |
ms | number | Execution time in milliseconds |
resourceUri | string? | UI resource URI, if applicable |
result | string? | Tool result content |
The agent finished. The payload contains the full ChatResult, identical to the synchronous POST /v1/chat response.
event: donedata: {"response":"The current weather...","conversationId":"conv_a1b2c3d4","skillName":null,"toolCalls":[...],"inputTokens":1250,"outputTokens":45,"stopReason":"end_turn","usage":{...}}An error occurred during processing. The stream closes after this event.
event: errordata: {"error":"Model returned an empty response"}| Field | Type | Description |
|---|---|---|
error | string | Error message |
Stream Example
Section titled “Stream Example”curl -X POST http://localhost:3000/v1/chat/stream \ -H "Content-Type: application/json" \ -H "Authorization: Bearer your-secret-key" \ -d '{"message": "What is 2 + 2?"}' \ --no-bufferevent: text.deltadata: {"runId":"run_f4e5d6","text":"2 + 2"}
event: text.deltadata: {"runId":"run_f4e5d6","text":" equals 4."}
event: donedata: {"response":"2 + 2 equals 4.","conversationId":"conv_b2c3d4e5","skillName":null,"toolCalls":[],"inputTokens":320,"outputTokens":12,"stopReason":"end_turn","usage":{"inputTokens":320,"outputTokens":12,"cacheCreationTokens":0,"cacheReadTokens":0,"costUsd":0.001,"model":"claude-sonnet-4-5-20250929","llmMs":650,"iterations":1}}