> ## 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.

# Creating Agents

> Step-by-step guide to building and deploying AI agents on Orbit

# Creating Agents

This guide walks you through creating, configuring, and deploying an AI agent on Orbit — from defining its behavior to connecting it to live channels.

## Step 1: Define the Agent

Create an agent with a name, instructions, and model selection.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Support Agent",
    "description": "Handles customer support inquiries for Acme Corp",
    "model": "claude-sonnet-4-6",
    "system_prompt": "You are a customer support agent for Acme Corp. Be helpful, concise, and professional. If you cannot resolve an issue, offer to transfer to a human agent.",
    "temperature": 0.3
  }'
```

## Step 2: Add Tools

Tools give your agent the ability to take actions — look up orders, create tickets, check inventory, etc.

There is no separate "add tool" endpoint. Tools are configured through the `tools` array on the agent's create or update body. Each entry is one of:

* A **string** referencing a built-in or registered tool by id (see [`GET /agents/tools`](#built-in-tools) for the catalog).
* A **function-call object** with `name`, `description`, and a JSON-Schema `parameters` object — the standard function-calling contract the model receives.

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/agents/agent_abc123 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "tools": [
      "search_knowledge",
      "transfer_agent",
      {
        "name": "lookup_order",
        "description": "Look up an order by order ID or customer email",
        "parameters": {
          "type": "object",
          "properties": {
            "order_id": { "type": "string", "description": "The order ID" },
            "email": { "type": "string", "description": "Customer email address" }
          }
        }
      }
    ]
  }'
```

The same `tools` array is accepted on `POST /api/v1/agents` at create time. A function-call entry tells the model the tool's shape but does not, by itself, perform an outbound HTTP call.

### Reusable HTTP tools (custom tools)

To register a reusable tool that calls an external HTTP endpoint, use the custom-tools surface. Custom tools are tenant-scoped, SSRF-validated, and can be referenced by name from any agent's `tools` array.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/custom-tools \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "lookup_order",
    "description": "Look up an order by order ID or customer email",
    "json_schema": {
      "type": "object",
      "properties": {
        "order_id": { "type": "string", "description": "The order ID" },
        "email": { "type": "string", "description": "Customer email address" }
      }
    },
    "executor_url": "https://api.acme.com/orders/search",
    "executor_secret": "whsec_...",
    "confirmation": "never"
  }'
```

Once registered, add the tool to an agent by its `name` in the `tools` array (e.g. `"tools": ["lookup_order"]`).

### Managing custom tools

Beyond create, the custom-tools surface is a full CRUD API rooted at `/api/v1/agents/custom-tools`. Every endpoint is tenant-scoped and requires the `owner`, `admin`, or `developer` role for writes.

A custom tool is returned in this shape. The plaintext `executor_secret` is never echoed back — `executor_secret_set` reports whether one is stored:

```json theme={null}
{
  "id": "tool_abc123",
  "name": "lookup_order",
  "description": "Look up an order by order ID or customer email",
  "json_schema": { "type": "object", "properties": { "...": {} } },
  "executor_url": "https://api.acme.com/orders/search",
  "executor_secret_set": true,
  "enabled": true,
  "timeout_ms": 30000,
  "confirmation": "never",
  "created_by_user_id": "user_xyz",
  "created_at": "2026-06-28T12:00:00.000Z",
  "updated_at": "2026-06-28T12:00:00.000Z"
}
```

**List all custom tools**

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/agents/custom-tools \
  -H "X-API-Key: dv_live_sk_..."
```

Returns `{ "tools": [...], "total": <count> }`.

**Get a single custom tool**

```bash theme={null}
curl https://api.orbit.devotel.io/api/v1/agents/custom-tools/tool_abc123 \
  -H "X-API-Key: dv_live_sk_..."
```

Returns `404 NOT_FOUND` if the tool does not exist in your tenant.

**Update a custom tool**

