Agent chat API
Santiago ships a built-in AI agent that runs inside the daemon: you send it a natural-language instruction and it drives the running profile for you using the automation API and the agent skill. This page documents the agent’s own HTTP surface — checking whether an LLM is configured, storing an API key, streaming a chat as Server-Sent Events, and managing per-profile history.
All endpoints live under the daemon base http://localhost:7891/api and use the standard envelope: { "ok": true, "data": {...} } on success, or { "ok": false, "error": { "code", "message" } } on failure. The daemon listens on localhost only.
Endpoint summary
Section titled “Endpoint summary”| Method | Path | Purpose |
|---|---|---|
GET | /api/agent/status | Is an LLM configured? List available models. |
GET | /api/agent/api-key | Get the configured key for a provider (masked). |
POST | /api/agent/api-key | Set the API key for a provider. |
DELETE | /api/agent/api-key | Remove the API key for a provider. |
GET | /api/agent/:profileId/history | Read stored chat messages for a profile. |
POST | /api/agent/:profileId/chat | Send a message → SSE stream of the agent’s run. |
POST | /api/agent/:profileId/stop | Abort the agent’s current execution. |
DELETE | /api/agent/:profileId | Clear history and destroy the session. |
The provider defaults to openai when the provider field or query parameter is omitted.
Configure an LLM key
Section titled “Configure an LLM key”Before the agent can run, it needs an LLM API key. Keys are stored per provider in the daemon’s local auth store, not in the profile.
Check status
Section titled “Check status”GET /api/agent/status tells you whether any provider key is configured and which models are available.
curl -s http://localhost:7891/api/agent/status | jq .data{ "configured": true, "models": [ { "provider": "openai", "id": "gpt-5", "name": "GPT-5" } ]}When no key is set, the daemon returns { "configured": false, "models": [] } — it never errors on this endpoint.
Set a key
Section titled “Set a key”POST /api/agent/api-key stores a key for a provider. apiKey is required; an empty or whitespace-only value returns 400 BAD_REQUEST.
curl -s http://localhost:7891/api/agent/api-key -X POST \ -H 'Content-Type: application/json' -d '{ "provider": "openai", "apiKey": "sk-..." }'{ "ok": true }Get a key (masked)
Section titled “Get a key (masked)”GET /api/agent/api-key returns the stored key for a provider, masked to its first and last four characters. Pass ?provider= to target a non-default provider.
curl -s "http://localhost:7891/api/agent/api-key?provider=openai" | jq .data{ "provider": "openai", "hasKey": true, "maskedKey": "sk-1…7f9c" }If no key is stored, hasKey is false and maskedKey is null.
Delete a key
Section titled “Delete a key”curl -s "http://localhost:7891/api/agent/api-key?provider=openai" -X DELETE{ "ok": true }Chat with the agent (SSE)
Section titled “Chat with the agent (SSE)”POST /api/agent/:profileId/chat sends one user message and streams the agent’s run back as Server-Sent Events. The profile must be running — if it isn’t, the daemon responds 404 with PROFILE_NOT_RUNNING. An empty message returns 400 BAD_REQUEST.
curl -N -s http://localhost:7891/api/agent/$PROFILE/chat -X POST \ -H 'Content-Type: application/json' -d '{ "message": "Go to example.com and tell me the page title" }'The -N flag disables curl buffering so you see events as they arrive. The response has Content-Type: text/event-stream; each event is a line of the form data: <json>\n\n.
SSE event shapes
Section titled “SSE event shapes”Each data: line carries a JSON object with a type discriminator. These are the events the daemon emits during a run:
type | Fields | Meaning |
|---|---|---|
tool_start | name, params | The agent began an automation tool call (e.g. navigate, click). |
tool_end | name, ok | That tool call finished; ok: false means it errored. |
text_delta | content | A chunk of the agent’s text reply — concatenate deltas in order. |
usage | input, output, total | Token totals, sent once after the run completes. |
done | — | The agent finished this turn. |
error | message | The run failed; the message is also stored in history. |
data: {"type":"tool_start","name":"navigate","params":{"url":"https://example.com"}}
data: {"type":"tool_end","name":"navigate","ok":true}
data: {"type":"tool_start","name":"snapshot","params":{}}
data: {"type":"tool_end","name":"snapshot","ok":true}
data: {"type":"text_delta","content":"The page title is "}
data: {"type":"text_delta","content":"\"Example Domain\"."}
data: {"type":"done"}
data: {"type":"usage","input":4821,"output":137,"total":4958}Stop a run
Section titled “Stop a run”To interrupt a long-running turn, call the stop endpoint with the same profile id. It returns whether an active run was aborted.
curl -s http://localhost:7891/api/agent/$PROFILE/stop -X POST | jq .data{ "aborted": true }History
Section titled “History”Each profile keeps its own chat history in the daemon. Messages are stored as you chat — user messages, the agent’s tool calls, agent text, and any errors.
Read history
Section titled “Read history”curl -s http://localhost:7891/api/agent/$PROFILE/history | jq .data{ "messages": [ { "type": "user", "content": "Go to example.com and tell me the page title" }, { "type": "tool", "content": "", "toolName": "navigate", "params": { "url": "https://example.com" } }, { "type": "agent", "content": "The page title is \"Example Domain\"." } ]}Message type is one of user, tool (carries toolName and params), agent, or error.
Clear history and end the session
Section titled “Clear history and end the session”DELETE /api/agent/:profileId clears the stored messages and destroys the in-memory agent session for that profile. The next chat starts fresh (and re-injects the skill).
curl -s http://localhost:7891/api/agent/$PROFILE -X DELETE{ "ok": true }Errors
Section titled “Errors”| Status | code | When |
|---|---|---|
400 | BAD_REQUEST | apiKey or message missing/empty. |
404 | PROFILE_NOT_RUNNING | The target profile isn’t launched. |
500 | AGENT_ERROR | The session couldn’t be created. |
500 | INTERNAL_ERROR | Storing or removing a key failed. |
Mid-stream failures during a chat aren’t HTTP errors — the connection has already returned 200, so a failure arrives as an error SSE event instead.
Related
Section titled “Related”- The local HTTP API — base paths, the envelope, and the localhost security model.
- Install the agent skill — run your own agent against the automation API instead.
- API reference — the full per-action automation catalog the agent drives.
- Autonomous mode — long-running agent sessions with a work duration.