Skip to main content

What are Custom Functions?

Custom functions let your Voice AI agent call your own APIs during live conversations. Check order status, update a CRM, book appointments, or integrate with any system - all while on a call.
Bolna custom functions follow the OpenAI function calling specification. If you’ve used OpenAI function calling before, you’ll feel right at home.
Custom function configuration modal showing JSON schema with name, description, parameters, and API endpoint configuration

How Custom Functions Work

1

LLM Reads Function Definition

The LLM sees your function’s name and description to understand what the function does
2

Decides When to Call

Based on conversation context, the LLM decides if this function should be triggered
3

Extracts Parameters

The LLM collects required parameter values from the conversation with the caller
4

Bolna Executes API Call

Bolna makes the HTTP request to your API endpoint with the extracted parameters
5

Response Feeds Back

The API response is returned to the LLM, which continues the conversation naturally
The description is everything. The LLM relies heavily on your description to know when to call the function. A vague description = unreliable triggering.

Complete Schema Structure

Every custom function has two parts: Function Definition (tells LLM what it does) and API Configuration (tells Bolna how to call your API).
{
  "name": "function_name",
  "description": "Detailed description of when to call this function",
  "pre_call_message": "What agent says while the API is being called",
  "parameters": {
    "type": "object",
    "properties": {
      "param_name": {
        "type": "string",
        "description": "What this parameter represents"
      }
    },
    "required": ["param_name"]
  },
  "key": "custom_task",
  "value": {
    "method": "GET",
    "param": {
      "param_name": "%(param_name)s"
    },
    "url": "https://your-api.com/endpoint",
    "api_token": "Bearer your_token",
    "headers": {}
  }
}
Mandatory: The field "key": "custom_task" must be present exactly as shown. Do not modify this value.

Schema Reference

Parts 1-2 follow the OpenAI function calling specification - they define what the function does. Parts 3-5 are Bolna extensions that define how to execute the API call automatically.

1. Function Definition

These fields tell the LLM about your function:
FieldRequiredDescription
nameYesUnique function identifier. Use snake_case (e.g., get_order_status)
descriptionYesTells the LLM when to trigger this function. Be specific and detailed.
parametersYesDefines what information to collect from the caller

2. Parameters Object

The parameters object defines what information the LLM should collect from the caller.
"parameters": {
  "type": "object",
  "properties": {
    "customer_name": {
      "type": "string",
      "description": "The customer's full name"
    },
    "order_id": {
      "type": "string",
      "description": "The order ID, usually starts with ORD-"
    },
    "quantity": {
      "type": "integer",
      "description": "Number of items"
    }
  },
  "required": ["customer_name", "order_id"]
}
Supported Data Types:
TypeUse CaseExample Values
stringText, names, IDs, phone numbers"John Doe", "ORD-12345"
integerWhole numbers5, 100, -10
numberDecimal numbers29.99, 3.14
booleanTrue/false flagstrue, false
Required vs Optional:
In required array?Behavior
YesLLM will keep asking until this value is provided
NoOptional - included if mentioned, skipped otherwise

3. Bolna Extensions

These fields are specific to Bolna:
FieldRequiredDescription
pre_call_messageNoMessage the agent speaks while API is executing (e.g., “One moment please…”)
keyYesMust be "custom_task" - identifies this as a custom function
valueYesAPI configuration object (see below)

4. API Configuration (value object)

This tells Bolna how to make the HTTP request:
FieldRequiredDescription
methodYesHTTP method: GET, POST, PUT, PATCH, DELETE
urlYesYour API endpoint URL
paramYesMaps parameters to API request using format specifiers
api_tokenNoAuthorization header value (e.g., Bearer your_token)
headersNoAdditional HTTP headers as key-value pairs

5. Format Specifiers

The param object maps your parameters to the API request. Use Python-style format specifiers:
Data TypeFormat SpecifierExample
String%(param_name)s"customer_id": "%(customer_id)s"
Integer%(param_name)i"quantity": "%(quantity)i"
Float%(param_name)f"price": "%(price)f"
Parameter names must match exactly. The name in properties must be identical to the name in param mapping (case-sensitive).

