Agents
Tools
Tools let the agent call your HTTP endpoints in the middle of a conversation, for example to check free slots, book an appointment or look up an order, and the built-in end_call lets it hang up.
How a tool call works
- You describe each tool: a name, when to use it, and a JSON schema of its arguments.
- During the call, the LLM decides to use a tool and fills in the arguments.
- If the tool has a
pre_call_message, the agent says it (“One moment, let me check.”). - Voxa sends the HTTP request to your
urland waits up totimeout_seconds. - Your response goes back to the LLM, which uses it to answer the caller.
The LLM can call tools again with the new information. One caller turn allows up to four LLM requests, so the agent can chain a few tool calls before it answers. When the LLM asks for several tools at once, they run one after another, in order.
Fields
tools is an array of tool objects.
| Field | Type | Default | Allowed | What it does |
|---|---|---|---|---|
name | string | required | letters, digits and _; starts with a letter or _; 1 to 64 characters; unique in the agent; not end_call | The function name the LLM calls. |
description | string | required | 3 to 1000 characters | When and why to use the tool. The LLM reads this to decide. |
parameters | object | {"type": "object", "properties": {}} | a JSON schema | The arguments the LLM fills in. |
method | string | POST | GET, POST | The HTTP method. |
url | string | required | Where the request goes. | |
headers | object | {} | string keys and values | Sent with every request, for example an Authorization header. |
pre_call_message | string | "" | Said just before the request runs, every time the tool is called. Leave empty to stay silent. | |
timeout_seconds | number | 8.0 | 1 to 30 | How long to wait for your response. |
{
"tools": [
{
"name": "check_availability",
"description": "Check which appointment slots are free for a doctor on a date. Use before offering any time to the caller.",
"parameters": {
"type": "object",
"properties": {
"doctor": {"type": "string", "description": "The doctor's name"},
"date": {"type": "string", "description": "YYYY-MM-DD"}
},
"required": ["doctor", "date"]
},
"method": "POST",
"url": "https://clinic.example.com/voxa/availability",
"headers": {"Authorization": "Bearer clinic_secret_token"},
"pre_call_message": "One moment, let me check.",
"timeout_seconds": 8
}
]
}
Header values are stored with the agent and returned by
GET /api/v1/agents/{id}to anyone who can view agents. They are masked in the audit log. Use a token made only for Voxa, and check it on your server.
The request Voxa sends
POST
The body is JSON: the arguments the LLM filled in, plus a _call object that tells you which call it is.
POST /voxa/availability HTTP/1.1
Host: clinic.example.com
Content-Type: application/json
Authorization: Bearer clinic_secret_token
{
"doctor": "Dr. Kulkarni",
"date": "2026-10-02",
"_call": {
"call_id": "00b1d622a8f04f4f8f3a3c2d9e5b7a11",
"agent_id": "1b81a241c0e44f5c9d1f0e2a7c3b9d10",
"from": "+918035001234",
"to": "+919812345678",
"user_data": {"customer_name": "Rohan", "patient_id": "P-1042"}
}
}
_call field | Meaning |
|---|---|
call_id | The Voxa call id. Use it to match the request to the call record and webhooks. |
agent_id | The agent’s id. |
from | The call’s from_number: your number on outbound calls, the caller’s number on inbound calls. Empty on browser calls. |
to | The call’s to_number: the customer on outbound calls, your number on inbound calls. Empty on browser calls. |
user_data | The user_data the call was placed with. Put ids you need here (customer id, order id) so the LLM doesn’t have to say them. |
GET
The arguments are sent as query parameters. _call is not sent with GET.
GET /voxa/availability?doctor=Dr.+Kulkarni&date=2026-10-02 HTTP/1.1
Host: clinic.example.com
Authorization: Bearer clinic_secret_token
Your response
Return JSON with what the agent needs to answer. Keep it small and plain: the LLM reads all of it.
{"date": "2026-10-02", "free_slots": ["10:30", "12:00", "16:15"]}
What the LLM receives:
| Your server | The LLM sees |
|---|---|
| Any status with a JSON body | {"status": 200, "result": {...your JSON...}} |
| Any status with a non-JSON body | {"status": 200, "result": "first 2000 characters of the body"} |
| A timeout or connection error | {"status": 0, "error": "the check_availability service did not answer"} |
The status is passed through, so a 404 or 500 reaches the LLM as it is. Voxa does not retry a tool call. Say in the prompt what the agent should do when a tool fails (“If the booking fails, apologise and say the clinic will call back”).
Your endpoint runs while the caller waits. Answer in under two seconds where you can, and set
pre_call_messagefor anything slower.
The built-in end_call
Every agent has an end_call tool. You don’t add it, and you can’t add a tool with that name.
- The LLM is told to use it only when the conversation is complete, and to say goodbye first, in the same reply.
- When the LLM calls it, the goodbye finishes playing and then the line drops. The call ends with
hangup_by: "agent"andhangup_reason: "agent ended the call". hangup_messageis not added: the LLM’s own goodbye is the last thing said.
For a rule-based way to end calls, see Hang up using a prompt.
Testing a tool
Run the same request a live call would make, without placing a call. In the console, use Run test on the Tools tab. Through the API:
curl -X POST https://voxa.abhinavyadav.in/api/v1/tools/test \
-H "X-API-Key: $VOXA_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"tool": {
"name": "check_availability",
"description": "Check free slots",
"url": "https://clinic.example.com/voxa/availability",
"headers": {"Authorization": "Bearer clinic_secret_token"}
},
"args": {"doctor": "Dr. Kulkarni", "date": "2026-10-02"}
}'
{
"ok": true,
"status": 200,
"elapsed_ms": 184,
"response": {"date": "2026-10-02", "free_slots": ["10:30", "12:00", "16:15"]},
"sent_to_model": {"status": 200, "result": {"date": "2026-10-02", "free_slots": ["10:30", "12:00", "16:15"]}}
}
In a test, _call has call_id and agent_id set to "test", empty numbers and empty user_data. The test needs the agents.edit permission.
On the call record
- Transcript. Each tool call adds a turn with
"role": "tool"and text likecheck_availability({'doctor': 'Dr. Kulkarni', 'date': '2026-10-02'}). - Timeline and webhooks.
tool.callcarriesname,argsandurl.tool.resultcarriesname,status, theresult(cut to about 3000 characters) andlatency_ms; itsleveliswarningwhen the status isn’t 2xx.end_callproduces atool.callevent only. See Webhook events. - If the LLM calls a tool that doesn’t exist, it gets
{"error": "unknown tool"}and anerrorevent is recorded.