> ## Documentation Index
> Fetch the complete documentation index at: https://docs.powabase.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Agents

> Create AI agents, assign tools and knowledge bases, configure MCP servers and hooks, and execute conversations.

Each agent wraps an LLM with a system prompt, tools, knowledge bases, and optional MCP servers. Agents use a ReAct loop to reason about user messages, call tools as needed, and generate streaming responses. Sessions maintain conversation history across turns.

## Common Patterns

Create an agent, assign tools and knowledge bases, then use the streaming endpoint for conversations. Pass session\_id to continue multi-turn conversations. For human-in-the-loop workflows, configure hooks and use the approve endpoint when approval\_requested events are received.

## Agent CRUD

### GET /api/agents

List all agents.

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(f"{BASE_URL}/api/agents", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/agents' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### POST /api/agents

Create a new agent.

<RequestExample>
  ```json Request theme={null}
  {
    "name": "My Agent",
    "model": "gpt-4o",
    "system_prompt": "You are a helpful assistant.",
    "settings": { "temperature": 0.7 }
  }
  ```

  The body honors `name` (required), `model`, `system_prompt`, and `settings` (object). Top-level fields like `temperature` are silently dropped; anything model-tuning related must be nested in `settings`. `model` is a LiteLLM model ID passed through unchanged (e.g. `gpt-4o`, `claude-sonnet-4-6`, `openrouter/<org>/<model>`); see [Bring your own LLM](/guides/byollm) for the per-provider format and key requirements.
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(f"{BASE_URL}/api/agents", headers=headers, json={"name": "My Agent", "model": "gpt-4o", "system_prompt": "You are helpful.", "settings": {"temperature": 0.7}})
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents`, { method: "POST", headers, body: JSON.stringify({ name: "My Agent", model: "gpt-4o", system_prompt: "You are helpful.", settings: { temperature: 0.7 } }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/agents' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"name": "My Agent", "model": "gpt-4o"}'
  ```
</CodeGroup>

### GET /api/agents/{id}

Get an agent by ID.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(f"{BASE_URL}/api/agents/{agent_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents/${agentId}`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/agents/{id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### PATCH /api/agents/{id}

Update an agent's name, model, system prompt, or settings.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.patch(f"{BASE_URL}/api/agents/{agent_id}", headers=headers, json={"settings": {"temperature": 0.5}})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}`, { method: "PATCH", headers, body: JSON.stringify({ settings: { temperature: 0.5 } }) });
  ```

  ```bash cURL theme={null}
  curl -X PATCH '{BASE_URL}/api/agents/{id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"settings": {"temperature": 0.5}}'
  ```
</CodeGroup>

### DELETE /api/agents/{id}

Delete an agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.delete(f"{BASE_URL}/api/agents/{agent_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}`, { method: "DELETE", headers });
  ```

  ```bash cURL theme={null}
  curl -X DELETE '{BASE_URL}/api/agents/{id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

## Tool Assignments

### POST /api/agents/{id}/tools

Assign a tool to the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<RequestExample>
  ```json Request theme={null}
  { "tool_name": "database_query" }
  ```
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(f"{BASE_URL}/api/agents/{agent_id}/tools", headers=headers, json={"tool_name": "database_query"})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/tools`, { method: "POST", headers, body: JSON.stringify({ tool_name: "database_query" }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/agents/{id}/tools' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"tool_name": "database_query"}'
  ```
</CodeGroup>

### GET /api/agents/{id}/tools

List tool assignments for the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(f"{BASE_URL}/api/agents/{agent_id}/tools", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents/${agentId}/tools`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/agents/{id}/tools' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### PATCH /api/agents/{id}/tools/{assignment_id}

Update a tool assignment's `config_override`.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<ParamField path="assignment_id" type="string" required>
  Assignment ID
</ParamField>

<ParamField body="config_override" type="object" required>
  The full `config_override` object. Tool-specific shape; for database tools include `schemas: { <schema_name>: [<table_name>, ...] }`.
</ParamField>

<Note>
  The body MUST use the key `config_override`. Passing `config` (or any other top-level key) is a silent no-op: the route returns 200 with the unchanged assignment because the handler only inspects `config_override`.

  **How `config_override` works per tool type:**

  * **Database tools (`database_query`, `database_write`)**: `config_override.schemas: { <schema>: [<table>, ...] }` controls which tables the agent can read/write. Schemas must be a dict, schema names must match `^[a-zA-Z_][a-zA-Z0-9_]{0,63}$`, system schemas (`ai`, `auth`, `storage`, `pg_*`, etc.) are rejected, and each value must be a list of valid table names.

  * **All other builtin tools (`web_search`, `web_scrape`, `http_request`, `code_execute`, `storage_read`, `storage_write`)**: any key in `config_override` whose name matches a parameter in the tool's `input_schema` is **force-injected into every call's arguments**. Keys not in the schema are silently dropped (no error, no warning). For example, setting `config_override: { "max_results": 3, "include_domains": ["example.com"] }` on a `web_search` assignment locks every search the agent does to 3 results from `example.com`.

    This constrains a tool without changing what the agent is told about it: the LLM still sees the full schema, but its requested values for those keys are overridden.

  * **Custom tools**: `config_override` is not used. Custom-tool behavior is controlled via the `Tool` row's own `config` (endpoint, method, headers) at create time.

  * **MCP tools**: `config_override` is not used. MCP-tool behavior is controlled via the `AgentMcpServer` row's `headers` and `enabled` flag.
</Note>

<RequestExample>
  ```json Request theme={null}
  {
    "config_override": {
      "schemas": {
        "public": ["users", "orders"]
      }
    }
  }
  ```
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  response = requests.patch(f"{BASE_URL}/api/agents/{agent_id}/tools/{assignment_id}", headers=headers, json={"config_override": {"schemas": {"public": ["users", "orders"]}}})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/tools/${assignmentId}`, { method: "PATCH", headers, body: JSON.stringify({ config_override: { schemas: { public: ["users", "orders"] } } }) });
  ```

  ```bash cURL theme={null}
  curl -X PATCH '{BASE_URL}/api/agents/{id}/tools/{assignment_id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"config_override": {"schemas": {"public": ["users", "orders"]}}}'
  ```
</CodeGroup>

### DELETE /api/agents/{id}/tools/{assignment_id}

Remove a tool assignment from the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<ParamField path="assignment_id" type="string" required>
  Assignment ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.delete(f"{BASE_URL}/api/agents/{agent_id}/tools/{assignment_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/tools/${assignmentId}`, { method: "DELETE", headers });
  ```

  ```bash cURL theme={null}
  curl -X DELETE '{BASE_URL}/api/agents/{id}/tools/{assignment_id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

