---
title: "Custom API"
description: "Configure a Custom API action to call an API connection, use the response in a conversation, and optionally show an interactive widget."
---

:::note[Plan]
Actions are available on the **Starter** plan and above.
:::

**Custom API** lets your agent call your API during a conversation. For example, it can look up an order when a visitor gives an order number, or create a record in your system and use the response in its answer. You can also show an enabled interactive [widget](/docs/widgets) after a successful JSON response.

You can add several Custom API actions to one agent, one for each task. The actions list shows each one by its **Action name**.

To add one, go to **Build** > **Actions**, select **Create action**, and select **Custom API**. A new action starts turned off. Select an [API connection](/docs/agents/api-connections) and save the action to turn it on.

## How it runs

1. A visitor's message matches the action's **When to use** text. See [Trigger Conditions](/docs/actions/trigger-conditions).
2. If a required [input field](#input-fields) is missing, the agent asks the visitor for it. Their answer completes the action on their next message. The agent never makes up a value.
3. Chatvox sends the request through the selected API connection, filling in any [variables](#variables). While it waits, the visitor sees your [While it runs](#details) message, such as "Looking up your order…".
4. The agent answers using the API response in plain words. It does not mention internal details, such as record types or status codes, unless they help the visitor, like an order number.

## Details

- **Action name**: a short name, such as "Check order status".
- **What it does**: a short description of the result the visitor gets, such as "Sends the visitor's idea to the team".
- **When to use**: what the visitor wants, in plain language. Do not list the details the action needs; make them required [input fields](#input-fields) instead:

  > When the visitor wants to know where their order is, or when it will arrive.

- **While it runs**: a short message visitors see on the web widget and Public Chat while the request runs, such as "Looking up your order…". Leave it empty to show "Working on it…". Up to 80 characters. Visitors never see the action's name.

## API connection and request

The **Endpoint** card combines a saved API connection with the request details:

- **API connection**: the connection for this agent. It supplies the base URL, authentication, and headers. See [API connections](/docs/agents/api-connections) to create or manage one.
- **Method**: `GET`, `POST`, `PUT`, `PATCH`, or `DELETE`.
- **Path and query**: the endpoint path, such as `/orders/{{order_number}}`. Chatvox adds it to the connection's base URL. Put query parameters here or add them through **Send fields as**.
- **Send fields as**: **JSON body** or **Query params**. `GET` requests always use query parameters.
- **Request body (optional)**: a JSON body for methods other than `GET`. See [Request body](#request-body).
- **Timeout (ms)**: how long to wait for a reply, from 1,000 to 15,000 milliseconds.

Input fields already used in the path are not sent a second time as request fields. When you set a request body, use placeholders to include input values.

## Input fields

Input fields are the values the agent collects from the conversation and sends with the request. Select **Add field** and fill in:

| Setting | Example | Description |
| --- | --- | --- |
| **Label** | Order number | A readable name. |
| **Key** | `order_number` | The name your API expects. Also the variable name, `{{order_number}}`. |
| **Type** | Text | **Text**, **Number**, or **Boolean**. |
| **Description** | The order number, such as A-1234 | Tells the agent what to send. Say where the value comes from and what it looks like. |
| **Required** | On | The agent asks the visitor for it before calling your API. |

:::tip
Tell the agent where the value comes from and what it looks like. For example: "The order number the visitor gave, such as A-1234. Never guess it from a product name."
:::

## Variables

Variables put values into **Path and query**, the selected connection's header values, and the **Request body (optional)**. Write them in double braces, such as `{{order_number}}`.

| Variable | Value |
| --- | --- |
| `{{key}}` | The value of the input field with that key, such as `{{order_number}}`. |
| `{{conversation.id}}` | The conversation's ID. It matches `conversation_id` in the [REST API](/docs/developers/conversations) and [webhooks](/docs/developers/webhooks). |
| `{{contact.name}}`, `{{contact.email}}`, `{{contact.phone}}` | The visitor's verified details, such as a logged-in user your website identifies with [User identification](/docs/deployments/web-widget/user-identification). Empty for anonymous visitors. |
| `{{page.url}}` | The page the visitor chatted from, without its query string. Web widget and Public Chat only. |
| `{{utm.source}}`, `{{utm.medium}}`, `{{utm.campaign}}`, `{{utm.term}}`, `{{utm.content}}` | The UTM parameters of the link the visitor arrived through, saved when the conversation starts. Empty if there were none. Web widget and Public Chat only. |

- Built-in values come from the conversation, never from the agent. The agent cannot change `{{contact.email}}`, for example.
- Values in the path and query are encoded for you, so a value like `A/1` does not break the request.
- A variable with no value becomes empty text, or `null` in the request body.
- Keep variables out of the URL host, such as `https://{{site}}.example.com`. The API connection sets the host.

## Request body

When **Send fields as** is **JSON body**, leave **Request body (optional)** empty to send the input fields as a JSON object, such as `{"order_number": "A-1234"}`. With **Query params**, Chatvox sends the fields in the URL. `GET` requests always use query params. If you enter a request body, Chatvox sends it as JSON as well.

To send your own shape, write it as JSON with variables:

```json
{
  "order": "{{order_number}}",
  "quantity": "{{quantity}}",
  "customer": { "email": "{{contact.email}}" },
  "note": "Sent from {{page.url}}"
}
```

- A value that is only a variable keeps its type. With a **Number** field, `"{{quantity}}"` is sent as `3`, not `"3"`.
- Text around a variable is kept, as in `"Sent from {{page.url}}"`.
- When you set a request body, input fields only reach your API through variables.

Authentication and shared headers are configured on the [API connection](/docs/agents/api-connections). Use a key with only the permissions this action needs. Chatvox sends the request when a visitor triggers the action.

## Response

Tell Chatvox how to read your API's reply:

- **Response format**: **JSON**, or **Text** for plain-text replies. With **Text**, the agent sees the first 2,000 characters.
- **Success path**: where the success flag is, such as `success`. Leave it empty to use the HTTP status: any 2xx status means success.
- **Message path**: where a human-readable message is. Leave it empty to use `message`.
- **Data path**: where the data is. Leave it empty to use `data`, or the whole response if there is no `data`.

Paths use dots for nested values, such as `order.status`. For example, with this response, the paths are `success`, `message`, and `data`:

```json
{
  "success": true,
  "message": "Order found",
  "data": { "status": "Shipped", "eta": "Friday" }
}
```

Return only what the agent needs. Smaller responses give more accurate answers.

### What the AI can see

For **JSON** responses, **What the AI can see** controls which response data the agent receives:

- **All fields**: the agent receives all mapped data and the mapped message.
- **Only selected fields**: select **Add field path** under **Fields the AI can see** and enter each path. The agent receives only those paths from the data selected by **Data path**, along with the request status and success result. It does not receive the mapped message.

For example, if **Data path** is `data` and the response is:

```json
{
  "data": {
    "order": {
      "status": "Shipped",
      "tracking_url": "https://example.com/track/A-1234"
    }
  }
}
```

Enter `order.status` to give the agent only the status from that data. The widget uses its own declared fields. It can still show `order.tracking_url` if it declares that field. This setting does not restrict the widget.

## Widget

In the **Widget** card, select a widget belonging to this agent, or select **No widget** to leave the reply without a card.

Select **Edit widget** to change the selected widget, or **Create widget** to build one. Both open a new tab. The widget list refreshes when you return to the action tab. See [Building widgets](/docs/widgets/building-widgets) and the [product search example](/docs/widgets/product-search-example).

The enabled widget appears under the reply after a successful **JSON** response. It does not appear after a failed request or a **Text** response. **Send test request** does not show the widget. Test the complete reply in the Playground. You can also set a widget's **When to show** condition to show it on its own; see [Trigger Conditions](/docs/actions/trigger-conditions).

### Map fields

When you select a widget, the **Map fields** section appears under the widget selector. It controls where each widget field comes from in the API response.

Empty rows use the response key with the same name. If your API response keys do not match your widget fields, map each field to its path in the response data (relative to **Data path**). Dot paths map whole values; transforming individual items inside a list is not supported.

To fill mappings with AI, select **Suggest mapping**. **Suggest mapping** requires sending a test request first so sample response data is available. It uses AI once, consumes a few credits, and suggests paths for your widget fields. Suggested paths show a **Suggested** badge until edited. If credits are insufficient, an error appears: `Not enough credits to suggest a mapping.` If the request fails, the action shows `Couldn’t suggest a mapping. Try again.` Changes to mappings are saved when you save the action.

Example mapping table for an order lookup action:

| Widget field | Response path | Value (from last test) | Description |
| --- | --- | --- | --- |
| `order.number` | `id` | `"1042"` | Maps the top-level `id` in data to `order.number`. |
| `order.status` | `fulfillment.status` | `"shipped"` | Maps nested status to `order.status`. |
| `order.arrival` | `estimated_delivery` | `"Tuesday, Oct 20"` | Maps delivery string to `order.arrival`. |
| `order.items` | `line_items` | `2 items` | Maps the array of line items. |

Widget data combines the default example, action inputs, and mapped API response, in that order. Later values override earlier values. Visitors receive only the fields declared by the widget. See [Widgets](/docs/widgets).

## Test it

Use the **Test** card:

1. Fill in sample values for the input fields.
2. Select **Send test request**.
3. Check the result the agent receives. It includes the success result, HTTP status, and data. With **All fields**, it also includes the mapped message. With **Only selected fields**, check that the data contains only your selected paths.

The test uses your unsaved changes, so you can fix the URL or paths and test again before saving. Built-in variables, such as `{{contact.email}}`, are empty in a test.

:::warning
The test sends a real request. A `POST` test creates real records in your system.
:::

Then try it in a conversation. The [Playground](/docs/agents/playground) runs actions as your web widget would, or you can use your live [web widget](/docs/deployments/web-widget/installation) or [Public Chat](/docs/deployments/public-chat/general-settings). Start with a message that leaves out the details, such as "Where is my order?", and check that the agent asks for them.

## Limits

- The API connection's base URL and the request must use HTTPS and reach a public internet address. Local and private addresses, such as `localhost` or `192.168.x.x`, are blocked.
- Redirects are not followed.
- Responses can be up to 1 MB.
- Requests time out after the **Timeout (ms)** you set, up to 15 seconds.

## Examples

### Check an order's status

Look up an order in your store's API.

| Setting | Value |
| --- | --- |
| **Action name** | Check order status |
| **What it does** | Looks up an order's status and delivery date. |
| **When to use** | When the visitor wants to know where their order is, or when it will arrive. |
| **While it runs** | Looking up your order… |
| **API connection** | Orders, with base URL `https://api.example.com` and a read-only bearer token |
| **Method** | `GET` |
| **Path and query** | `/orders/{{order_number}}` |
| **Input field** | `order_number`, Text, required. Description: "The order number the visitor gave, such as A-1234." |
| **Data path** | `order` |

> **Visitor**: Where is my order?
>
> **Agent**: I can check that. What is your order number?
>
> **Visitor**: A-1234
>
> **Agent**: Order A-1234 has shipped and should arrive on Friday.

### Send an idea to your team

Save visitor ideas as drafts in WordPress, with the details your team needs to follow up. This uses the WordPress REST API with an [application password](https://make.wordpress.org/core/2020/11/05/application-passwords-integration-guide/).

| Setting | Value |
| --- | --- |
| **Action name** | Send an idea to the team |
| **What it does** | Sends the visitor's idea to the team for review. |
| **When to use** | When the visitor wants to send, share, or suggest an idea, request, or note to the team. |
| **Method** | `POST` |
| **API connection** | WordPress, with base URL `https://example.com/wp-json/wp/v2` and WordPress Basic auth |
| **Path and query** | `/posts` |
| **Input fields** | `title`, Text, required: "A short title for the idea." `details`, Text, required: "The idea exactly as the visitor described it. Never a greeting." |
| **Message path** | `title.raw` |
| **Data path** | `id` |

**Request body (optional)**:

```json
{
  "title": "{{title}}",
  "status": "draft",
  "content": "{{details}}\n\nFrom: {{contact.email}}\nConversation: {{conversation.id}}\nPage: {{page.url}}\nCampaign: {{utm.campaign}}"
}
```

> **Visitor**: Hi, I have an idea for the team.
>
> **Agent**: What is your idea?
>
> **Visitor**: A Mailchimp integration, so captured leads sync to our newsletter list.
>
> **Agent**: Thank you. I have sent your idea to the team as "Mailchimp integration for captured leads."

The draft includes the page the visitor was on and the campaign that brought them, so your team has the context.

### Request a callback

Create a callback request in your CRM, and keep the conversation and campaign with it.

| Setting | Value |
| --- | --- |
| **Action name** | Request a callback |
| **What it does** | Asks the sales team to call the visitor back. |
| **When to use** | When the visitor wants someone from the team to call them back. |
| **Method** | `POST` |
| **API connection** | CRM, with base URL `https://api.example.com` and an API key header |
| **Path and query** | `/callbacks` |
| **Input fields** | `phone`, Text, required: "The phone number the visitor gave for the call." `best_time`, Text, required: "When the visitor prefers to be called, in their words." |

**Request body (optional)**:

```json
{
  "phone": "{{phone}}",
  "best_time": "{{best_time}}",
  "conversation_id": "{{conversation.id}}",
  "source": "{{utm.source}}",
  "campaign": "{{utm.campaign}}"
}
```

## Troubleshooting

Start by sending the same message in the Playground and reading the reply's [action steps](/docs/actions/action-steps): they show whether the action ran, what it sent and received, and why it did not run.

| Problem | What to check |
| --- | --- |
| `Choose an API connection from this agent.` | Select or create an API connection for this agent, then save the action. |
| The action never runs | Make **When to use** describe what the visitor wants, not the details the action needs. See [Trigger Conditions](/docs/actions/trigger-conditions). Also check that the action is on, and that its channel is on under **Channels**. |
| The agent says it cannot do it | The message did not match **When to use**. Broaden it to cover how visitors actually ask. |
| `Variables cannot be used in the URL host.` | Keep variables in **Path and query**. The base URL in the API connection cannot contain variables. |
| `URL must resolve to a public IP address.` | The request points to a local or private address. Use your API's public HTTPS address. |
| `{{contact.email}}` is empty | The visitor is not verified. Set up [User identification](/docs/deployments/web-widget/user-identification), or add an input field for the email. |
| UTM variables are empty | The visitor did not arrive through a link with UTM parameters, or the conversation started on WhatsApp or the API. |
| The agent says the request could not be completed | Your API returned an error, or the success flag was false. Use the **Test** card to see the response. |
| The agent uses the wrong part of the response | Set **Message path** and **Data path**, and use the **Test** card to check what the agent receives. |

## Channels

Select where the action runs: **Web widget**, **Public Chat**, **WhatsApp**, and **API** (conversations started through the [REST API](/docs/developers/agents-and-chat)). All four are on by default. The API request can run on each enabled channel, but an enabled widget appears only in the web widget, Public Chat, and Playground. [Custom API](/docs/actions/custom-api) and [MCP Server](/docs/actions/mcp-server) are the only actions that can run in API conversations. See [Where actions run](/docs/actions#where-actions-run).