Examples

curl --location 'https://my-api-dev.xyz?customer_phone=+19876543210' \
--header 'Authorization: Bearer your_api_token' \
--header 'header1: value1' \
--header 'header2: value2'
The above request corresponds to the following GET custom function:
{
  "name": "get_product_details",
  "description": "Use this tool to fetch product details when the customer asks about products, pricing, or availability.",
  "pre_call_message": "Let me look that up for you.",
  "parameters": {
    "type": "object",
    "properties": {
      "customer_phone": {
        "type": "string",
        "description": "Customer's phone number for verification"
      }
    },
    "required": ["customer_phone"]
  },
  "key": "custom_task",
  "value": {
    "method": "GET",
    "param": {
      "customer_phone": "%(customer_phone)s"
    },
    "url": "https://my-api-dev.xyz",
    "api_token": "Bearer your_api_token",
    "headers": {
      "header1": "value1",
      "header2": "value2"
    }
  }
}
curl --location 'https://my-api-dev.xyz/v1/store_rating' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer your_api_token' \
--header 'version: 1.0' \
--data '{
    "customer_id": "134532",
    "rating": "5",
    "comment": "Great service!"
}'
The above request corresponds to the following POST custom function:
{
  "name": "save_feedback",
  "description": "Use this tool to save customer feedback when they provide a rating, review, or comment about our service.",
  "pre_call_message": "I'm saving your feedback now.",
  "parameters": {
    "type": "object",
    "properties": {
      "customer_id": {
        "type": "string",
        "description": "The customer's unique ID"
      },
      "rating": {
        "type": "string",
        "description": "Rating from 1 to 5"
      },
      "comment": {
        "type": "string",
        "description": "Customer's comment or feedback"
      }
    },
    "required": ["customer_id", "rating"]
  },
  "key": "custom_task",
  "value": {
    "method": "POST",
    "param": {
      "customer_id": "%(customer_id)s",
      "rating": "%(rating)s",
      "comment": "%(comment)s"
    },
    "url": "https://my-api-dev.xyz/v1/store_rating",
    "api_token": "Bearer your_api_token",
    "headers": {
      "Content-Type": "application/json",
      "version": "1.0"
    }
  }
}

Using Variables and Dynamic Context

Context variables defined in your agent prompt are automatically substituted into custom functions. The LLM won’t ask the caller for these values - they’re already available.

Auto-Injected System Variables

VariableDescription
{agent_id}The id of the agent
{call_sid}Unique id of the phone call (from Twilio, Plivo, etc.)
{from_number}Phone number that initiated the call
{to_number}Phone number that received the call

How Variable Substitution Works

You are agent Sam speaking to {customer_name}.
The agent ID is "{agent_id}".
The call ID is "{call_sid}".
The customer's phone is "{to_number}".
ParameterDefined in Prompt?Result
to_numberYes (system)Auto-substituted
agent_idYes (system)Auto-substituted
customer_nameYes (passed via call)Auto-substituted
customer_addressNoLLM will collect from caller

Best Practices

Write Detailed Descriptions

Include synonyms and variations: “Use when customer asks about order status, shipping, delivery, or tracking”

Always Add Pre-call Message

Set a friendly message like “Let me check that for you” so callers know to wait

Use Required Fields Wisely

Only mark parameters as required if the function truly cannot work without them

Test APIs First

Verify your API works with tools like curl or Postman before adding to Bolna

Leverage Auto-Injection

Put known data in the agent prompt to avoid asking callers for information you already have

Match Names Exactly

Parameter names in properties and param must be identical (case-sensitive)

Troubleshooting

Cause: Description doesn’t match what the caller is saying.Fix: Make your description more comprehensive. Include synonyms and example phrases.
Cause: Incorrect URL, authentication, or headers.Fix: Test your API with curl or Postman first. Verify api_token format and required headers.
Cause: Typo or format specifier mismatch.Fix: Ensure parameter names match exactly between properties and param. Use %(name)s format.
Cause: Variable not defined in agent prompt.Fix: Add the variable to your agent prompt using {variable_name} syntax.

Next Steps