## Knowledge Base Assignments

### POST /api/agents/{id}/knowledge-bases

Link a knowledge base to the agent. Creates a dynamic search tool.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<RequestExample>
  ```json Request theme={null}
  { "knowledge_base_id": "kb-uuid" }
  ```
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(f"{BASE_URL}/api/agents/{agent_id}/knowledge-bases", headers=headers, json={"knowledge_base_id": kb_id})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/knowledge-bases`, { method: "POST", headers, body: JSON.stringify({ knowledge_base_id: kbId }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/agents/{id}/knowledge-bases' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"knowledge_base_id": "kb-uuid"}'
  ```
</CodeGroup>

### GET /api/agents/{id}/knowledge-bases

List knowledge base assignments for the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(f"{BASE_URL}/api/agents/{agent_id}/knowledge-bases", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents/${agentId}/knowledge-bases`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/agents/{id}/knowledge-bases' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### DELETE /api/agents/{id}/knowledge-bases/{assignment_id}

Remove a knowledge base from the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<ParamField path="assignment_id" type="string" required>
  Assignment ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.delete(f"{BASE_URL}/api/agents/{agent_id}/knowledge-bases/{assignment_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/knowledge-bases/${assignmentId}`, { method: "DELETE", headers });
  ```

  ```bash cURL theme={null}
  curl -X DELETE '{BASE_URL}/api/agents/{id}/knowledge-bases/{assignment_id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

## MCP Servers

### POST /api/agents/{id}/mcp-servers

