
# Custom Functions

Custom functions let your AI bot connect to other systems during live conversations. Instead of saying "I'll check and get back to you," the bot can look up an order status, check inventory, or create a record in your CRM (customer relationship management system - the software you use to track leads and customers, such as HubSpot or Salesforce) - all in real time, while the customer waits.

::: walkthrough custom-function
:::

---

## Custom Functions vs Webhooks

This is the most common point of confusion, so it's worth getting clear on it before you build anything.

| | Webhooks | Custom Functions |
|---|----------|------------------|
| **Direction** | One-way (fire-and-forget) | Two-way (call and wait) |
| **What the bot does** | Sends out a notification when something happens, then keeps going. | Calls out, **pauses, waits for the response**, and uses what comes back to continue the conversation. |
| **Visibility to the conversation** | The downstream result is invisible to the bot — it never sees what happened. | The response is fed straight back into the AI, so the bot can quote it, reason about it, and reply to the customer with it. |
| **Best for** | Logging events, syncing data to a CRM, triggering external automations (Zapier, Make, n8n). | Anything where the bot needs an **answer** before it can reply — live lookups, real-time pricing, on-the-fly content generation. |

**When to pick which:** If you just need to *tell* another system that something happened, use a webhook - a one-way automated message sent to another system (see **Settings → Integrations → Webhooks**). If the bot needs to *learn* something from another system before continuing the conversation, use a custom function.

---

## Examples of What Custom Functions Unlock

Because the response feeds back into the conversation, custom functions open up things webhooks simply can't do:

- **Live Shopify or WooCommerce inventory lookup** — Before quoting a customer, the bot checks stock in real time and answers "Yes, we have 12 in size M" instead of "let me check and get back to you."
- **Dynamic pricing from a Google Sheet** — Your sales team updates pricing in a sheet; the bot reads the latest row mid-conversation and quotes the current price without anyone touching the AI configuration.
- **Voice-AI callback agent** — When the bot qualifies a lead, it triggers a voice agent (for example, an ElevenLabs-powered caller) to phone the lead back within minutes, and confirms to the customer "great, expect a call in the next 5 minutes."
- **Custom quotation PDF, generated and emailed mid-chat** — The bot collects requirements, calls your quote-builder, gets back a PDF URL, and tells the customer "I've just emailed your quotation — check your inbox."

---

## What Can Custom Functions Do?

Think of custom functions as giving your bot superpowers beyond just chatting. Here are real-world examples:

- **Order tracking** - A customer asks "Where's my order?" and the bot checks your e-commerce system and replies with the shipping status and tracking link
- **Inventory check** - "Do you have this in size 10?" The bot checks your stock system and gives a real-time answer
- **CRM updates** - When the bot qualifies a lead, it automatically creates or updates a record in HubSpot, Salesforce, or any other CRM
- **Quote generation** - The bot collects the customer's requirements and generates a personalized quote from your pricing system
- **Booking** - The bot creates an appointment in your external booking system
- **Discount verification** - "Is this coupon code valid?" The bot checks and confirms
- **Account lookup** - A returning customer is automatically recognized and their account details are pulled up

**The customer never sees what happens behind the scenes.** They simply experience a bot that can answer their questions with real, up-to-date information.

---

## How Custom Functions Work (The Simple Version)

Here is what happens when a custom function is triggered during a conversation:

1. **The customer asks something** that needs real-time data (e.g., "Where's my order?")
2. **The bot recognizes** that it needs to use a custom function to answer
3. **The bot collects** any missing information from the customer (e.g., "What's your order number?")
4. **The platform sends a request** to your system (your website, CRM, or any other tool) with the relevant details
5. **Your system responds** with the data (e.g., order status, tracking number, delivery date)
6. **The bot reads the response** and crafts a natural reply: "Your order ORD-4582 has been shipped and should arrive by Friday!"

### What a custom function call costs

Each custom function call is billed at your Agent's AI Quality tier:

| AI Quality tier | Credits per custom function call | With your own Anthropic key (BYOK) connected |
|---|---|---|
| Pro | 1 credit | 0 credits — runs on your key |
| Economy (deprecated) | 0.5 credits | 0 credits — runs on your key |
| Max | 0.25 credits | still 0.25 credits, billed even with your own key connected, because Max runs on our own model |
| Mini | 0.15 credits | still 0.15 credits, billed even with your own key connected, because Mini runs on our own model |

---

## Setting Up a Custom Function (Step by Step)

1. In the main sidebar, under **AI Studio**, click **Custom Functions**.

