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

# Custom Functions

> Give your voice agents the ability to call external APIs during conversations

# Custom Functions

Custom functions let your voice agents call external HTTP endpoints mid-conversation. When the agent determines it needs to perform an action — check the weather, book an appointment, look up an order — it invokes a function you've defined, waits for the response, and continues the conversation with that data.

## How it works

1. You define a function with a **name**, **description**, **URL**, and **input/output schema**
2. You attach it to an agent
3. During a voice call, the agent's LLM reads the description and decides when to call it
4. The bot sends a `POST` request to your URL with the input parameters
5. Your endpoint responds with data
6. The agent uses that data to continue the conversation

```
User: "What's the weather in Mumbai?"
    ↓
Agent LLM decides to call `get_weather` function
    ↓
Bot says: "Checking the weather..." (random started_message)
    ↓
POST https://your-api.com/weather
Body: { "city": "Mumbai" }
    ↓
Response: { "temperature": 32, "condition": "Sunny" }
    ↓
Agent: "It's currently 32 degrees and sunny in Mumbai!"
```

***

## Create a function

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.vocobase.com/api/v2/functions \
    -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
    -H "Content-Type: application/json" \
    -d '{
      "name": "get_weather",
      "description": "Retrieve current weather for a city. Call this when the user asks about weather conditions.",
      "url": "https://api.weather.com/v1/current",
      "headers": [
        { "key": "Authorization", "value": "Bearer weather_api_key_here" }
      ],
      "input_schema": [
        {
          "name": "city",
          "type": "string",
          "description": "City name",
          "required": true
        }
      ],
      "output_schema": [
        {
          "name": "temperature",
          "type": "number",
          "description": "Temperature in Celsius",
          "required": true
        },
        {
          "name": "condition",
          "type": "string",
          "description": "Weather condition (e.g., Sunny, Rainy)",
          "required": true
        }
      ],
      "timeout": 10,
      "started_messages": ["Checking the weather...", "Let me look that up..."],
      "error_message": "Sorry, I could not get the weather right now."
    }'
  ```

  ```javascript JavaScript theme={null}
  const response = await fetch("https://api.vocobase.com/api/v2/functions", {
    method: "POST",
    headers: {
      "Authorization": "Bearer rg_live_abc123def456ghi789jkl012",
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      name: "get_weather",
      description: "Retrieve current weather for a city. Call this when the user asks about weather conditions.",
      url: "https://api.weather.com/v1/current",
      headers: [
        { key: "Authorization", value: "Bearer weather_api_key_here" }
      ],
      input_schema: [
        { name: "city", type: "string", description: "City name", required: true }
      ],
      output_schema: [
        { name: "temperature", type: "number", description: "Temperature in Celsius", required: true },
        { name: "condition", type: "string", description: "Weather condition", required: true }
      ],
      timeout: 10,
      started_messages: ["Checking the weather...", "Let me look that up..."],
      error_message: "Sorry, I could not get the weather right now.",
    }),
  });
  const { data: func } = await response.json();
  console.log(func.id); // Save this function ID
  ```

  ```python Python theme={null}
  import requests

  response = requests.post(
      "https://api.vocobase.com/api/v2/functions",
      headers={
          "Authorization": "Bearer rg_live_abc123def456ghi789jkl012",
          "Content-Type": "application/json",
      },
      json={
          "name": "get_weather",
          "description": "Retrieve current weather for a city. Call this when the user asks about weather conditions.",
          "url": "https://api.weather.com/v1/current",
          "headers": [
              {"key": "Authorization", "value": "Bearer weather_api_key_here"}
          ],
          "input_schema": [
              {"name": "city", "type": "string", "description": "City name", "required": True}
          ],
          "output_schema": [
              {"name": "temperature", "type": "number", "description": "Temperature in Celsius", "required": True},
              {"name": "condition", "type": "string", "description": "Weather condition", "required": True}
          ],
          "timeout": 10,
          "started_messages": ["Checking the weather...", "Let me look that up..."],
          "error_message": "Sorry, I could not get the weather right now.",
      },
  )
  data = response.json()["data"]
  ```
</CodeGroup>

```json Response theme={null}
{
  "success": true,
  "data": {
    "id": "f1234567-abcd-1234-abcd-123456789012",
    "name": "get_weather",
    "description": "Retrieve current weather for a city...",
    "method": "POST",
    "url": "https://api.weather.com/v1/current",
    "header_keys": ["Authorization"],
    "input_schema": [...],
    "output_schema": [...],
    "timeout": 10,
    "started_messages": ["Checking the weather...", "Let me look that up..."],
    "error_message": "Sorry, I could not get the weather right now.",
    "created_at": "2026-04-16T10:30:00Z",
    "updated_at": "2026-04-16T10:30:00Z"
  }
}
```

<Note>
  Header **values** are encrypted at rest and never returned by the API. Only `header_keys` (the key names) appear in responses.
</Note>

***

## Function fields

| Field              | Type   | Required | Description                                                                                                          |
| ------------------ | ------ | -------- | -------------------------------------------------------------------------------------------------------------------- |
| `name`             | string | Yes      | Unique identifier. 1–50 chars, `snake_case` only (lowercase, numbers, underscores). Must start with a letter.        |
| `description`      | string | Yes      | 1–500 chars. **This is critical** — the LLM reads this to decide when to call the function. Be specific.             |
| `url`              | string | Yes      | HTTPS endpoint the bot will `POST` to. 1–2000 chars. `localhost` and private IPs are blocked in production.          |
| `headers`          | array  | No       | Up to 10 headers. Each: `{ key: string, value: string }`. Values are encrypted at rest; only key names are returned. |
| `input_schema`     | array  | No       | Up to 100 fields. Describes what the bot sends in the request body. See [Schema fields](#schema-fields).             |
| `output_schema`    | array  | No       | Up to 100 fields. Describes the expected response structure. Helps the LLM interpret your API response.              |
| `timeout`          | number | No       | 1–60 seconds. Default: 30. How long the bot waits for your endpoint to respond.                                      |
| `started_messages` | array  | No       | Up to 5 strings (max 200 chars each). Bot picks one at random to say while the function executes.                    |
| `error_message`    | string | No       | Max 500 chars. What the bot says if the function fails.                                                              |

### Schema fields

Each entry in `input_schema` or `output_schema`:

```json theme={null}
{
  "name": "city",
  "type": "string",
  "description": "City name to look up",
  "required": true
}
```

| Field         | Type    | Description                                                               |
| ------------- | ------- | ------------------------------------------------------------------------- |
| `name`        | string  | Field name                                                                |
| `type`        | string  | `string`, `number`, `boolean`, `string[]`, `number[]`, or `custom`        |
| `description` | string  | Helps the LLM understand what to pass or expect                           |
| `required`    | boolean | Whether the field is mandatory                                            |
| `jsonSchema`  | object  | Required when `type` is `custom`. A JSON Schema object for complex types. |

<Warning>
  The `description` field on the function is the most important part. The LLM uses it to decide **when** to call the function. Be explicit: "Call this when the user asks about order status" is better than "Get order info".
</Warning>

***

## Reserved function names

These names are used by built-in integrations and cannot be used:

`knowledge_base_search`, `calendar_create_event`, `calendar_list_events`, `calendar_update_event`, `calendar_delete_event`, `gmail_send_email`, `gmail_search_emails`, `slack_send_message`, `slack_list_channels`, `add_to_cart`, `view_cart`, `clear_cart`, `checkout`

***

## Attach a function to an agent

After creating a function, attach it to one or more agents:

<CodeGroup>
  ```bash cURL theme={null}
  curl -X POST https://api.vocobase.com/api/v2/agent/AGENT_ID/functions/FUNCTION_ID \
    -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
  ```

  ```javascript JavaScript theme={null}
  await fetch(
    `https://api.vocobase.com/api/v2/agent/${agentId}/functions/${functionId}`,
    {
      method: "POST",
      headers: { "Authorization": "Bearer rg_live_abc123def456ghi789jkl012" },
    }
  );
  ```

  ```python Python theme={null}
  requests.post(
      f"https://api.vocobase.com/api/v2/agent/{agent_id}/functions/{function_id}",
      headers={"Authorization": "Bearer rg_live_abc123def456ghi789jkl012"},
  )
  ```
</CodeGroup>

```json Response theme={null}
{
  "success": true,
  "data": { "attached": true }
}
```

### Enable or disable a function for an agent

Functions are enabled by default when attached. Toggle with `enabled`:

```bash cURL theme={null}
curl -X PATCH https://api.vocobase.com/api/v2/agent/AGENT_ID/functions/FUNCTION_ID \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{ "enabled": false }'
```

```json Response theme={null}
{
  "success": true,
  "data": { "enabled": false }
}
```

### Detach a function from an agent

```bash cURL theme={null}
curl -X DELETE https://api.vocobase.com/api/v2/agent/AGENT_ID/functions/FUNCTION_ID \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

