zoxaAI
Homepage
API ReferenceExamples

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

  1. Validates the auth probe against Twilio (account SID + token).
  2. Provisions the webhook on Twilio's number so future calls reach us.
  3. Validates the full inline agent — every tool, range, and language pairing — and rejects with structured 400 details on any failure.
  4. Snapshots the resolved config onto the binding row (defaults materialized, end_call seeded).
  5. 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

PatternFieldsWhen
Personalize per callagentId + contextVariables filling {{placeholders}}Custom greeting/prompt per customer without making 1000 agents.
One-off config changeFetch config → tweak → send inline as agentA different voice or temperature for a single call.
Multiple LLM personalitiesOne saved agent per personality, dispatch via your own routerWhen personalities diverge beyond what placeholders cover.
Throttle long callsmaxCallDurationSReminder calls: 120. Support: 600.
Aggressive idle handlinguserIdleTimeoutS: 4 + userIdleMaxRetries: 1Outbound where silence usually means the user wandered off.
Lenient idle handlinguserIdleTimeoutS: 15 + userIdleMaxRetries: 3Inbound support — the caller may be looking up account info.
Human-feel callsEnable backgroundSound + fillerWordsCuts the "obviously a bot" tells on latency and dead air.

On this page