::: master-only
<figure><img src="../.gitbook/assets/v2-custom-functions.png" alt="The empty Custom Functions page with a New function button"><figcaption><p>The Custom Functions page before you've created any. Each one is an HTTP API call the AI can invoke as a tool.</p></figcaption></figure>
:::

2. Click the green **+ Add Function** (or **New function**) button at the top-right.

::: master-only
<figure><img src="../.gitbook/assets/v2-custom-function-builder-empty.png" alt="The New custom function pop-up, empty, showing Name, Description, and AI Action fields under Details, and Method and URL fields under Request"><figcaption><p>The New custom function pop-up. A Visual / JSON toggle at the top lets you switch to editing the raw function definition if you prefer.</p></figcaption></figure>
:::

The custom functions list displays a table with the following columns:

| Column | What It Shows |
|--------|--------------|
| **Name** | The function name (e.g., `check_order_status`) |
| **Description** | A short summary of what the function does (truncated to 50 characters in the table) |
| **Method** | The HTTP method used, shown as a colored badge: GET (blue), POST (green), PUT (orange), DELETE (red) |
| **Created** | The date the function was created |

This makes it easy to scan your functions at a glance and find the one you need.

### Step 1: Give It a Name and Description

::: master-only
<figure><img src="../.gitbook/assets/v2-custom-function-builder-filled.png" alt="The Details section of the New custom function pop-up filled in with Name check_order_status, a Description, and an AI Action explaining when to call it"><figcaption><p>Name, Description, and AI Action filled in. The AI reads all three to decide when to call this function.</p></figcaption></figure>
:::

| Field | What to Enter | Example |
|-------|--------------|---------|
| **Name** | A short name using letters, numbers, and underscores | `check_order_status` |
| **Description** | Explain what this function does (the AI reads this to decide when to use it) | "Looks up the current status of a customer's order using their order number" |
| **Purpose (AI Action)** | Tell the AI exactly when and how to use this function | "Use this when a customer asks about their order status, shipping, or delivery. Ask for their order number first." |

**Tip:** Be very specific in the description and purpose. The clearer you are about when the function should be used, the more reliably the bot will use it at the right time.

### Step 2: Set Up the Connection

You need to tell the app where to send the request:

| Field | What to Enter | Example |
|-------|--------------|---------|
| **URL** | The web address of your system's endpoint (the specific address on your system that receives the request and sends back data) | `https://api.yourstore.com/v1/orders/status` |
| **Method** | The type of request to send | See options below |

**Which method to choose:**

| Method | When to Use It |
|--------|---------------|
| **GET** | Looking up information (order status, inventory, account details) |
| **POST** | Creating new records (support tickets, leads, bookings) or complex lookups |
| **PUT** | Updating an existing record completely |
| **PATCH** | Updating part of an existing record |
| **DELETE** | Removing a record |

If you are not sure which to use, check with your developer or the documentation of the system you are connecting to. **GET** (for lookups) and **POST** (for creating records) are the most common.

### Step 3: Add Authentication Headers

Most systems require authentication to accept requests. Add any required headers:

| Header | Example Value |
|--------|--------------|
| `Authorization` | `Bearer your-api-key-here` |
| `Content-Type` | `application/json` |

**Security tip:** Use a dedicated API key with limited permissions. Do not use admin-level credentials.

**Where to find API keys:** Check the settings or developer section of the system you are connecting to (e.g., your CRM, e-commerce platform, or booking tool).

### Step 4: Define the Input (What the Bot Sends)

Input parameters are the pieces of information the bot collects from the conversation and sends to your system.

For each parameter, you specify:

| Property | What It Means |
|----------|--------------|
| **Name** | The parameter name (must match what your system expects) |
| **Type** | What kind of data it is (text, number, true/false, etc.) |
| **Description** | Tell the AI what this information is and where to find it in the conversation |
| **Required** | If set to Yes, the bot will ask the customer for this information before proceeding |

**Available parameter types:**

| Type | What It Means |
|------|--------------|
| **string** | Text (names, order numbers, addresses) |
| **number** | A numeric value (quantity, price) |
| **boolean** | True or false (yes/no values) |
| **array** | A list of items. Sent as a real JSON list — in **Run Test** you can type it as `[8624]`, `["a", "b"]` or simply comma-separated (`8624, 8625`) and it is converted for you. If your API is strict about what the list contains — e.g. numbers only — set the optional **Item type** next to the type and every value in the list is converted to it. |
| **query_param** | Text that is sent as a URL parameter instead of in the request body. Use this when your API expects data in the URL (e.g., `?order_id=123`). |

