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

# Agents API

> Create, deploy, and chat with AI agents across channels

# Agents API

Build AI-powered conversational and task agents. Create agents with custom tools and guardrails, deploy them to messaging and voice channels, run conversations programmatically, and access interaction history.

**Base path:** `/api/v1/agents`

***

## CRUD Operations

### Create Agent

`POST /api/v1/agents`

<ParamField body="name" type="string" required>
  Agent display name
</ParamField>

<ParamField body="type" type="string" default="custom">
  Agent type. One of `custom`, `chatbot`, `router`, `voice`, `workflow`. Optional — defaults to `custom` when omitted.
</ParamField>

<ParamField body="model" type="string" default="claude-sonnet-4-6">
  Anthropic Claude model id. Orbit is Anthropic-only. Supported values: `claude-opus-4-7`, `claude-sonnet-4-6`, `claude-haiku-4-5-20251001`, `claude-fable-5`. Note: `claude-fable-5` is temporarily unavailable (export-suspended); requests pinned to it fall back to `claude-opus-4-7`. This list can change as models are added or availability shifts — call `GET /api/v1/agents/models` for the live, authoritative set.

  Optional. When you omit `model`, the agent record is created with `claude-sonnet-4-6` stored as its model — this is the **create-time field default**, and it is the model that agent runs on.

  Do not confuse this with the **platform default model**, which the runtime resolves separately (admin pin → auto-discovery → boot fallback) for agents left on the platform default rather than pinned here. That chain can settle on a different id, so the model serving a platform-default agent need not match this field's default. See [Agent model selection](/agents/model-selection).
</ParamField>

<ParamField body="system_prompt" type="string">
  System instructions that define the agent's behavior and personality. Optional.
</ParamField>

<ParamField body="tools" type="object[]">
  Tool definitions the agent can invoke

  <Expandable>
    <ParamField body="tools[].name" type="string" required>Tool name</ParamField>
    <ParamField body="tools[].description" type="string">What the tool does</ParamField>
    <ParamField body="tools[].parameters" type="object">JSON Schema of input parameters</ParamField>
  </Expandable>
</ParamField>