Returns `204 No Content` on success.

***

## List functions on an agent

See all functions currently active on an agent:

```bash cURL theme={null}
curl -X GET https://api.vocobase.com/api/v2/agent/AGENT_ID/functions \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

```json Response theme={null}
{
  "success": true,
  "data": {
    "functions": [
      {
        "id": "f1234567-abcd-1234-abcd-123456789012",
        "name": "get_weather",
        "description": "Get weather for a city",
        "method": "POST",
        "url": "https://api.weather.com/v1/current",
        "header_keys": ["Authorization"],
        "input_schema": [...],
        "output_schema": [...],
        "timeout": 10,
        "started_messages": ["Checking the weather..."],
        "error_message": null,
        "enabled": true,
        "created_at": "2026-04-16T10:30:00Z",
        "updated_at": "2026-04-16T10:30:00Z"
      }
    ]
  }
}
```

***

## Manage functions

### List all your functions

```bash cURL theme={null}
curl -X GET https://api.vocobase.com/api/v2/functions \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

### Get a single function

```bash cURL theme={null}
curl -X GET https://api.vocobase.com/api/v2/functions/FUNCTION_ID \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

### Update a function

All fields are optional — only provided fields are updated.

```bash cURL theme={null}
curl -X PUT https://api.vocobase.com/api/v2/functions/FUNCTION_ID \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012" \
  -H "Content-Type: application/json" \
  -d '{
    "description": "Updated description for better LLM invocation",
    "timeout": 15
  }'