Each parameter also has an optional **Request body path** field. Normally a parameter is sent as a top-level field in the request body (or as a query string value, for the `query_param` type). If your endpoint expects it nested instead — e.g. `{"order": {"id": "ORD-123"}}` — set the path to `order.id` and the platform nests the value there for you.

::: master-only
<figure><img src="../.gitbook/assets/v2-custom-function-input-parameters.png" alt="The Input parameters section of the custom function builder with one parameter filled in: order_number, type string, Required on, and a description"><figcaption><p>An input parameter filled in: Name, Type, Required, and Description. Click Add parameter to add more.</p></figcaption></figure>
:::

**Example: For an order status lookup, you might define:**

- **order_number** (string, required): "The customer's order number. Usually starts with ORD- followed by digits. Ask the customer for this if they haven't mentioned it."
- **email** (string, optional): "The customer's email address for additional verification. Only needed if the order number alone doesn't find a match."

### What Your System Receives Automatically

In addition to the input parameters you define, the platform automatically includes system data with every request. Your endpoint receives this in a `system` field:

| System Field | What It Contains |
|-------------|----------------|
| `system.contactId` | The platform ID of the contact in the conversation |
| `system.campaignId` | The campaign ID the conversation belongs to |
| `system.userId` | Your user ID |
| `system.channel` | The messaging channel (e.g., `"whatsapp"`, `"instagram"`) |
| `system.contact` | The full contact record (name, phone, email, tags, etc.) |
| `system.campaign` | The campaign configuration |
| `system.test` | `true` if this is a Try Out test, `false` for live conversations |

This is useful if your system needs to identify the contact, check which campaign triggered the function, or behave differently during testing.

> **Don't need the system data?** Turn on the **Skip System Data** toggle in the function builder. The bot will then send only the input parameters you defined — no contact or campaign data. Use this if your endpoint rejects unexpected fields or you simply want a leaner payload.

### Step 5: Test It, Then Let the Bot Read the Response

You normally don't need to map response fields at all. Once your endpoint responds, the bot reads the whole JSON response and uses your function's **Description** and **Purpose (AI Action)** — plus each parameter's own description — to work out what matters and present it naturally. A clear Description on the function itself ("Retrieves the current status of a customer order including shipping info and tracking") does more of the work here than field-by-field mapping would.

If your endpoint returns a large response and you only want the bot to see a few specific values, open the **Response mapping** section (collapsed by default, just above Test). Each row picks one top-level field out of the response: **Response field** is the field name in your API's JSON reply, and **Output field** is the name the bot receives it under. With at least one row filled in, the bot gets only your mapped values instead of the full response body. Leave the section empty to keep the default full-response behavior.

::: master-only
<figure><img src="../.gitbook/assets/v2-custom-function-response-mapping.png" alt="The Response mapping section of the custom function builder with one row mapping response field status to output field order_status"><figcaption><p>Response mapping (optional): each row hands the bot one top-level response field under the name you choose. Empty = the bot reads the full response.</p></figcaption></figure>
:::

Before you save, use the **Test** section at the bottom of the builder to fire the request exactly as configured and see the real response, without leaving the app:

::: master-only
<figure><img src="../.gitbook/assets/v2-custom-function-test-panel.png" alt="The Test section of the custom function builder, showing an auto-generated ORDER_NUMBER field filled with a test value and a Run test button"><figcaption><p>The Test section auto-generates one field per input parameter you defined. Fill in a realistic value and click Run test to see the actual status code and response body your endpoint sends back.</p></figcaption></figure>
:::

The response you see here is the endpoint's raw reply. If you've set up **Response mapping** above, the bot in a real chat only receives those mapped fields — the test always shows the full raw response so you can see what's available to map. If something looks off (unexpected field names, extra nesting), fix it on your endpoint or adjust your mapping.

---

## Assigning Functions to an Agent

After creating a custom function, you need to tell each Agent which functions it can use:

1. Open the [Agent](../ai-agents/ai-agents.md) under **AI Studio → AI Agents**.
2. Go to its **AI Abilities** tab. (For a campaign that still holds its own AI settings directly rather than through a separate Agent, the same list appears on that campaign's own **AI Abilities** step instead.)
3. You'll see a list of every custom function you've created. Toggle on each function you want this Agent's bot to be able to call.
4. Click **Save changes** at the bottom. Selections only apply once saved.

::: master-only
<figure><img src="../.gitbook/assets/v2-agent-abilities-tab.png" alt="An AI Agent's AI Abilities step showing the Custom functions and MCP servers sections"><figcaption><p>The <strong>AI Abilities</strong> step inside the agent editor — same Custom functions and MCP servers sections as the Agent's AI Abilities tab, just scoped to this one campaign.</p></figcaption></figure>
:::

Only assigned functions are available to the bot for that Agent. This prevents the bot from accidentally using functions that are not relevant.

---

## Testing Your Custom Functions

Before going live, test thoroughly:

1. **Run the built-in Test** - Use the **Test** section inside the function builder (see above) for a quick check without leaving the app — fill in realistic values and click Run test.
2. **Test your system's endpoint directly** - For the full checklist below, a dedicated tool like Postman (or your developer) digs deeper than a single Run test
3. **Test in Try Out** - Simulate a conversation where the customer asks something that should trigger the function
4. **Check the response** - Make sure the bot correctly reads and presents the data
5. **Test error scenarios** - What happens if the customer gives an invalid order number? What if your system is temporarily down?

### When the test comes back 401 or 403

A 401 or 403 means your endpoint received the request and refused it. The giveaway is that **nothing shows up in your own logs** — most tools reject an unauthorised call before they ever start the workflow, so there is nothing to see on your side and it looks like the request never arrived.

Almost always this is an authentication mismatch: your endpoint wants one kind of credential and the function is sending a different one. Check that the header you added in [Step 3](#step-3-add-authentication-headers) is the exact one your system expects.

The most common version of this is a webhook protected with **Basic Auth** (n8n, Make, and most self-hosted tools offer this as a checkbox on the webhook itself) while the function sends a custom secret header like `X-My-Secret`. Basic Auth only accepts an `Authorization` header, so a custom header is ignored and the call is rejected. You have two options:

- **Turn Basic Auth off** on the webhook, and check your custom header inside the workflow instead.
- **Keep Basic Auth on**, and add an `Authorization` header to the function whose value is the word `Basic` followed by your base64-encoded `username:password`.

Either one works — just make sure both sides agree.

### When the test comes back 404

The endpoint URL is wrong, or the workflow is not published. In n8n specifically, every webhook has a separate **Test** URL and **Production** URL, and the Test one only listens while you have the editor open. Copy the Production URL and make sure the workflow is active.

### Seeing failures in Try Out and Chats

When the AI calls a custom function during a conversation and the call fails — wrong credentials, endpoint down, timeout — the conversation now shows it: a red **"(function name) failed"** marker appears in the thread, in both the agent's **Try Out** tab and real conversations in **Chats**. Click the marker to expand the details: the status code your endpoint returned and its response body, which is usually enough to tell you exactly what to fix (a `401` with an "unauthorized" message means the authentication header, a timeout means your endpoint took longer than 30 seconds).

Successful calls show a marker too — click it to see what your endpoint actually sent back. This is the quickest way to debug an integration end-to-end: have the test conversation in Try Out, then click the function marker to see the real request outcome without leaving the page.

---

## Complete Example: Order Status Lookup

Here is a fully configured example you can use as a template:

**Basic Information:**
- **Name:** `check_order_status`
- **Description:** "Retrieves the current status of a customer order including shipping info and tracking"
- **AI Action:** "Call this when a customer asks about their order status, where their package is, or when it will arrive. Always ask for the order number first."

**Connection:**
- **URL:** `https://api.mystore.com/orders/lookup`
- **Method:** POST

**Headers:**
- `Authorization`: `Bearer sk_live_abc123`
- `Content-Type`: `application/json`

**Input Parameters:**
- `order_number` (text, required): "The order number provided by the customer"
- `customer_email` (text, optional): "Customer's email for additional verification"

**No response mapping needed** — with a clear Description and AI Action already set above, the bot reads whatever JSON your endpoint returns (status, tracking number, delivery date, items) and works out how to talk about it.

**What the conversation looks like:**

> **Customer:** Hey, where's my order?
>
> **Bot:** Hi! I'd be happy to check on your order. Could you share your order number with me?
>
> **Customer:** It's ORD-78234
>
> **Bot:** Let me look that up for you...
>
> Your order ORD-78234 has been shipped and is on its way! The estimated delivery date is March 10th. You can track your package here: https://tracking.example.com/1Z999AA1
>
> Is there anything else I can help you with?

---

## Complete Example: Shopify Order Status

If the store runs on Shopify, you don't need a developer to build a lookup endpoint — Shopify's own system can answer order questions directly. (For product and stock questions on a Shopify store you don't need a custom function at all: connect the store's built-in server instead — see [Connect a Shopify store](mcp-servers.md#ready-made-example-connect-a-shopify-store).)

**First, create an access token in Shopify.** Shopify changed this during 2026: apps can no longer be created inside the Shopify admin, and the new app screen hands you a **Client ID** and **Client secret** instead of a ready-made token. The steps below turn those into a permanent token. Allow about ten minutes, once per store. (If the store already has an older app created the old way, its existing token keeps working — skip straight to the custom function below.)

1. Go to the Shopify Dev Dashboard at [dev.shopify.com](https://dev.shopify.com), open your organisation, and click **Apps → Create app**. Name it something like `Order lookup`.
2. Give the app the **read_orders** permission, release a version, and install the app on the store.
3. Open the app's **Settings** and add the store's own web address (for example `https://www.yourstore.com/`) to the allowed redirect URLs. Save.
4. Still in **Settings**, copy the **Client ID** and the **Client secret**.
5. In a browser where you're signed in to that store's Shopify admin, open the address below, replacing the store name, the client ID, and the redirect address with your own:
   `https://YOUR-STORE.myshopify.com/admin/oauth/authorize?client_id=YOUR-CLIENT-ID&scope=read_orders&redirect_uri=https://www.yourstore.com/&state=12345`
   Approve the screen that appears. The browser lands on your redirect address and the address bar now contains `code=` followed by a long value — copy that value. It is only valid for a couple of minutes, so go straight to the next step.
6. Swap that code for the token, which you can do inside <span data-t="appName">DM Champ</span>. In the custom function builder, set **Method** to POST and **URL** to `https://YOUR-STORE.myshopify.com/admin/oauth/access_token`, add three text input parameters named `client_id`, `client_secret`, and `code`, then click **Test**, fill in the three values, and run it. The response contains `access_token` — that is your permanent token. Copy it somewhere safe, then clear the builder and set up the real function below.

**Then set up the custom function:**

**Basic Information:**
- **Name:** `check_shopify_order`
- **Description:** "Looks up an order in the store's Shopify system and returns its status, tracking, and items"
- **AI Action:** "Call this when a customer asks about their order status or delivery. Always ask for the order number first."

**Connection:**
- **URL:** `https://YOUR-STORE.myshopify.com/admin/api/2026-01/orders.json?status=any` — replace `YOUR-STORE` with the store's `.myshopify.com` name (this address uses the technical Shopify domain, not the store's custom domain)
- **Method:** GET

**Headers:**
- `X-Shopify-Access-Token`: `shpat_...` (the token from above)

**Input Parameters:**
- `name` (query_param, required): "The customer's order number exactly as it appears in their order confirmation, including the # sign — for example #1001. Ask the customer for it if they haven't mentioned it."

**No response mapping needed** — the bot reads the returned order (payment status, fulfillment status, tracking, items) and answers naturally.

**Good to know:** a token created this way can see orders from the **last 60 days** — plenty for day-to-day support questions, but not a full order history.

---

## Complete Example: Book an Appointment

**Basic Information:**
- **Name:** `create_booking`
- **Description:** "Creates a new appointment in our booking system"
- **AI Action:** "Use this after confirming the date, time, and contact details with the customer. Do not call until the customer explicitly confirms they want to book."

**Connection:**
- **URL:** `https://booking.mycompany.com/api/appointments`
- **Method:** POST

**Input Parameters:**
- `date` (text, required): "Appointment date in YYYY-MM-DD format"
- `time` (text, required): "Appointment time in HH:MM format"
- `name` (text, required): "Customer's full name"
- `phone` (text, required): "Customer's phone number"
- `service_type` (text, required): "The type of service being booked"

---

## Complete Example: Add a Newsletter Subscriber to Your CRM

A very common pattern: the bot finishes answering, offers your newsletter, the contact replies with their email address, and that address should land in your email tool straight away. Most CRMs (FluentCRM, ActiveCampaign, MailerLite, Brevo, and others) accept a simple POST for exactly this, so no automation platform is needed in between.

This example uses **FluentCRM** on WordPress. The shape is the same for any other tool that gives you an "incoming webhook" or a "create subscriber" endpoint.

**First, get the URL from your CRM.** In WordPress, open **FluentCRM → Settings → Incoming Webhooks** and create a webhook. Pick the list, tags, and subscription status that new contacts should get, then copy the webhook URL it generates. Anything you set here is applied automatically, so the bot only has to send the email address.

**Then set up the custom function:**

**Basic Information:**
- **Name:** `add_newsletter_subscriber`
- **Description:** "Adds someone to our newsletter list using the email address they gave in the chat"
- **AI Action:** "Use this the moment the contact agrees to subscribe to the newsletter and gives their email address. Do not call it before they have actually given an address, and do not call it twice for the same person."

**Connection:**
- **URL:** the webhook URL you copied from your CRM
- **Method:** POST

**Input Parameters:**
- `email` (string, required): "The email address the contact gave in the conversation"
- `first_name` (string, optional): "The contact's first name, if they mentioned it"

**Skip System Data:** turn this **on**. Your CRM only needs the fields above, and a leaner payload avoids errors from tools that reject unexpected fields.

**Response mapping:** not needed here. Nothing has to come back for the bot to continue.

**Don't forget to switch the function on for the Agent that runs the conversation** (see [Assigning Functions to an Agent](#assigning-functions-to-an-agent)). This is the most common reason a correctly built function never fires.

::: tip
**Tip:** the bot also has a built-in **Update Contact Email** tool, which saves the address onto the contact record inside the platform. That is separate from this function, and useful alongside it — the built-in tool keeps your own contact record complete, the custom function pushes the address to your CRM.
:::


---

## Tips for Reliable Custom Functions

1. **Make sure repeated requests are safe.** If the same request is accidentally sent twice, it should not create duplicate records. Network hiccups can occasionally cause this.

2. **Return clear error messages.** If something goes wrong on your system's end, return a human-readable error. The bot will relay it to the customer gracefully.

3. **Keep response times under 10 seconds.** If your system takes longer, consider returning a quick acknowledgment first.

4. **Handle expired or invalid credentials.** If your API key expires, make sure the error message is clear so the bot knows to alert a human instead of retrying.

5. **Write detailed descriptions.** The AI uses your descriptions to figure out when to call the function and how to extract the right information from the conversation. Vague descriptions lead to mistakes.

6. **Test with real conversations.** Try Out is great for initial testing, but monitor your first few live conversations to make sure everything works with real customer queries.

7. **Keep logs on your end.** Ask your developer to log the requests coming from the app so you can quickly debug any issues.

8. **Use a public final URL.** Your function URL must be a public web (HTTP/HTTPS) address. Internal, localhost, and private-network addresses are rejected for security, and the platform does not follow redirects — point the function at the final URL directly, not at one that redirects to it.

---

## Execution Limits

Every custom function has an optional **Execution limits** section at the bottom of the editor. It controls how often the AI may run the function, and whether an earlier result can be reused. Everything here is optional — leave it all empty and the function behaves exactly as before.

::: master-only
<figure><img src="../.gitbook/assets/v2-custom-function-execution-limits.png" alt="The Execution limits section of the custom function editor: Read-only function and Serve cached result on repeat calls toggles, a Max runs per conversation field, and a Max runs per time window pair of fields"><figcaption><p>The Execution limits section. All fields are optional — empty means no limit.</p></figcaption></figure>
:::

**Read-only function.** Turn this on if your function only *reads* data — a stock lookup, a price check, an order-status search — and never creates or changes anything. When a temporary network hiccup interrupts the AI mid-reply, the platform can then safely retry the conversation turn instead of leaving the customer without an answer. Only enable it if the function truly never writes anything: a function that creates records must stay off, so a retry can never accidentally run it twice. Functions that use the GET method are treated as read-only automatically, since GET requests are meant to only fetch data; POST, PUT, PATCH and DELETE functions need the toggle.

**Serve cached result on repeat calls.** When the AI calls the function again with the same inputs (for example, the customer asks the same question twice), the previous result is reused instead of calling your endpoint again. Cached results are kept for up to 24 hours, and a call with *different* inputs always goes to your endpoint fresh.

**Max runs per conversation.** A hard cap on how many times the function can run within one conversation. Set it to 1 for functions that should only ever fire once per chat — generating a quote, triggering a callback, starting an automation. When the cap is reached, the AI is told the function already ran and is given the most recent result, so it can still answer the customer instead of going silent.

**Max runs per time window.** A rate limit across time: for example, at most 5 runs within 60 minutes. Useful for functions that call paid third-party services or trigger heavier automations. Both boxes must be filled in together (a number of runs and a window in minutes, up to 7 days).

A couple of things to know:

- Limits count **successful** runs only. A call that failed on your endpoint's side does not use up the budget.
- When a run is blocked by a limit, the customer is never left hanging — the AI is told why and works with the information it already has.
- Limits apply everywhere the function runs: regular chats on every channel, and functions managed by an automation. Test conversations in Try Out are not counted and not limited.

---

## Built-In Bot Tools

In addition to the custom functions you build yourself, the platform ships with a library of pre-built tools the AI bot can use during a conversation. These cover the most common things a bot needs to do — alerting a teammate, booking an appointment, tagging a contact, searching your website, scheduling a follow-up, and more — so you do not have to wire them up from scratch.

**The bot decides when to use each tool** based on what is happening in the conversation and how your Agent (and its attached campaign) is configured. Most of these tools turn on automatically when the related feature is enabled (for example, the booking tools only become available once you connect a calendar and enable bookings).

**Credit cost:** Each tool call is billed at your Agent's AI Quality tier, and custom functions you build yourself are billed the same way:

| AI Quality tier | Credits per tool call | With your own Anthropic key (BYOK) connected |
|---|---|---|
| Pro | 1 credit | 0 credits — runs on your key |
| Economy (deprecated) | 0.5 credits | 0 credits — runs on your key |
| Max | 0.25 credits | still 0.25 credits, billed even with your own key connected, because Max runs on our own model |
| Mini | 0.15 credits | still 0.15 credits, billed even with your own key connected, because Mini runs on our own model |

### Team & Task Tools

| Tool | What It Does | When the Bot Uses It |
|------|--------------|----------------------|
| **Alert a Team Member** | Pauses the bot for this contact and emails your team that a human is needed. The chat is flagged so a teammate can pick it up. | When the customer asks for a human, is frustrated, or asks something the bot is not allowed or able to answer. |
| **Create a Task** | Creates a new task on your task board, optionally linked to the contact and conversation. The bot keeps replying as normal — the task is just a note for your team to follow up. | For non-urgent items like a feature request, an upsell opportunity, or a callback the team should handle later. |
| **Suggest an FAQ Update** | When the bot hits a question it cannot answer well, it creates a task asking your team to add an answer to the knowledge base. | When a contact asks something not covered by your existing FAQs — so the gap gets fixed for next time. |
| **Add Context to an FAQ Suggestion** | If another contact later asks a similar question with a different angle, the bot appends that context to the existing FAQ suggestion instead of creating a duplicate task. | Automatic — keeps your task list clean when multiple people raise the same knowledge gap. |

### Contact Tools

| Tool | What It Does | When the Bot Uses It |
|------|--------------|----------------------|
| **Tagging** | Runs automatically after each bot reply — it isn't a tool the customer-facing bot decides to call. The system reviews the recent conversation and applies relevant tags, reusing your existing tags where possible (and creating a new one only when needed). | Automatic — whenever the conversation reveals something worth segmenting on, such as interest, intent, lead quality, or language. |
| **Update Contact Name** | Saves the contact's first and/or last name when they share it. | When the customer introduces themselves or corrects a name. |
| **Update Contact Email** | Saves the contact's email address when they share it. | When the customer provides an email — for newsletters, receipts, account lookups, etc. |

### Appointment & Booking Tools

These tools are only available when bookings are enabled on the campaign linked to your Agent, and a calendar event type is configured.

| Tool | What It Does | When the Bot Uses It |
|------|--------------|----------------------|
| **Check Available Time Slots** | Looks up which times are open in your connected calendar for a given date or date range. | When the customer wants to book and the bot needs to offer real availability. |
| **Book an Appointment** | Creates the appointment in your calendar and confirms the booking back to the customer. | After the customer confirms a specific date and time. |
| **Move an Appointment** | Reschedules an existing appointment to a new date and time. | When the customer asks to reschedule. |
| **Cancel an Appointment** | Cancels an existing appointment. | When the customer asks to cancel. |
| **Look Up Appointments** | Pulls up a contact's existing appointments so the bot knows what is already on the books. | When the customer asks "when is my appointment?" or before offering to reschedule. |

### Knowledge & Web Tools

| Tool | What It Does | When the Bot Uses It |
|------|--------------|----------------------|
| **Search Your Website** | Scans the URLs you have added to the campaign's dynamic URLs list to find product pages, articles, or other content that answers the customer's question. Available only when **AI Web Search** is turned on and you've added at least one Dynamic URL. If AI Web Search is off, the bot cannot read pages or links — even ones the customer pastes into the chat. | When the customer asks about something that is likely on your website — products, pricing, locations, policies. |
| **Check a Link** | Reads the contents of a specific URL so the bot can answer questions about that page. Available only when **AI Web Search** is turned on and you've added at least one Dynamic URL. If AI Web Search is off, the bot cannot read pages or links — even ones the customer pastes into the chat. | When the customer shares a link or asks about a specific page on your site. |
| **Search the Web** | Runs a public Google search and returns the top results, so the bot can answer questions outside your own content. | When the customer asks about something general (e.g., directions, public info) that is not in your knowledge base. Only used if web search is enabled. |

### Follow-Up Tools

These tools require follow-ups to be enabled on the campaign linked to your Agent.

| Tool | What It Does | When the Bot Uses It |
|------|--------------|----------------------|
| **Schedule a Smart Follow-Up** | Schedules an intelligent follow-up message using your follow-up sequence — picks the right template and timing based on the conversation. | When the customer goes quiet or asks the bot to "check back later." |
| **Schedule a Follow-Up** | Schedules a basic follow-up at a specific time. | When the bot needs to nudge the conversation forward at a defined moment. |

### Custom Function Runner

| Tool | What It Does | When the Bot Uses It |
|------|--------------|----------------------|
| **Run a Custom Function** | Executes one of the custom functions you have built and assigned to the Agent (see the rest of this page). | When the customer's request matches the purpose of one of your custom functions. |

### Restaurant Booking Tools (Zenchef and Formitable)

These tools are only available when a Zenchef or Formitable integration is connected. They let the bot manage restaurant reservations end-to-end.

| Tool | What It Does | When the Bot Uses It |
|------|--------------|----------------------|
| **Check Restaurant Availability** | Looks up open reservation slots for a given date, party size, and (optionally) seating area. | When a guest asks to book a table. |
| **Create a Restaurant Booking** | Creates a new reservation. | After the guest confirms a specific slot. |
| **Update a Restaurant Booking** | Changes the date, time, party size, or notes on an existing reservation. | When the guest asks to modify their booking. |
| **Cancel or Change Booking Status** | Cancels a reservation or updates its status (e.g., confirmed, no-show). | When the guest cancels, or when the bot needs to mark a status change. |
| **Search Bookings** | Finds existing reservations matching criteria like name, email, or date. | When a returning guest asks about an existing reservation. |
| **Update Guest Profile** | Updates the guest's profile in the restaurant system (preferences, notes, contact info). | When the guest shares dietary preferences, a new phone number, or other profile-level info. |
| **List Restaurant Products** | Pulls the list of menus, set menus, or add-ons available for booking. | When the guest asks "what set menus do you have?" or the bot needs to attach a menu to a reservation. |

### Turning Tools On and Off

Most tools are controlled on the Agent's **AI Abilities** tab (or the campaign's **AI Abilities** step, if you're working from a still-classic campaign):

- **Booking tools** turn on when you enable bookings and connect a calendar — this stays a per-campaign setting for now, with a link straight to that campaign's step from the Agent's own AI Abilities tab
- **Follow-up tools** turn on when you enable follow-ups
- **Restaurant tools** turn on when you connect a Zenchef or Formitable account
- **Web search** has its own toggle on the **FAQs & Knowledge** tab
- **Task tools** can be switched off per Agent with the **Allow AI to create tasks** toggle (they're on by default; the account-wide Tasks switch in **Settings → Profile → Features** turns the whole task system off everywhere)
- **Contact-update tools** are controlled on the same **AI Abilities** tab — whether the AI may rename contacts or save extra collected info onto them
- **Alert tools** are always available; **tagging** runs automatically after each bot reply (it's not a tool the bot chooses to call)

If you want the bot to stop using a specific built-in tool, the cleanest way is to disable the underlying feature (for example, turn off bookings to disable all booking tools).

---

## Functions Managed by an Automation

Some entries on your Custom Functions page may carry a **Managed by automation** badge. Those weren't created here — they come from an automation with an **AI Agent Function** trigger, which gives your agent an ability whose steps you build visually on the automation canvas instead of pointing at an outside web address.

A managed function is looked after for you: its name, description and fields always follow what's set on the automation's trigger, so it can't be edited or deleted from this page — use its **Open automation** link and change the automation itself. You can still choose which agents have it the normal way, though: on an agent's **AI Abilities** tab it appears alongside the agent's other abilities with a regular on/off toggle (if its automation is paused, the row says so — the ability goes live when the automation is switched on). Everything else about it behaves like any other custom function: the AI decides when to call it, collects the details you defined, and can use the automation's reply in the same conversation.

If you're choosing between the two: point a regular custom function at a system that already has an address to call; build an automation with an AI Agent Function trigger when the work is something you'd rather assemble from steps — look something up in a spreadsheet or database, branch on a condition, create records — without running a server of your own. See [Automations](../automations/automations.md#letting-your-ai-agent-call-an-automation).

---

## Plan Requirements

Custom functions are available on plans that include the custom functions feature. Check your subscription to confirm availability.

---

## Next Steps

- [Connect MCP Servers to Your Bot](mcp-servers.md) — a ready-made bundle of tools instead of one function at a time.
- [AI Agents](../ai-agents/ai-agents.md) — the main page of the AI Studio group Custom Functions lives in, and where custom functions get assigned to a bot.