`PATCH` applies a partial update — send only the fields you want to change. Accepts `description`, `json_schema`, `executor_url`, `executor_secret`, `enabled`, `timeout_ms`, and `confirmation`. For `executor_secret`: send a new value to rotate it, send `null` to clear it, or omit it to leave it unchanged. A changed `executor_url` is re-validated against the SSRF rules before it is saved.

```bash theme={null}
curl -X PATCH https://api.orbit.devotel.io/api/v1/agents/custom-tools/tool_abc123 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Look up an order by ID, email, or phone",
    "enabled": false,
    "timeout_ms": 15000
  }'
```

**Delete a custom tool**

Deleting a tool also removes it from every agent that referenced it, so no agent is left pointing at a dead tool.

```bash theme={null}
curl -X DELETE https://api.orbit.devotel.io/api/v1/agents/custom-tools/tool_abc123 \
  -H "X-API-Key: dv_live_sk_..."
```

Returns `{ "id": "tool_abc123", "deleted": true }`.

**Test a custom tool (dry run)**

Fire a one-off request at the tool's `executor_url` with sample `args` to confirm it is wired up correctly. This never persists anything and forwards the live response back to you. Optionally override the URL, secret, or timeout to test an unsaved change without first updating the tool.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/custom-tools/tool_abc123/test \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "args": { "order_id": "ORD-12345" }
  }'
```

The response reports the outcome of the call to your executor:

```json theme={null}
{
  "ok": true,
  "status": 200,
  "duration_ms": 142,
  "response_body": { "order_id": "ORD-12345", "status": "shipped" },
  "error": null
}
```

When the call fails, `ok` is `false`, `error` carries a short reason (for example a timeout or an `HTTP 500` from your endpoint), and `status` is the HTTP status your executor returned (or `0` when no response was received).

### Built-in Tools

Orbit provides several built-in tools that agents can use out of the box. The
authoritative list is returned live by `GET /api/v1/agents/tools`:

| Tool ID            | Name                  | Description                      |
| ------------------ | --------------------- | -------------------------------- |
| `send_sms`         | Send SMS              | Send an SMS message via Devotel  |
| `send_whatsapp`    | Send WhatsApp         | Send a WhatsApp message          |
| `send_email`       | Send Email            | Compose and send email           |
| `lookup_contact`   | Look up contact       | Search contact directory         |
| `check_balance`    | Check balance         | Retrieve account balance         |
| `create_ticket`    | Create ticket         | Open a support ticket            |
| `search_knowledge` | Search knowledge base | Semantic search across documents |
| `transfer_agent`   | Transfer to human     | Escalate to live agent           |

Reference a built-in tool by its ID in the `tools` array when creating an agent
(for example, `"tools": ["search_knowledge"]`). To ground `search_knowledge` in
your own content, attach documents via `knowledge_base_ids` (see Step 3).

## Step 3: Connect Knowledge Base

Knowledge lives in a **knowledge base** (KB) — a reusable container of documents that you create once and then link to one or more agents. There is no per-agent upload endpoint; instead you create a KB, upload documents to it, and attach the KB to the agent by ID.

### 3a. Create a knowledge base

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/knowledge-bases \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Acme Support Docs",
    "description": "FAQs and policies for the support agent"
  }'
```

The response includes the new KB's `id` (e.g. `kb_xyz789`). Use it in the next two steps.

### 3b. Upload documents

Upload each document to the KB with a multipart request. The `file` part is required; `type` is optional and inferred from the file extension when omitted.

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/knowledge-bases/kb_xyz789/documents \
  -H "X-API-Key: dv_live_sk_..." \
  -F "file=@faq.pdf"
```

Orbit chunks, embeds, and indexes documents in Qdrant for fast retrieval. Embeddings are handled by Orbit's internal pipeline — you don't manage embedding API keys.

### 3c. Link the knowledge base to the agent

Attach one or more KBs to the agent with the `knowledge_base_ids` array on create or update. IDs are validated against your tenant — an unknown ID returns `422 INVALID_KNOWLEDGE_BASE_IDS`.

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/agents/agent_abc123 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "knowledge_base_ids": ["kb_xyz789"]
  }'
```

## Step 4: Configure Guardrails

