zoxaAI
Homepage
API ReferenceTools

Inline tool format

The canonical CallConfigSnapshot.tools[] shape — used when you pass an inline transient agent on POST /api/v1/call. Differs from the saved-tool format on every typed tool.

Every agent config's tools[] array — on a saved agent (POST /agents) or a transient inline agent (POST /api/v1/call) — can mix two kinds of entries:

EntryWhat it does
UUID string — e.g. "abc12345-..."A reference to a saved tool. The runner fetches it from the DB and registers it.
Inline tool dict — { type, name, description, config }A one-off tool defined inline. Validated against the canonical schema below.

This page documents the inline dict shape. For the saved-tool (POST /tools) shape, see Tool types.

The inline shape is NOT the saved-tool shape

The two formats differ on function (nested server block vs. flat fields), on transferCall (no messageType/audioRecordingId), and on query (knowledgeBases instead of groups). Don't copy a definition.config from GET /tools/{tool_uuid} and paste it as an inline tool — it will fail validation.

Common envelope

Every inline tool dict has these fields:

FieldTypeRequiredNotes
typeenum✓One of function, endCall, transferCall, query, testTool. (No calculator — that's a saved-tool only category today.)
namestring✓ for function/query/transferCall/testTool1..64 chars, [A-Za-z0-9_-]+. The function name the LLM sees. For endCall, defaults to "end_call". Names must be unique after sanitization; end_call / retrieve_from_knowledge_base / safe_calculator are reserved (endCall tools may use end_call).
descriptionstring—Function description shown to the LLM.
configobject✓Type-specific config — schemas below.

Unknown fields are rejected (extra="forbid" on every config). Send only what's documented.


function — HTTP API

Call an external HTTP API. The LLM produces an arguments object matching parameters; the runner makes the HTTP request.

{
  "type": "function",
  "name": "lookup_order",
  "description": "Look up the status of an order by id.",
  "config": {
    "server": {
      "url": "https://api.acme.com/orders/{{orderId}}",
      "method": "GET",
      "headers": { "X-Tenant": "acme" },
      "timeout_ms": 5000
    },
    "parameters": {
      "type": "object",
      "properties": {
        "orderId": { "type": "string", "description": "Order ID like ORD-42." }
      },
      "required": ["orderId"]
    },
    "async": false,
    "messages": ["Looking that up...", "One moment..."]
  }
}

config fields

FieldTypeRequiredNotes
server.urlstring✓Must start with http:// or https://. Supports {{contextVariable}} substitution. Private/loopback/link-local IPs and a blocked-host list are rejected.
server.methodenum—GET, POST (default), PUT, PATCH, DELETE.
server.headersobject—Static headers merged into every request. Supports {{contextVariable}} in values.
server.timeout_msint—Request timeout in ms. 1000..30000. Default 10000.
parametersobject | null—A JSON Schema object ({type, properties, required, ...}) describing the arguments the LLM may produce. Pass null (or omit) for a no-arg tool.
asyncbool—When true, the runner fires the request and returns control to the LLM immediately without waiting for a response. Default false.
messagesstring[]—Filler phrases the agent speaks before the request runs (e.g. "Let me check that..."). ONE per invocation, picked per messagesOrder. Max 5; [] (default) is silent. Sent via TTSSpeakFrame.
messagesOrder"random" / "in-order""random"random = uniform pick among all variants; in-order = invocation K speaks entry K (wrapping around) within the call.

`parameters` is JSON Schema, not a list

The saved-tool format uses parameters: [{name, type, description, required}, ...]. The inline format takes a raw JSON Schema dict — same shape OpenAI/Anthropic use for function tools. The conversion happens in the saved-tool path; inline tools skip the conversion and need real JSON Schema.


endCall — let the LLM hang up

Lets the LLM explicitly end the call. Optional goodbye message.

{
  "type": "endCall",
  "name": "end_call",
  "description": "End the call when the caller says goodbye.",
  "config": {
    "messageType": "custom",
    "customMessages": ["Thanks for calling. Goodbye!", "Take care — bye!"]
  }
}

config fields

FieldTypeRequiredNotes
messageType"none" / "custom" / "audio"—Default "custom" (speaks one of customMessages, defaulting to ["Goodbye!"]). "none" still speaks a built-in farewell — there is no silent-hangup option.
customMessagesstring[]≥1 non-blank entry required when messageType="custom"Farewell variants — ONE is chosen at random each time the farewell plays. Max 5 entries.
audioRecordingIdstringrequired when messageType="audio"UUID of a pre-recorded audio file.

No `endCallReason` field

The end reason is captured automatically (agent_hangup, user_hangup, max_duration, silence_timeout, voicemail, disconnect, error) on CallModel.end_reason — no LLM tokens needed and nothing to configure on the tool.


transferCall — transfer to a phone or SIP endpoint

{
  "type": "transferCall",
  "name": "transfer_to_billing",
  "description": "Transfer the call to billing when the caller asks for refunds.",
  "config": {
    "destination": "+14155550199",
    "customMessage": "Transferring you to billing now.",
    "timeout": 30,
    "recordTransfer": true
  }
}

config fields

FieldTypeRequiredNotes
destinationstring✓E.164 number (+1...) or SIP endpoint (PJSIP/123, SIP/123).
customMessagestring | null—Optional phrase spoken right before transfer.
timeoutint (5..300)—Seconds to wait for the destination to answer. Default 30.
recordTransferbool—Default true. Carrier-side recording of the transfer leg (caller ↔ destination), attached to the call record as a transfer recording. Ignored when full-call carrier recording (recordInProviderConsole) is on.

Inline `transferCall` has no `messageType` / `audioRecordingId`

The saved-tool format accepts messageType and audioRecordingId. The inline format does not — the schema rejects them. If you need an audio pre-transfer message, save a transferCall tool via POST /tools and reference it by UUID instead.

Every telephony provider zoxaAI supports accepts transferCall — Twilio, Vobiz, Asterisk ARI, Plivo, Vonage, Telnyx, Cloudonix, and Tata Smartflo. Web calls (WebRTC / WebSocket) cannot transfer. If a transfer can't proceed — a web call, or a provider whose transfer credentials aren't configured — the runner rejects it with reason: "provider_not_supported"; the LLM is told and can verbalize the failure to the caller rather than going silent.


query — knowledge-base retrieval

Retrieve from one or more named KB buckets during the call.

{
  "type": "query",
  "name": "knowledge_base",
  "description": "Search the knowledge base for product / billing answers.",
  "config": {
    "knowledgeBases": [
      {
        "name": "billing",
        "description": "Pricing, refunds, plan changes.",
        "documentIds": ["doc-uuid-1", "doc-uuid-2"]
      },
      {
        "name": "support",
        "description": "Troubleshooting and how-to.",
        "documentIds": ["doc-uuid-3"]
      }
    ],
    "messages": ["One moment, checking that..."],
    "timeout": 5
  }
}

config fields

FieldTypeRequiredNotes
knowledgeBasesarray✓One or more named buckets (min_length=1).
knowledgeBases[].namestring✓Short bucket label — the LLM sees this as the function-argument value.
knowledgeBases[].descriptionstring✓Tells the LLM when to query this bucket.
knowledgeBases[].documentIdsstring[]✓Document UUIDs from GET /files. Must be non-empty.
messagesstring[]—Filler variants spoken during retrieval — ONE per lookup, picked per messagesOrder. Max 5; [] is silent.
messagesOrder"random" / "in-order""random"random = uniform pick among all variants; in-order = sequential within a call, wrapping around.
timeoutint (1..30)—Per-query timeout in seconds. Default 5.

`knowledgeBases`, not `groups`

The saved-tool format uses groups[]. The inline format uses knowledgeBases[]. Same fields inside each entry (name, description, documentIds) — only the outer key differs.


testTool — simulated tool for rehearsal

Exercises the full tool-calling flow — invocation, dead air, success/failure handling — without a real backend. The description drives when the LLM calls it; execution is a configurable delay followed by the configured outcome.

{
  "type": "testTool",
  "name": "simulate_crm_lookup",
  "description": "Look up the caller's CRM record when they ask about their account.",
  "config": {
    "delaySeconds": 6.0,
    "outcome": "success",
    "responseText": "Account active, premium tier.",
    "messages": ["Let me pull up your account.", "One moment please."]
  }
}

config fields

FieldTypeRequiredNotes
delaySecondsnumber (0.5..30)—Simulated execution time. Default 6. Realism task sound plays over it like any real tool.
outcome"success" / "failure"—Default "success".
responseTextstring—Returned as the result payload (success) or error detail (failure). Blank = a built-in steering message the agent relays naturally.
messagesstring[]—Filler variants spoken as the simulated execution starts — ONE per invocation, picked per messagesOrder, same as every other tool. Max 5; [] (default) is silent.
messagesOrder"random" / "in-order""random"random = uniform pick among all variants; in-order = sequential within a call, wrapping around.

Complete inline example

A transient agent on POST /api/v1/call with one of each inline tool:

{
  "type": "outbound",
  "callConfig": {
    "provider": "twilio",
    "auth": { "accountSid": "AC...", "authToken": "..." },
    "phoneNumber": "+14155550100"
  },
  "toNumber": "+14155550199",
  "agent": {
    "name": "Aria from Acme",
    "systemPrompt": "You are Aria from Acme. Be concise.",
    "greeting": { "firstMessages": ["Hi, this is Aria from Acme — how can I help?"] },
    "llm": { "provider": "openai", "model": "gpt-5.4-mini" },
    "tts": { "provider": "cartesia" },
    "stt": { "provider": "soniox" },
    "tools": [
      {
        "type": "function",
        "name": "lookup_order",
        "description": "Look up the status of an order by id.",
        "config": {
          "server": {
            "url": "https://api.acme.com/orders/{{orderId}}",
            "method": "GET",
            "headers": { "X-Tenant": "acme" },
            "timeout_ms": 5000
          },
          "parameters": {
            "type": "object",
            "properties": { "orderId": { "type": "string" } },
            "required": ["orderId"]
          },
          "messages": ["Let me look that up..."]
        }
      },
      {
        "type": "endCall",
        "name": "end_call",
        "description": "End when the caller says goodbye.",
        "config": { "messageType": "custom", "customMessages": ["Thanks for calling Acme. Goodbye!"] }
      },
      {
        "type": "query",
        "name": "knowledge_base",
        "description": "Search the KB for billing / product answers.",
        "config": {
          "knowledgeBases": [
            { "name": "billing", "description": "Pricing, refunds.", "documentIds": ["doc-uuid-1"] }
          ]
        }
      },
      "abc12345-6789-0123-4567-89abcdef0123"
    ]
  }
}

The last entry — a UUID string — references a saved tool, mixed freely with the inline dicts.

See also

On this page