<Note>
  Channel binding is **not** set at create time. `POST /api/v1/agents` does not
  accept a `channels` field — pass it and it is silently ignored. Bind an agent
  to channels when you activate it via [Deploy Agent](#deploy-agent).
</Note>

<ParamField body="safety_config" type="object">
  Guardrail configuration enforced by Orbit's agent runtime. There is no separate guardrails endpoint — send this on create or update.

  <Expandable>
    <ParamField body="safety_config.pii_redaction" type="boolean">Redact detected PII from agent output</ParamField>
    <ParamField body="safety_config.pii_detection" type="boolean">Flag inbound messages containing PII</ParamField>
    <ParamField body="safety_config.content_filter" type="boolean">Filter profane or unsafe content</ParamField>
    <ParamField body="safety_config.harmful_content" type="boolean">Block harmful-content responses</ParamField>
    <ParamField body="safety_config.prompt_injection" type="boolean">Screen inbound messages for prompt-injection attempts</ParamField>
    <ParamField body="safety_config.approval_required" type="boolean">Require human approval before responses are sent</ParamField>
    <ParamField body="safety_config.blocked_topics" type="string[]">Topics the agent must refuse to discuss</ParamField>
    <ParamField body="safety_config.content_policy" type="string">Free-text content policy appended to the system guardrails</ParamField>
    <ParamField body="safety_config.max_response_length" type="integer">Maximum characters per agent response</ParamField>
  </Expandable>
</ParamField>

<ParamField body="escalation_triggers" type="object[]">
  Conditions that hand a conversation off to a human. Each entry is one of `{ "type": "keyword", "value": "<string>" }`, `{ "type": "sentiment_below", "value": <0–1> }`, or `{ "type": "turn_count_above", "value": <positive integer> }`.
</ParamField>

<ParamField body="knowledge_base_ids" type="string[]">
  IDs of knowledge bases (Qdrant collections) to attach for RAG
</ParamField>

<ParamField body="temperature" type="number" default="0.7">
  LLM temperature (0.0–2.0)
</ParamField>

<ParamField body="max_tokens" type="integer" default="4096">
  Maximum tokens per LLM response
</ParamField>

<RequestExample>
  ```bash theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/agents \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Support Bot",
      "type": "chatbot",
      "model": "claude-sonnet-4-6",
      "system_prompt": "You are a helpful customer support agent for Acme Corp.",
      "tools": [
        {
          "name": "lookup_order",
          "description": "Look up order details by order ID",
          "parameters": {
            "type": "object",
            "properties": { "order_id": { "type": "string" } },
            "required": ["order_id"]
          }
        }
      ],
      "safety_config": {
        "pii_redaction": true,
        "content_filter": true,
        "blocked_topics": ["competitor pricing"]
      },
      "temperature": 0.5
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={null}
  {
    "data": {
      "id": "agt_abc123",
      "name": "Support Bot",
      "type": "chatbot",
      "model": "claude-sonnet-4-6",
      "status": "draft",
      "tools_count": 1,
      "created_at": "2026-03-08T12:00:00Z"
    },
    "meta": {
      "request_id": "req_agt_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

### List Agents

`GET /api/v1/agents`

Retrieve all agents with cursor-based pagination.

<ParamField query="cursor" type="string">Cursor for pagination</ParamField>
<ParamField query="limit" type="integer" default="25">Results per page (max 200)</ParamField>
<ParamField query="status" type="string">Filter by status: `draft`, `active`, `paused`, `archived`</ParamField>

### Get Agent

`GET /api/v1/agents/{id}`

Retrieve full configuration of an agent including tools, guardrails, and deployment status.

### Update Agent

`PUT /api/v1/agents/{id}`

Update an agent's configuration. Changes take effect for new conversations immediately.

### Delete Agent

`DELETE /api/v1/agents/{id}`

Delete an agent. Active conversations are terminated gracefully.

**Response:** `204 No Content`

***

## Deploy

### Deploy Agent

`POST /api/v1/agents/{id}/deploy`

Activate an agent and make it available on its configured channels. The agent must have a valid `system_prompt`.

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

<ParamField body="channel" type="string" required>
  The channel to deploy the agent to. One of `webhook`, `sms`, `whatsapp`, `voice`, `rcs`.
</ParamField>

<ParamField body="webhook_url" type="string">
  HTTPS callback URL for the `webhook` channel. Must be a valid HTTPS URL. An empty string is ignored and leaves any previously set URL unchanged.
</ParamField>

<ParamField body="phone_number" type="string">
  E.164 phone number to bind the agent to. **Required when `channel` is `voice`.**
</ParamField>

<RequestExample>
  ```bash theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/agents/agt_abc123/deploy \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "channel": "voice",
      "phone_number": "+14155550100"
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "agt_abc123",
      "status": "active",
      "config": {
        "deployment": {
          "channel": "voice",
          "phone_number": "+14155550100",
          "deployed_at": "2026-03-08T12:00:00Z"
        }
      }
    },
    "meta": {
      "request_id": "req_deploy_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

### Undeploy Agent

`POST /api/v1/agents/{id}/undeploy`

Remove an agent's channel deployment. This clears the `config.deployment`
metadata and sets the agent's `status` to `paused`. For a `voice` deployment
it also releases the bound phone number, so that number becomes available to
deploy to another agent.

No request body is required. To bring the agent back, call
[Deploy Agent](#deploy-agent) again — redeploying flips the status back to
`active` regardless of its previous state.

This is distinct from [pausing an agent](#pause--resume-agent): pausing stops
new conversations but keeps the channel binding, whereas undeploying removes
the binding entirely.

This endpoint is rate limited to 5 requests per minute per API key, the same
budget as [Deploy Agent](#deploy-agent).

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

<RequestExample>
  ```bash theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/agents/agt_abc123/undeploy \
    -H "X-API-Key: dv_live_sk_your_key_here"
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "agt_abc123",
      "status": "paused",
      "config": {}
    },
    "meta": {
      "request_id": "req_undeploy_001",
      "timestamp": "2026-03-08T12:05:00Z"
    }
  }
  ```
</ResponseExample>

### Pause / Resume Agent

There are no dedicated `pause` or `resume` endpoints. An agent's lifecycle is
controlled through its `status` field via [Update Agent](#update-agent).

To pause an agent, send `PUT /api/v1/agents/{id}` with `{ "status": "paused" }`.
Existing conversations are completed but no new ones are started.

To resume a paused agent, send `PUT /api/v1/agents/{id}` with `{ "status": "active" }`.

To fully remove an agent's channel deployment (rather than just pause it), use
[Undeploy Agent](#undeploy-agent) above.

Valid `status` values are `draft`, `active`, `paused`, and `archived`.

<RequestExample>
  ```bash theme={null}
  curl -X PUT https://api.orbit.devotel.io/api/v1/agents/agt_abc123 \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "status": "paused"
    }'
  ```
</RequestExample>

***

## Chat

### Send Chat Message

`POST /api/v1/agents/{id}/chat`

Send a message to an agent and receive a response. Supports both stateless single-turn and stateful multi-turn conversations.

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

<ParamField body="message" type="string" required>
  User message text
</ParamField>

<ParamField body="conversationId" type="string">
  Existing conversation ID to append onto, for multi-turn. Omit to start a new conversation.
</ParamField>

<ParamField body="history" type="object[]">
  Optional conversation history to seed the agent, additive on top of any persisted state. Each item is `{ "role": "user" | "assistant" | "system", "content": string }`. Use this to inject prior turns or context instead of a separate context field.
</ParamField>

<ParamField body="sandbox" type="boolean" default="false">
  Run the message in sandbox mode (does not persist the conversation).
</ParamField>

<ParamField body="contactId" type="string">
  Associate the conversation with a contact record.
</ParamField>

<ParamField body="channel" type="string">
  Channel the message originates from (e.g., `whatsapp`, `sms`, `web`).
</ParamField>

<Note>
  Streaming is a **separate endpoint** — `POST /api/v1/agents/{id}/chat/stream` — not a `stream: true` flag on this request. See [Streaming Responses](#streaming-responses) below.
</Note>

<RequestExample>
  ```bash theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/agents/agt_abc123/chat \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "message": "What is the status of order ORD-12345?",
      "history": [
        { "role": "user", "content": "Hi, my name is Jane Doe (customer cust_xyz)." },
        { "role": "assistant", "content": "Hi Jane! How can I help you today?" }
      ]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "conversation_id": "conv_abc123",
      "response": "Hi Jane! I found your order ORD-12345. It was shipped yesterday via FedEx and is expected to arrive by March 10th. The tracking number is FX-987654321.",
      "detected_language": null,
      "tool_calls": [
        {
          "tool": "lookup_order",
          "input": { "order_id": "ORD-12345" },
          "output": { "status": "shipped", "carrier": "FedEx", "eta": "2026-03-10" }
        }
      ],
      "tokens_used": 307,
      "latency_ms": 1840
    },
    "meta": {
      "request_id": "req_chat_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

### Streaming Responses

`POST /api/v1/agents/{id}/chat/stream`

Streaming uses a dedicated endpoint rather than a flag on the chat request. It accepts the same request body as `POST /api/v1/agents/{id}/chat` (`message`, `conversationId`, `history`, `sandbox`, `contactId`, `channel`) and returns the response as Server-Sent Events (SSE) with `Content-Type: text/event-stream`. The runtime emits six event types as the turn progresses, each carrying a single JSON `data` payload:

* `event: status` — a progress update while the turn runs (for example, retrieval or routing). `data` is `{ "status": "...", "detail": "..." }`; `detail` may be omitted.
* `event: token` — one incremental fragment of the assistant response. `data` is `{ "content": "..." }`. Concatenate every `content` value in order to reconstruct the streamed reply.
* `event: tool_start` — the agent invoked a tool. `data` is `{ "tool": "...", "input": { ... } }`.
* `event: tool_end` — a tool returned. `data` is `{ "tool": "...", "input": { ... }, "output": ... }`, where `output` is the tool result (a string or JSON value).
* `event: response` — the completed turn. `data` is `{ "response": "...", "tokensUsed": <number>, "promptTokensUsed": <number>, "completionTokensUsed": <number>, "apiCallsUsed": <number>, "escalated": <boolean>, "conversation_id": "..." }`. `tokensUsed` is the scalar total token count for the turn; `promptTokensUsed` and `completionTokensUsed` split it, and `conversation_id` is the id to reuse for follow-up turns.
* `event: error` — a terminal failure (upstream reset, timeout, provider error). `data` is `{ "code": "...", "message": "..." }`. Treat this as stream completion.

The `status`, `token`, `tool_start`, and `tool_end` events are progress frames and may arrive zero or more times in any order as the turn runs. The stream terminates with exactly one `response` (success) or `error` (failure) event, after which the connection closes.

```
event: status
data: {"status": "retrieving", "detail": "Looking up order history"}

event: tool_start
data: {"tool": "lookup_order", "input": {"order_id": "ORD-12345"}}

event: tool_end
data: {"tool": "lookup_order", "input": {"order_id": "ORD-12345"}, "output": {"status": "shipped", "carrier": "FedEx", "eta": "2026-03-10"}}

event: token
data: {"content": "Hi Jane! "}

event: token
data: {"content": "I found your order ORD-12345."}

event: response
data: {"response": "Hi Jane! I found your order ORD-12345. It was shipped yesterday via FedEx and is expected to arrive by March 10th.", "tokensUsed": 307, "promptTokensUsed": 210, "completionTokensUsed": 97, "apiCallsUsed": 1, "escalated": false, "conversation_id": "conv_abc123"}
```

On failure:

```
event: error
data: {"code": "UPSTREAM_RESET", "message": "Agent stream was interrupted. Please try again."}
```

### Legacy streaming (POST /{id}/stream)

`POST /api/v1/agents/{id}/stream`

A compatibility shim for SDK runtimes that cannot consume the preferred `POST /api/v1/agents/{id}/chat/stream` endpoint. It runs the same blocking turn as `POST /api/v1/agents/{id}/chat`, then replays the completed response over Server-Sent Events word by word, with a short gap between words, so a client gets a token-by-token render without the runtime producing true incremental tokens. Prefer `chat/stream` for new integrations; reach for this endpoint only when your client already expects a `token` / `done` SSE shape.

It accepts the same request body as `POST /api/v1/agents/{id}/chat` (`message`, `conversationId`, `history`, `sandbox`, `contactId`, `channel`) and returns `Content-Type: text/event-stream`. The endpoint is rate limited to 20 requests per minute per tenant, the same budget as `chat/stream`.

Three event types are emitted:

* `event: token` — one fragment of the response. `data` is `{ "text": "..." }`. The response is split on whitespace and each word and each run of whitespace is sent as its own `token` event, so concatenating every `text` value in order reconstructs the original response, spacing included.
* `event: done` — the turn completed. `data` is `{ "totalTokens": <number>, "costCents": <number>, "conversationId": "..." }`. Read `conversationId` to continue a multi-turn conversation.
* `event: error` — a terminal failure. `data` is `{ "code": "...", "message": "..." }`. No further events follow.

Every successful stream ends with a single `done` event after the last `token`; a failed stream ends with one `error` event instead. The connection then closes. If the client disconnects mid-stream, the server stops sending tokens.

```
event: token
data: {"text": "Hi"}

event: token
data: {"text": " "}

event: token
data: {"text": "Jane!"}

event: done
data: {"totalTokens": 307, "costCents": 4, "conversationId": "conv_abc123"}
```

On failure:

```
event: error
data: {"code": "STREAM_ERROR", "message": "Stream failed"}
```

***

## Conversations

### List Conversations

`GET /api/v1/agents/{id}/conversations`

Retrieve conversation history for an agent.

<ParamField query="cursor" type="string">Cursor for pagination</ParamField>
<ParamField query="limit" type="integer" default="25">Results per page (max 200)</ParamField>
<ParamField query="q" type="string">Search conversations by message content (case-insensitive substring match, max 200 characters)</ParamField>

### Get Conversation Messages

`GET /api/v1/agents/{id}/conversations/{conversationId}/messages`

Retrieve the full message history of a specific conversation.

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": [
      {
        "id": "amsg_001",
        "role": "user",
        "content": "What is the status of order ORD-12345?",
        "created_at": "2026-03-08T12:00:00Z"
      },
      {
        "id": "amsg_002",
        "role": "assistant",
        "content": "Hi Jane! Your order ORD-12345 was shipped yesterday via FedEx.",
        "tool_calls": [
          { "tool": "lookup_order", "input": { "order_id": "ORD-12345" } }
        ],
        "created_at": "2026-03-08T12:00:02Z"
      },
      {
        "id": "amsg_003",
        "role": "user",
        "content": "Great, thanks!",
        "created_at": "2026-03-08T12:00:15Z"
      }
    ],
    "meta": {
      "request_id": "req_msgs_001",
      "timestamp": "2026-03-08T12:01:00Z",
      "pagination": {
        "cursor": "cur_amsg_003",
        "has_more": false,
        "total": 3
      }
    }
  }
  ```
</ResponseExample>

***

## MCP Servers

Register your own [Model Context Protocol](https://modelcontextprotocol.io) (MCP) servers so an agent can discover and call tools you host, without Devotel adding a built-in integration for each one. At inference time the agent runtime fetches each enabled server, lists its tools, and adds them to the agent's tool catalog alongside built-in tools.

Servers are scoped to a single agent and to your organization. Management endpoints require the `owner`, `admin`, or `developer` role; listing a server's configuration is available to any authenticated member.

### Register MCP Server

`POST /api/v1/agents/{agentId}/mcp-servers`

<ParamField path="agentId" type="string" required>Agent ID the server is registered against.</ParamField>

<ParamField body="name" type="string" required>
  Display name for the server. Must be unique within the agent — registering a second server with the same name returns `409 MCP_SERVER_NAME_CONFLICT`.
</ParamField>

<ParamField body="server_url" type="string" required>
  The server's endpoint. Must be a public `https://` URL. URLs that resolve to private, loopback, link-local, or cloud-metadata addresses are rejected with `422 INVALID_MCP_SERVER_URL`.
</ParamField>

<ParamField body="server_type" type="string" required>
  Transport. Only `http_sse` is supported.
</ParamField>

<ParamField body="auth_credentials" type="string">
  A static bearer token or API key sent to your server, stored encrypted at rest. Mutually exclusive with `oauth2`.
</ParamField>

<ParamField body="oauth2" type="object">
  An OAuth2 grant the runtime exchanges for a live access token before calling your server. Mutually exclusive with `auth_credentials`. The interactive authorization-code (browser redirect) flow is not supported.

  <Expandable>
    <ParamField body="oauth2.grant_type" type="string" required>One of `client_credentials` or `refresh_token`.</ParamField>
    <ParamField body="oauth2.token_url" type="string" required>Token endpoint. Same `https://`-only, public-address validation as `server_url`.</ParamField>
    <ParamField body="oauth2.client_id" type="string" required>OAuth2 client id.</ParamField>
    <ParamField body="oauth2.client_secret" type="string" required>OAuth2 client secret (stored encrypted at rest).</ParamField>
    <ParamField body="oauth2.refresh_token" type="string">Refresh token. Required when `grant_type` is `refresh_token`.</ParamField>
    <ParamField body="oauth2.scope" type="string">Space-separated scopes to request.</ParamField>
  </Expandable>
</ParamField>

<ParamField body="tool_allowlist" type="string[]">
  Restrict which of the server's tools the agent can see and call. Omit to expose every discovered tool. Pass an array to expose only those tool names. Pass `[]` to expose none without removing the server.
</ParamField>

<ParamField body="metadata" type="object">
  Free-form key/value metadata stored with the server.
</ParamField>

<ParamField body="enabled" type="boolean" default="true">
  Whether the agent loads tools from this server.
</ParamField>

<RequestExample>
  ```bash theme={null}
  curl -X POST https://api.orbit.devotel.io/api/v1/agents/agt_abc123/mcp-servers \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "Acme Tools",
      "server_url": "https://mcp.acme.com/sse",
      "server_type": "http_sse",
      "auth_credentials": "secret-bearer-token",
      "tool_allowlist": ["lookup_order", "create_ticket"]
    }'
  ```
</RequestExample>

<ResponseExample>
  ```json 201 theme={null}
  {
    "data": {
      "id": "mcpsrv_abc123",
      "organization_id": "org_abc123",
      "agent_id": "agt_abc123",
      "name": "Acme Tools",
      "server_url": "https://mcp.acme.com/sse",
      "server_type": "http_sse",
      "metadata": { "tool_allowlist": ["lookup_order", "create_ticket"] },
      "enabled": true,
      "has_auth_credentials": true,
      "auth_type": "bearer",
      "tool_allowlist": ["lookup_order", "create_ticket"]
    },
    "meta": {
      "request_id": "req_mcp_001",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

<Note>
  Credentials are never returned over the wire. Responses expose only a
  `has_auth_credentials` boolean and `auth_type` (`bearer`, `oauth2`, or `none`).
</Note>

### List MCP Servers

`GET /api/v1/agents/{agentId}/mcp-servers`

<ParamField path="agentId" type="string" required>Agent ID.</ParamField>
<ParamField query="cursor" type="string">Cursor for the next page. Pass back the `meta.pagination.cursor` value from the previous response; omit for the first page.</ParamField>
<ParamField query="limit" type="integer" default="100">Results per page (max 200).</ParamField>

Returns the servers registered for the agent, newest first, with cursor-based pagination. Each entry carries `id`, `organization_id`, `agent_id`, `name`, `server_url`, `server_type`, `metadata`, `enabled`, `created_at`, `updated_at`, `has_auth_credentials`, and `tool_allowlist` (`null` when no allowlist is set). Use `meta.pagination.cursor` with `has_more: true` to fetch the next page.

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": [
      {
        "id": "mcpsrv_abc123",
        "organization_id": "org_abc123",
        "agent_id": "agt_abc123",
        "name": "Acme Tools",
        "server_url": "https://mcp.acme.com/sse",
        "server_type": "http_sse",
        "metadata": { "tool_allowlist": ["lookup_order", "create_ticket"] },
        "enabled": true,
        "created_at": "2026-03-08T12:00:00Z",
        "updated_at": "2026-03-08T12:00:00Z",
        "has_auth_credentials": true,
        "tool_allowlist": ["lookup_order", "create_ticket"]
      }
    ],
    "meta": {
      "request_id": "req_mcp_002",
      "timestamp": "2026-03-08T12:00:00Z",
      "pagination": {
        "cursor": "2026-03-08T12:00:00Z|mcpsrv_abc123",
        "has_more": false
      }
    }
  }
  ```
</ResponseExample>

### Update MCP Server

`PATCH /api/v1/agents/{agentId}/mcp-servers/{id}`

Change a registered server's tool allowlist and/or enabled state. Send at least one field.

<ParamField path="agentId" type="string" required>Agent ID.</ParamField>
<ParamField path="id" type="string" required>MCP server ID.</ParamField>

<ParamField body="tool_allowlist" type="string[] | null">
  An array replaces the allowlist (`[]` exposes no tools); `null` clears it so every discovered tool is exposed again; omit to leave it unchanged.
</ParamField>

<ParamField body="enabled" type="boolean">
  Toggle the server on or off without deleting it.
</ParamField>

<RequestExample>
  ```bash theme={null}
  curl -X PATCH https://api.orbit.devotel.io/api/v1/agents/agt_abc123/mcp-servers/mcpsrv_abc123 \
    -H "X-API-Key: dv_live_sk_your_key_here" \
    -H "Content-Type: application/json" \
    -d '{ "enabled": false }'
  ```
</RequestExample>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "mcpsrv_abc123",
      "agent_id": "agt_abc123",
      "metadata": { "tool_allowlist": ["lookup_order", "create_ticket"] },
      "enabled": false,
      "tool_allowlist": ["lookup_order", "create_ticket"]
    },
    "meta": {
      "request_id": "req_mcp_003",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

### Delete MCP Server

`DELETE /api/v1/agents/{agentId}/mcp-servers/{id}`

Remove a registered server. The call is idempotent: deleting an id that no longer exists returns `200` with `deleted: 0` rather than a 404, so retries are safe.

<ParamField path="agentId" type="string" required>Agent ID.</ParamField>
<ParamField path="id" type="string" required>MCP server ID.</ParamField>

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "deleted": 1,
      "id": "mcpsrv_abc123",
      "agent_id": "agt_abc123"
    },
    "meta": {
      "request_id": "req_mcp_004",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

### Probe MCP Server

`POST /api/v1/agents/{agentId}/mcp-servers/probe`

Test a candidate URL and credentials **before** registering. The probe runs the same `tools/list` request the runtime uses at discovery time, so a successful probe predicts the server will register cleanly. Nothing is persisted.

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

<ParamField body="server_url" type="string" required>Server endpoint to probe. Same `https://`-only, public-address validation as on register.</ParamField>
<ParamField body="server_type" type="string">Transport. `http_sse` if provided.</ParamField>
<ParamField body="auth_credentials" type="string">Static bearer token to test. Mutually exclusive with `oauth2`.</ParamField>
<ParamField body="oauth2" type="object">OAuth2 grant to test (same shape as on register). Mutually exclusive with `auth_credentials`.</ParamField>

The response reports the outcome. `status` is one of `ok`, `ssrf_blocked`, `timeout`, `unreachable`, `auth_failed`, `http_error`, or `protocol_error`. On success `tools` lists the discovered tool names; otherwise `error` carries a short, safe explanation.

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "ok": true,
      "status": "ok",
      "tool_count": 2,
      "tools": ["lookup_order", "create_ticket"],
      "http_status": 200,
      "error": null,
      "checked_at": "2026-03-08T12:00:00Z"
    },
    "meta": {
      "request_id": "req_mcp_005",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

### Test MCP Server

`POST /api/v1/agents/{agentId}/mcp-servers/{id}/test`

Health-check an already-registered server using its stored credentials, and save the result so the dashboard can show a health status without re-probing.

<ParamField path="agentId" type="string" required>Agent ID.</ParamField>
<ParamField path="id" type="string" required>MCP server ID.</ParamField>

The response uses the same verdict fields as the probe (`ok`, `status`, `tool_count`, `tools`, `http_status`, `error`, `checked_at`), plus the server `id` and `agent_id`.

<ResponseExample>
  ```json 200 theme={null}
  {
    "data": {
      "id": "mcpsrv_abc123",
      "agent_id": "agt_abc123",
      "ok": true,
      "status": "ok",
      "tool_count": 2,
      "tools": ["lookup_order", "create_ticket"],
      "http_status": 200,
      "error": null,
      "checked_at": "2026-03-08T12:00:00Z"
    },
    "meta": {
      "request_id": "req_mcp_006",
      "timestamp": "2026-03-08T12:00:00Z"
    }
  }
  ```
</ResponseExample>

***

## Agent Statuses

| Status     | Description                               |
| ---------- | ----------------------------------------- |
| `draft`    | Agent created but not yet deployed        |
| `active`   | Agent is live and accepting conversations |
| `paused`   | Agent is temporarily deactivated          |
| `archived` | Agent is retired and no longer deployable |

## Examples

### Node.js

```javascript theme={null}
const orbit = new Devotel({ apiKey: 'dv_live_sk_xxxx' })

const agent = await orbit.agents.create({
  name: 'Support Bot',
  type: 'chatbot',
  model: 'claude-sonnet-4-6',
  systemPrompt: 'You are a helpful support agent.',
})

// Channel binding happens at deploy time, not create time.
// Deploy targets one channel per call; repeat for each channel you need.
await orbit.agents.deploy(agent.data.id, {
  channel: 'whatsapp',
  phone_number: '+15551234567',
})

const response = await orbit.agents.chat(agent.data.id, {
  message: 'Hello, I need help with my order.',
})

console.log(response.data.content)
```

<Note>
  A first-party Python SDK is on the roadmap but not yet shipped. Call the REST endpoints above with `requests` / `httpx` / any HTTP client.
</Note>