Set safety boundaries for your agent's behavior. Guardrails are part of the agent
itself — there is no separate guardrails endpoint. Send `safety_config` and
`escalation_triggers` on the create (`POST /agents`) or update (`PUT /agents/:id`)
body.

```bash theme={null}
curl -X PUT https://api.orbit.devotel.io/api/v1/agents/agent_abc123 \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "safety_config": {
      "pii_redaction": true,
      "pii_detection": true,
      "content_filter": true,
      "harmful_content": true,
      "prompt_injection": true,
      "blocked_topics": ["competitor pricing", "internal processes"],
      "max_response_length": 2000,
      "approval_required": false
    },
    "escalation_triggers": [
      { "type": "keyword", "value": "speak to human" },
      { "type": "keyword", "value": "manager" },
      { "type": "keyword", "value": "complaint" },
      { "type": "turn_count_above", "value": 50 },
      { "type": "sentiment_below", "value": 0.3 }
    ]
  }'
```

`safety_config` accepts: `blocked_topics`, `max_response_length`, `content_policy`,
`pii_redaction`, `pii_detection`, `harmful_content`, `prompt_injection`,
`content_filter`, and `approval_required`. Each `escalation_triggers` entry is one
of `{ "type": "keyword", "value": "<string>" }`,
`{ "type": "sentiment_below", "value": <0–1> }`, or
`{ "type": "turn_count_above", "value": <positive integer> }` (replacing the old flat
`escalation_keywords` / `max_turns` fields).

## Step 5: Deploy

Deploying is **how you bind an agent to a channel** — there is no `channels` field on the agent itself, and channels are never assigned on the create or update body. Activate your agent to start handling live conversations. Each deploy call targets **one channel**, so call the endpoint once per channel you want to go live on.

The request body **requires** a `channel` field — one of `webhook`, `sms`, `whatsapp`, `voice`, or `rcs`. A bodyless `POST` is rejected with `422 Unprocessable Entity`. `phone_number` is **required when `channel` is `voice`** and optional for the other phone-backed channels (`sms`, `whatsapp`, `rcs`); supply `webhook_url` for the `webhook` channel.

Deploy to WhatsApp:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc123/deploy \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "whatsapp",
    "phone_number": "+18005551234"
  }'
```

Deploy to the `webhook` channel (provide `webhook_url` so Orbit can post agent events to your endpoint — it must be an `https://` URL):

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc123/deploy \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "channel": "webhook",
    "webhook_url": "https://api.acme.com/orbit/agent-events"
  }'
```

To go live on multiple channels (for example SMS, voice, and RCS), repeat the call once per channel:

```bash theme={null}
# SMS
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc123/deploy \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "channel": "sms", "phone_number": "+18005551234" }'

# Voice
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc123/deploy \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "channel": "voice", "phone_number": "+18005551234" }'

# RCS
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc123/deploy \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "channel": "rcs", "phone_number": "+18005551234" }'
```

## Testing

Test your agent before going live without sending real messages or incurring
channel/outbound costs. Use the **dry-run** endpoint to replay a scripted
scenario in sandbox mode — mutative tools are short-circuited and no live side
effects occur (note: the agent still calls the LLM, so model/token cost applies):

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc123/dry-run \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "scenario": {
      "turns": [
        { "user_msg": "Hi, I need help with order ORD-12345" }
      ]
    }
  }'
```

The dry-run response returns a turn-by-turn trace and (optionally) an assertion
verdict — supply `expected_tool_calls`, `must_contain`, or `must_not_contain`
inside `scenario` to gate the run.

For an interactive single-turn test against the live agent runtime, use the
**chat** endpoint instead. This is a real turn — it incurs LLM cost — but it is
not channel-routed, so no outbound message is sent:

```bash theme={null}
curl -X POST https://api.orbit.devotel.io/api/v1/agents/agent_abc123/chat \
  -H "X-API-Key: dv_live_sk_..." \
  -H "Content-Type: application/json" \
  -d '{
    "message": "Hi, I need help with order ORD-12345"
  }'
```