```

### Delete a function

```bash cURL theme={null}
curl -X DELETE https://api.vocobase.com/api/v2/functions/FUNCTION_ID \
  -H "Authorization: Bearer rg_live_abc123def456ghi789jkl012"
```

Returns `204 No Content`. Deleting a function automatically detaches it from all agents.

***

## Full lifecycle example

```bash theme={null}
# 1. Create the function
FUNC=$(curl -s -X POST https://api.vocobase.com/api/v2/functions \
  -H "Authorization: Bearer $API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "check_order",
    "description": "Look up order status by order ID. Call when user asks about their order.",
    "url": "https://your-api.com/orders/status",
    "input_schema": [{ "name": "order_id", "type": "string", "description": "Order ID", "required": true }],
    "timeout": 10,
    "started_messages": ["Looking up your order..."]
  }')
FUNCTION_ID=$(echo $FUNC | jq -r '.data.id')

# 2. Attach to agent
curl -X POST "https://api.vocobase.com/api/v2/agent/$AGENT_ID/functions/$FUNCTION_ID" \
  -H "Authorization: Bearer $API_KEY"

# 3. Verify it is active
curl "https://api.vocobase.com/api/v2/agent/$AGENT_ID/functions" \
  -H "Authorization: Bearer $API_KEY"
```

***

## Building your function endpoint

Your endpoint receives a `POST` request from the bot with the input fields in the body:

```json theme={null}
{
  "city": "Mumbai"
}
```

Respond with a JSON object matching your `output_schema`:

```json theme={null}
{
  "temperature": 32,
  "condition": "Sunny"
}
```

**Requirements:**

* Must respond within the configured `timeout` (default 30 seconds)
* Must return valid JSON
* Must return a `2xx` status code
* HTTPS required in production

If your endpoint fails (timeout, non-2xx, invalid JSON), the bot says the `error_message` and continues the conversation.

***

## Error responses

| Status | Code                     | Cause                                           |
| ------ | ------------------------ | ----------------------------------------------- |
| `400`  | `VALIDATION_ERROR`       | Name format, URL format, schema too large, etc. |
| `403`  | `FUNCTION_LIMIT_REACHED` | Plan limit on number of functions reached       |
| `403`  | `FORBIDDEN`              | Attempt to modify a built-in function           |
| `404`  | `FUNCTION_NOT_FOUND`     | Function or agent not found                     |
| `409`  | `FUNCTION_NAME_TAKEN`    | A function with this name already exists        |
| `409`  | `ALREADY_ATTACHED`       | Function is already attached to this agent      |

***

## Tips

<Tip>
  **Write clear descriptions.** The description is the #1 factor in whether the agent calls the function at the right time. Include specific trigger phrases: "Call this when the user asks about their order status or delivery date."
</Tip>

* **Keep timeouts short.** Users are waiting in real-time. A 10-second timeout feels like an eternity in a voice conversation.
* **Use `started_messages`.** They fill the silence while your endpoint processes. Without them, there is dead air.
* **One function, many agents.** Create a function once and attach it to as many agents as you need.

## Next steps

<CardGroup cols={2}>
  <Card title="Quick Start" icon="rocket" href="/quickstart">
    Create an agent to attach functions to.
  </Card>

  <Card title="Knowledge Base" icon="book" href="/knowledge-base">
    Give your agent document context alongside custom functions.
  </Card>
</CardGroup>
