Skip to content
VoxaDocs
Navigation
Open console →

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

  1. You describe each tool: a name, when to use it, and a JSON schema of its arguments.
  2. During the call, the LLM decides to use a tool and fills in the arguments.
  3. If the tool has a pre_call_message, the agent says it (“One moment, let me check.”).
  4. Voxa sends the HTTP request to your url and waits up to timeout_seconds.
  5. 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.

FieldTypeDefaultAllowedWhat it does
namestringrequiredletters, digits and _; starts with a letter or _; 1 to 64 characters; unique in the agent; not end_callThe function name the LLM calls.
descriptionstringrequired3 to 1000 charactersWhen and why to use the tool. The LLM reads this to decide.
parametersobject{"type": "object", "properties": {}}a JSON schemaThe arguments the LLM fills in.
methodstringPOSTGET, POSTThe HTTP method.
urlstringrequiredWhere the request goes.
headersobject{}string keys and valuesSent with every request, for example an Authorization header.
pre_call_messagestring""Said just before the request runs, every time the tool is called. Leave empty to stay silent.
timeout_secondsnumber8.01 to 30How 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 fieldMeaning
call_idThe Voxa call id. Use it to match the request to the call record and webhooks.
agent_idThe agent’s id.
fromThe call’s from_number: your number on outbound calls, the caller’s number on inbound calls. Empty on browser calls.
toThe call’s to_number: the customer on outbound calls, your number on inbound calls. Empty on browser calls.
user_dataThe 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 serverThe 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_message for 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" and hangup_reason: "agent ended the call".
  • hangup_message is 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 like check_availability({'doctor': 'Dr. Kulkarni', 'date': '2026-10-02'}).
  • Timeline and webhooks. tool.call carries name, args and url. tool.result carries name, status, the result (cut to about 3000 characters) and latency_ms; its level is warning when the status isn’t 2xx. end_call produces a tool.call event only. See Webhook events.
  • If the LLM calls a tool that doesn’t exist, it gets {"error": "unknown tool"} and an error event is recorded.
Esc