Full-configuration reference
Fully-populated agent and POST /call bodies showing every field with realistic values. Use as a reference to see what's available.
The example pages above show minimal working configs. This page shows the opposite — every available field populated so you can see the full surface. Don't copy this blob as-is; copy only the fields you actually need.
How to read this
Each block below is a complete working request. Inline comments call out why you'd set the field and what happens if you leave it at default. Per-field ranges live on the Agent Config schema page.
1. Full agent body (POST /agents)
{
// --- Identity & metadata ----------------------------------------------------
"name": "Acme Outbound Voice Agent", // 1..120 chars. Shown in dashboard list & call history.
"description": "Calls leads to qualify B2B SaaS demand.",
// --- Languages — ORDER IS PRIORITY ------------------------------------------
// languages[0] is the primary; the rest are code-switch hints. Every entry
// must be supported by BOTH the chosen STT and TTS model or the save fails.
"languages": ["en", "hi"],
// --- Conversation behavior --------------------------------------------------
"systemPrompt": "You are a polite, concise voice agent for Acme calling {{customerName}}. Ask one question at a time and never improvise pricing.",
"timezone": "Asia/Kolkata", // IANA zone; drives {{current_time}}/{{current_date}}.
"greeting": {
"firstMessages": [ // Up to 5 VARIANTS — one picked at random per call.
"Hi, this is Aria from Acme — do you have a moment?",
"Hello! Aria here from Acme. Is now a good time?"
], // [] = the LLM improvises the opener.
"interruptible": false, // true = caller can talk over the opener.
"speakFirst": "agent", // "agent" | "user". Use "user" for inbound where the caller has the agenda.
"agentDelayS": 0.0, // 0..4 s padding before the opener — useful for telephony jitter.
"userTimeoutS": 3.0 // 1..5 s to wait for the caller (speakFirst="user") before opening anyway.
},
// --- Language model ---------------------------------------------------------
"llm": {
"provider": "openai", // See GET /agents/models/available for the live catalog.
"model": "gpt-5.4-mini",
"temperature": 0.7, // 0..2 (Anthropic clamps to 0..1 on save).
"maxTokens": 251, // Reply-length rail — a spoken turn is a sentence or two.
"prewarm": true // Hidden warm-up request at call start; big first-turn latency win.
},
// --- Transcriber (STT) ------------------------------------------------------
// Top-level provider/model + one tuning block per provider. Only the block
// matching `provider` is used; the others may stay (handy when switching).
"stt": {
"provider": "soniox", // soniox | flux | assemblyai | sarvam | cartesia
"model": null, // null = provider default (soniox → stt-rt-v5).
"interruptionMinWords": 0, // THE barge-in knob. 0 = instant (default); N = caller needs N words to cut in.
"soniox": {
"contextTerms": "Acme, zoxa", // Comma-separated recognition bias.
"endpointSensitivity": 0.3, // -1..1 — higher ends the turn faster.
"endpointLatencyAdjustmentLevel": 2, // 0..3 — accuracy-for-speed trade.
"maxEndpointDelayMs": 2000 // Hard cap on post-pause wait.
},
"assemblyai": {
"mode": "balanced", // balanced | min_latency | max_accuracy
"keyterms": "",
"voiceFocus": "near-field", // off | near-field | far-field — provider-side speaker isolation.
"voiceFocusThreshold": 0.9 // 0..1 isolation aggressiveness.
}
// flux / sarvam / cartesia blocks — see the schema page.
},
// --- Voice (TTS) ------------------------------------------------------------
"tts": {
"provider": "elevenlabs", // elevenlabs | cartesia | smallest | sarvam | xai | inworld
"model": "eleven_flash_v2_5", // null = provider default.
"voice": "7qBNUtXRGP0jPi0H4r8k", // null = provider default voice.
"elevenlabs": {
"stability": 0.4,
"similarityBoost": 0.75,
"style": 0.0,
"useSpeakerBoost": true,
"speed": 1.0, // 0.7..1.2 for ElevenLabs.
"autoMode": true,
"applyTextNormalization": "auto" // auto | on | off
}
// cartesia / smallest / sarvam / xai / inworld blocks — see the schema page.
},
// --- Input cleanup ----------------------------------------------------------
"noiseGate": {
"enabled": true, // On by default.
"model": "voice-focus", // voice-focus (isolate nearest speaker) | noise-suppression (general cleanup)
"enhancementLevel": 0.8 // 0..1 processing strength.
},
// --- Human-feel audio (all OFF by default) ----------------------------------
"backgroundSound": { "enabled": true, "sound": "library", "volume": 0.08 },
"taskSound": { "enabled": true, "sound": "typing", "volume": 0.35, "startAfterMs": 800 },
"acknowledgeSounds": { "enabled": false, "words": "hmm, hm hmm, okay", "wordOrder": "round-robin", "volume": 1.0, "thresholdS": 3.0, "probability": 0.8 },
"fillerWords": { "enabled": true, "words": "so, yeah, okay", "wordOrder": "round-robin", "thresholdMs": 300, "probability": 0.75 },
// --- Call & idle guards (flat fields) ----------------------------------------
"maxCallDurationS": 300, // 10..7200, multiple of 10. Ends with endedReason="max_duration".
"userIdleTimeoutS": 7, // 3..25 s of caller silence before a re-engage line.
"userIdleMessages": [ // Escalating lines: retry N speaks entry N-1.
"Are you still there?",
"Hello? I'll have to hang up soon if I can't hear you."
],
"userIdleMaxRetries": 2, // 1..5 attempts, then endedReason="silence_timeout".
// --- Tools: saved-tool UUIDs and/or inline definitions -----------------------
// An endCall tool is MANDATORY — if you omit one, the default is auto-seeded.
"tools": [
"abc12345-6789-0123-4567-89abcdef0123", // UUID reference to a saved tool.
{
"type": "endCall",
"name": "end_call",
"description": "End the call when the caller indicates they're done.",
"config": {
"messageType": "custom", // custom | audio | none
"customMessages": [ // Farewell VARIANTS — one picked at random.
"Thanks for your time. Have a great day!",
"Thanks for talking with me — goodbye!"
]
}
}
],
// --- Telephony behavior ------------------------------------------------------
"voicemailDetection": true, // Detect answering machines on outbound calls.
"recordInProviderConsole": false, // Also record on the carrier side (Twilio/Vobiz console).
// --- Lifecycle webhook + summary ---------------------------------------------
"webhook": {
"url": "https://your.app/zoxa/webhook", // http(s):// object form only — no bare string.
"headers": { "X-Tenant": "acme", "X-Env": "prod" }
},
"enableSummarization": true, // AI summary on the call row + in call.completed webhook.
// --- Saved {{var}} defaults ---------------------------------------------------
// The bottom layer of call-time substitution; per-call contextVariables
// override non-blank; system built-ins always win.
"contextVariables": { "customerName": "there" },
// --- Sibling, write-only (NOT part of the config) ----------------------------
"webhookSecret": "whsec_your_hmac_key" // HMAC key for webhook signatures. Never returned.
}2. Full outbound POST /call body
{
"type": "outbound",
// --- Provider & credentials (inline) ---------------------------------------
"callConfig": {
"provider": "twilio", // twilio | vobiz | plivo | vonage | telnyx
"phoneNumber": "+14155550100", // Your number — the FROM side.
"auth": {
"accountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"authToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
},
// ...or reference a dashboard-saved telephony configuration instead:
// "telephonyConfigurationId": 12, // XOR callConfig — exactly one.
"toNumber": "+14155559999", // Required for outbound. E.164.
// --- Agent selection — exactly ONE -------------------------------------------
"agentId": "550e8400-e29b-41d4-a716-446655440000", // Saved agent...
// "agent": { /* ...or a full inline Agent Config (section 1, minus webhookSecret) */ },
// --- Per-call {{var}} values --------------------------------------------------
"contextVariables": {
"customerName": "Aman",
"orderId": "ORD-42",
"tier": "premium"
}
}There is no per-call overrides field and no top-level webhookUrl — the webhook lives inside the agent config, and per-call config variation is done by sending a tweaked config inline as agent (see outbound calls).
Inbound variant (with a saved agent)
{
"type": "inbound", // No toNumber — the DID IS callConfig.phoneNumber.
"callConfig": {
"provider": "vobiz",
"phoneNumber": "+917971542879",
"auth": {
"authId": "MA_xxxxxxxxxx",
"authToken": "xxxxxxxxxxxxxxxxxxxxx",
"applicationId": "30378500269436896" // Vobiz-specific. Omit to use the account's default app.
}
},
"agentId": "550e8400-...",
"contextVariables": { "lineLabel": "support-hotline" }
}3. Inbound + inline transient agent + inline tools (the kitchen-sink request)
The most expressive single request the API accepts — registers an inbound number and ships a complete agent definition inline (no saved entities needed), bundling every inline tool type.
{
"type": "inbound",
"callConfig": {
"provider": "twilio",
"phoneNumber": "+14155550100",
"auth": {
"accountSid": "ACxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
"authToken": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
}
},
// --- Inline transient agent (no agentId) -------------------------------------
"agent": {
"name": "Acme inbound triage",
"description": "Triages inbound calls and routes to humans when needed.",
"languages": ["en"],
"systemPrompt": "You are the first-line agent for Acme. Greet the caller warmly, ask what they need, and route them. Use lookup_order for order questions and the knowledge base for billing questions. If the caller asks for a human, transfer them.",
"greeting": {
"firstMessages": ["Hello, this is Acme — how can I help you today?"],
"interruptible": true
},
"llm": { "provider": "openai", "model": "gpt-5.4-mini", "temperature": 0.7 },
"tts": { "provider": "elevenlabs", "voice": "7qBNUtXRGP0jPi0H4r8k" },
"stt": { "provider": "soniox", "interruptionMinWords": 2 },
"tools": [
// 3.1 — HTTP function tool. The LLM picks parameter values; zoxaAI makes the call.
{
"type": "function",
"name": "lookup_order",
"description": "Look up the status of an order by id. Use when the caller mentions an order.",
"config": {
"server": {
"url": "https://api.acme.com/orders/{{orderId}}", // {{var}} substitution works in URLs.
"method": "GET", // GET | POST | PUT | PATCH | DELETE
"headers": { "X-Tenant": "acme" },
"timeout_ms": 5000 // 1000..30000. Default 10000.
},
"parameters": { // JSON Schema for the LLM's arguments; null = no-arg tool.
"type": "object",
"properties": {
"orderId": { "type": "string", "description": "Order ID like ORD-42." }
},
"required": ["orderId"]
},
"async": false, // true = fire-and-forget.
"messages": [ // Filler lines spoken before the request — SEQUENTIAL per
"Let me look that up...", // invocation, wrapping around. [] = silent.
"One second, checking again..."
]
}
},
// 3.2 — Transfer-call tool. Telephony calls only; web calls reject it at runtime.
{
"type": "transferCall",
"name": "transfer_to_billing",
"description": "Transfer the caller to billing for refund questions.",
"config": {
"destination": "+14155550199", // E.164 or SIP endpoint.
"customMessage": "Transferring you to a human teammate now.",
"timeout": 30, // 5..300 s ring duration.
"recordTransfer": true // Carrier-side recording of the transfer leg.
}
},
// 3.3 — Knowledge-base query tool over named document buckets.
{
"type": "query",
"name": "search_kb",
"description": "Search the KB for billing or product questions.",
"config": {
"knowledgeBases": [
{
"name": "billing",
"description": "Pricing, refunds, plan changes. Use when the caller asks about money.",
"documentIds": ["doc-uuid-billing-overview", "doc-uuid-refund-policy"]
}
],
"messages": ["One moment — checking that..."],
"timeout": 5 // 1..30 s per query.
}
},
// 3.4 — End-call tool (mandatory — auto-seeded if you omit it).
{
"type": "endCall",
"name": "end_call",
"description": "End the call only when the caller clearly says goodbye.",
"config": {
"messageType": "custom",
"customMessages": ["Thanks for calling Acme. Have a great day!"]
}
},
// 3.5 — Test tool: simulated backend for rehearsing tool flows.
{
"type": "testTool",
"name": "simulate_crm_lookup",
"description": "Look up the caller's CRM record. Use when they ask about their account.",
"config": {
"delaySeconds": 6.0, // 0.5..30 s of realistic dead air.
"outcome": "success", // success | failure
"responseText": "Account is active, premium tier, renewal on Nov 3."
}
},
// 3.6 — UUID reference to a saved tool. Mix freely with inline objects.
"abc12345-6789-0123-4567-89abcdef0123"
],
"webhook": { "url": "https://your.app/zoxa/inbound-hook", "headers": { "X-Tenant": "acme" } },
"enableSummarization": true
},
// --- Baked into the binding's snapshot (applies to every inbound call) --------
"contextVariables": {
"lineLabel": "support-hotline",
"supportEmail": "[email protected]"
}
}What zoxaAI does with this request
- Validates the auth probe against Twilio (account SID + token).
- Provisions the webhook on Twilio's number so future calls reach us.
- Validates the full inline agent — every tool, range, and language pairing — and rejects with structured
400details on any failure. - Snapshots the resolved config onto the binding row (defaults materialized,
end_callseeded). - Upserts the binding keyed on
(provider, phoneNumber).
Re-POSTing replaces the whole snapshot
Inbound registration is last-write-wins on the full agent snapshot. A partial re-POST doesn't merge — fields you omit reset to schema defaults. Always send the complete agent you want live on the number.
4. Full WebSocket POST /call body
{
"transport": "websocket", // Also the default when omitted.
"agentId": "550e8400-...",
"contextVariables": { "session": "ws-test-1" }
}The wire format is fixed — send 16 kHz PCM-16 mono, receive 24 kHz PCM-16 mono. There is nothing to declare.
5. Full WebRTC POST /call body
{
"transport": "webrtc", // Required — default is websocket.
"agentId": "550e8400-...",
"contextVariables": {
"userId": "u_42",
"currentPage": "/dashboard/orders/4711"
}
}6. Full inbound binding PATCH body
Swap the agent on an existing binding without re-provisioning the provider (credentials and webhook URL stay):
{
// Either point at a saved agent:
"agentId": "different-agent-uuid",
// ...or ship a fully-inline agent:
// "agent": { /* full Agent Config, see section 1 */ },
// Baked into the stored snapshot (system vars still resolve per call):
"contextVariables": { "campaign": "winter-2026" }
}Rules: send exactly one of agent / agentId. Model ids are checked against the live catalog at this boundary (422 on unknown models).
Common patterns you'll actually use
| Pattern | Fields | When |
|---|---|---|
| Personalize per call | agentId + contextVariables filling {{placeholders}} | Custom greeting/prompt per customer without making 1000 agents. |
| One-off config change | Fetch config → tweak → send inline as agent | A different voice or temperature for a single call. |
| Multiple LLM personalities | One saved agent per personality, dispatch via your own router | When personalities diverge beyond what placeholders cover. |
| Throttle long calls | maxCallDurationS | Reminder calls: 120. Support: 600. |
| Aggressive idle handling | userIdleTimeoutS: 4 + userIdleMaxRetries: 1 | Outbound where silence usually means the user wandered off. |
| Lenient idle handling | userIdleTimeoutS: 15 + userIdleMaxRetries: 3 | Inbound support — the caller may be looking up account info. |
| Human-feel calls | Enable backgroundSound + fillerWords | Cuts the "obviously a bot" tells on latency and dead air. |
Related
- Quickstart — the minimal version of all this
- Example agents — pre-built configs by use case
- Agent config — field-by-field reference
- Call config — per-provider
callConfig.authshapes