Add an MCP server to the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<RequestExample>
  ```json Request theme={null}
  {
    "url": "https://mcp.example.com",
    "transport": "http",
    "name": "My MCP"
  }
  ```
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(f"{BASE_URL}/api/agents/{agent_id}/mcp-servers", headers=headers, json={"url": "https://mcp.example.com", "transport": "http", "name": "My MCP"})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/mcp-servers`, { method: "POST", headers, body: JSON.stringify({ url: "https://mcp.example.com", transport: "http", name: "My MCP" }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/agents/{id}/mcp-servers' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"url": "https://mcp.example.com", "transport": "http"}'
  ```
</CodeGroup>

### GET /api/agents/{id}/mcp-servers

List MCP servers for the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(f"{BASE_URL}/api/agents/{agent_id}/mcp-servers", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents/${agentId}/mcp-servers`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/agents/{id}/mcp-servers' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### PUT /api/agents/{id}/mcp-servers/{server_id}

Update an MCP server configuration.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<ParamField path="server_id" type="string" required>
  MCP Server ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.put(f"{BASE_URL}/api/agents/{agent_id}/mcp-servers/{server_id}", headers=headers, json={"url": "https://new-url.com"})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/mcp-servers/${serverId}`, { method: "PUT", headers, body: JSON.stringify({ url: "https://new-url.com" }) });
  ```

  ```bash cURL theme={null}
  curl -X PUT '{BASE_URL}/api/agents/{id}/mcp-servers/{server_id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"url": "https://new-url.com"}'
  ```
</CodeGroup>

### DELETE /api/agents/{id}/mcp-servers/{server_id}

Remove an MCP server from the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<ParamField path="server_id" type="string" required>
  MCP Server ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.delete(f"{BASE_URL}/api/agents/{agent_id}/mcp-servers/{server_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/mcp-servers/${serverId}`, { method: "DELETE", headers });
  ```

  ```bash cURL theme={null}
  curl -X DELETE '{BASE_URL}/api/agents/{id}/mcp-servers/{server_id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

## Hooks

Intercept agent execution at lifecycle boundaries. For the full event/type semantics, the webhook request/response contract, and the streaming caveat, see [Hooks & Middleware](/concepts/agents-tools#hooks--middleware).

### POST /api/agents/{id}/hooks

Add a hook to the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<ParamField body="event" type="string" required>
  When the hook fires. One of: `OnRunStart`, `PreToolUse`, `OnDelegation`, `PostToolUse`, `PreResponse`, `OnRunComplete`. Stored verbatim and **not** validated; an unrecognized value is saved but never fires.
</ParamField>

<ParamField body="type" type="string" required>
  What the hook does. One of: `http` (POST to a webhook URL), `rule` (local condition evaluation), `approval` (human-in-the-loop gate). Also stored verbatim and not validated.
</ParamField>

<ParamField body="config" type="object" required>
  Type-specific configuration (may be `{}`). For `http`: `url` (required), optional `headers`, `timeout_seconds` (default 5). For `approval`: optional `message`, `timeout` (default 300). For `rule`: a `condition` string or a `rules` array.
</ParamField>

<ParamField body="matcher" type="string">
  Tool name to target. Omit to match all tools (or for non-tool events).
</ParamField>

<ParamField body="enabled" type="boolean" default="true">
  Whether the hook is active.
</ParamField>

<ParamField body="position" type="integer" default="0">
  Execution order among the agent's hooks (ascending).
</ParamField>

<Note>
  Hooks are immutable; there is no update endpoint. To change a hook, `DELETE` it and create a new one.
</Note>

<CodeGroup>
  ```python Python theme={null}
  # An HTTP hook that POSTs to your webhook before every tool call
  response = requests.post(f"{BASE_URL}/api/agents/{agent_id}/hooks", headers=headers, json={"event": "PreToolUse", "type": "http", "config": {"url": "https://example.com/hook"}})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/hooks`, { method: "POST", headers, body: JSON.stringify({ event: "PreToolUse", type: "http", config: { url: "https://example.com/hook" } }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/agents/{id}/hooks' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"event": "PreToolUse", "type": "http", "config": {"url": "https://example.com/hook"}}'
  ```
</CodeGroup>

<ResponseExample>
  ```json 201 theme={null}
  {
    "id": "hook_...",
    "agent_id": "agent_...",
    "event": "PreToolUse",
    "matcher": null,
    "type": "http",
    "config": { "url": "https://example.com/hook" },
    "enabled": true,
    "position": 0,
    "created_at": "2026-01-01T00:00:00Z"
  }
  ```
</ResponseExample>

### GET /api/agents/{id}/hooks

List hooks for the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(f"{BASE_URL}/api/agents/{agent_id}/hooks", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents/${agentId}/hooks`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/agents/{id}/hooks' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### DELETE /api/agents/{id}/hooks/{hook_id}

Remove a hook from the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<ParamField path="hook_id" type="string" required>
  Hook ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.delete(f"{BASE_URL}/api/agents/{agent_id}/hooks/{hook_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/${agentId}/hooks/${hookId}`, { method: "DELETE", headers });
  ```

  ```bash cURL theme={null}
  curl -X DELETE '{BASE_URL}/api/agents/{id}/hooks/{hook_id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

## Execution

### GET /api/agents/{id}/sessions

List chat sessions for the agent.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(f"{BASE_URL}/api/agents/{agent_id}/sessions", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents/${agentId}/sessions`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/agents/{id}/sessions' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### POST /api/agents/{id}/run

Run agent synchronously, returning the full response as JSON. No streaming, no tools, no ReAct loop: a single LLM call against the conversation context. Useful for question-answering. If you need tool use, use `/run/stream`.

The endpoint accepts the **same context-selection surface** as `/run/stream`: you can attach ad-hoc retrieval (`knowledge_bases`), a pre-built context (`context_handler_id`), a raw context string (`context_override`), or by-reference items (`context_items`). Only **one** context source may be provided per request; sending more than one returns 400.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<RequestExample>
  ```json Request theme={null}
  {
    "message": "Hello",
    "session_id": "optional-session-uuid",
    "knowledge_bases": [{ "id": "kb-uuid", "top_k": 5 }],
    "max_context_tokens": 8000,
    "citations_enabled": false
  }
  ```
</RequestExample>

| Body field           | Type              | Notes                                                                                             |
| -------------------- | ----------------- | ------------------------------------------------------------------------------------------------- |
| `message`            | string (required) | User input                                                                                        |
| `session_id`         | string            | Reuse a session for multi-turn. Omit to start a fresh session; `session_id` is returned.          |
| `knowledge_bases`    | array             | Ad-hoc retrieval: `[{ id, top_k?, similarity_threshold?, filter_metadata?, retrieval_method? }]`  |
| `context_handler_id` | string            | Use a pre-built context handler (see [Context Handlers](/api-reference/context-handlers))         |
| `context_override`   | string            | Raw context string injected verbatim; skips retrieval                                             |
| `context_items`      | array             | By-reference items: `[{ id, ... }]` or by-value items with `text`                                 |
| `max_context_tokens` | int               | Truncate retrieved context to this token budget (default from agent settings or platform default) |
| `citations_enabled`  | bool              | Append a citation instruction and parse `[1]`-style refs from the response (default `false`)      |
| `temperature`        | float             | Per-run override; falls back to agent setting                                                     |
| `response_format`    | object            | LiteLLM-compatible JSON-schema for structured output                                              |

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(f"{BASE_URL}/api/agents/{agent_id}/run", headers=headers, json={"message": "Hello"})
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents/${agentId}/run`, { method: "POST", headers, body: JSON.stringify({ message: "Hello" }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/agents/{id}/run' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"message": "Hello"}'
  ```
</CodeGroup>

### POST /api/agents/{id}/run/stream

Run agent with streaming SSE. Supports tools, the ReAct loop, and multi-turn via session\_id. SSE event types include start, chunk, tool\_call, tool\_result, reasoning, reasoning\_summary, and complete; the reasoning events surface the model's internal thought stream when reasoning is enabled. Set reasoning\_requested=true to request reasoning output for this run.

The streaming endpoint accepts the **same context-selection surface** as `/run` above (`knowledge_bases` / `context_handler_id` / `context_override` / `context_items`, mutually exclusive), plus all the same `max_context_tokens`, `citations_enabled`, `temperature`, and `response_format` fields. See [Streaming](/concepts/streaming-patterns) for the full SSE event catalog.

<ParamField path="id" type="string" required>
  Agent ID
</ParamField>

<RequestExample>
  ```json Request theme={null}
  {
    "message": "Hello",
    "session_id": "optional-session-uuid",
    "reasoning_requested": false,
    "knowledge_bases": [{ "id": "kb-uuid", "top_k": 5 }]
  }
  ```
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(
      f"{BASE_URL}/api/agents/{agent_id}/run/stream",
      headers=headers,
      json={"message": "Hello", "reasoning_requested": True},
      stream=True,
  )
  for line in response.iter_lines():
      if line and line.decode().startswith("data: "):
          event = json.loads(line.decode()[6:])
          print(event)
  ```

  ```typescript TypeScript theme={null}
  const response = await fetch(`${BASE_URL}/api/agents/${agentId}/run/stream`, {
    method: "POST", headers,
    body: JSON.stringify({ message: "Hello", reasoning_requested: true }),
  });
  // Parse SSE events: start, chunk, tool_call, tool_result, reasoning, complete
  ```

  ```bash cURL theme={null}
  curl -N -X POST '{BASE_URL}/api/agents/{id}/run/stream' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"message": "Hello", "reasoning_requested": true}'
  ```
</CodeGroup>

### GET /api/agents/runs/{run_id}

Fetch a single agent run by `run_id`, independent of session context. Returns: `id`, `run_id`, `session_id`, `parent_orchestration_run_id`, `parent_workflow_execution_id`, `status`, `input_messages`, `output_messages`, `content`, `usage`, `retrieved_context`, `error`, `started_at`, `completed_at`, `steps`, `events`, `tool_calls`, `reasoning_steps`, and `created_at`. The parent fields are `null` for top-level runs; `session_id` is `null` for delegated or workflow-block runs.

For runs that belong to a session, ownership is enforced: the caller must own the session (or be using a service-role key). To avoid leaking existence, ownership failures are returned as 404, not 403.

<ParamField path="run_id" type="string" required>Run ID</ParamField>

<CodeGroup>
  ```python Python theme={null}
  response = requests.get(f"{BASE_URL}/api/agents/runs/{run_id}", headers=headers)
  ```

  ```typescript TypeScript theme={null}
  const res = await fetch(`${BASE_URL}/api/agents/runs/${runId}`, { headers });
  ```

  ```bash cURL theme={null}
  curl '{BASE_URL}/api/agents/runs/{run_id}' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}"
  ```
</CodeGroup>

### POST /api/agents/runs/{run_id}/approve

Approve or deny a pending tool call (human-in-the-loop).

<ParamField path="run_id" type="string" required>
  Run ID
</ParamField>

<RequestExample>
  ```json Request theme={null}
  { "approved": true }
  ```
</RequestExample>

<CodeGroup>
  ```python Python theme={null}
  response = requests.post(f"{BASE_URL}/api/agents/runs/{run_id}/approve", headers=headers, json={"approved": True})
  ```

  ```typescript TypeScript theme={null}
  await fetch(`${BASE_URL}/api/agents/runs/${runId}/approve`, { method: "POST", headers, body: JSON.stringify({ approved: true }) });
  ```

  ```bash cURL theme={null}
  curl -X POST '{BASE_URL}/api/agents/runs/{run_id}/approve' -H "apikey: {API_KEY}" -H "Authorization: Bearer {API_KEY}" -H "Content-Type: application/json" -d '{"approved": true}'
  ```
</CodeGroup>

## Error Responses

Agent routes return `{"error": "<message>"}`.

| Status | Description                                                                                                                                          |
| ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------- |
| 400    | Missing or invalid required field (e.g. `name`, `tool_type`/`tool_name`, `knowledge_base_id`, MCP server `name`/`url`, hook `event`/`type`/`config`) |
| 400    | Invalid `config_override.schemas` on tool PATCH (e.g. not a dict, contains a system schema, or has invalid table names)                              |
| 404    | No agent, tool/KB/MCP/hook assignment, or run exists with the given ID                                                                               |
| 404    | `/run` or `/run/stream`: the supplied `session_id` is not owned by the caller (returned as 404 to avoid leaking existence)                           |
| 409    | Knowledge base already assigned to this agent                                                                                                        |
| 409    | An MCP server with this name already exists for this agent                                                                                           |
