VoxPro Developer Docs

Everything you need to build, launch, and integrate AI voice assistants — outbound campaigns, inbound routing, live tool calls, post-call webhooks, and more.

REST API Webhook Tools Automations LiveKit SIP Knowledge Base Blast Campaigns

Introduction

VoxPro is a real-time AI voice platform. An Assistant defines how the AI sounds and behaves; a Call instantiates that assistant for a specific customer. Calls run on LiveKit rooms connected via SIP trunks.

1
Create
Assistant
2
Upload
Knowledge
3
Launch
Call / Blast
4
Tools &
Automations
5
Review
Logs

Key concepts

Assistant

A saved configuration: voice, LLM model, system prompt, first message, tools, and knowledge base. Identified by a unique id.

Room

A LiveKit room created per call. roomName is the primary key used in call logs, campaign jobs, and recordings.

Blast Campaign

A set of outbound calls placed concurrently from a CSV list, with optional automatic retry on unanswered / voicemail.

Webhook Tool

An HTTP endpoint you own. The AI calls it mid-conversation to look up data or trigger actions. You return a plain-text response.

Authentication

All API endpoints require a Bearer token in the Authorization header. Obtain one by logging in via the VoxPro UI — the token is stored in localStorage as voxpro.session.

Request header
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
JavaScript — read token from localStorage
const session = JSON.parse(localStorage.getItem("voxpro.session") || "null");
const token = session?.token;

const res = await fetch("/assistants", {
  headers: { Authorization: `Bearer ${token}` }
});
const { assistants } = await res.json();
💡
For FormData uploads (files), set only the Authorization header — omit Content-Type so the browser sets the correct multipart/form-data boundary automatically.

Roles

RoleAccess
adminFull access to all assistants, campaigns, logs, tools, customers, and sounds.
user / clientAccess scoped to their organization's assistants and logs.

Base URL & Headers

Base URL
https://<your-voxpro-host>
# e.g. https://ai.yourcompany.com
HeaderValueRequired
AuthorizationBearer <token>required
Content-Typeapplication/jsonFor JSON bodies
Content-Typeomit (let browser set it)For file uploads
📝
All responses are JSON unless noted otherwise (recordings return audio/wav with Range support).

Assistants — Overview

An Assistant is the blueprint for every AI voice call. It stores voice, LLM, prompt, knowledge, tools, and recording settings in one object persisted to disk (or Postgres if configured).

Assistants — Configuration Fields

Identity

FieldTypeDescription
idstringAuto-generated unique ID (read-only after creation)
namestringInternal identifier name
displayNamestringShown to callers / UI
industrystringe.g. "finance", "healthcare"
clientIdstring|nullLinks assistant to a customer allocation

LLM (Language Model)

FieldTypeDefaultDescription
llmProviderstring"ytl"openai · ytl · anthropic · local
llmModelstring"ilmu-text-free-v2"Model ID for the chosen provider
systemPromptstringFull system prompt. Supports {{variable}} template syntax
firstMessagestringWhat the agent says first. Also supports {{variable}}
temperaturenumber0.4LLM temperature (0.0–2.0)
maxTokensnumber220Max tokens per LLM reply
useCasestring"voiceAssistant"Selects default system prompt template if none set
languagestring"en"ISO 639-1 language code

ASR (Speech Recognition)

FieldTypeDescription
asrProviderstringytl · deepgram · openai · mesolitica
vadSensitivitynumberVoice activity detection threshold (0.0–1.0). Default: 0.62
minUtteranceMsnumberMinimum speech length before transcribing (ms). Default: 240
silenceThresholdMsnumberSilence duration before ending an utterance (ms). Default: 650

TTS (Voice / Speech Synthesis)

FieldTypeDescription
voiceProviderstringytl · openai · elevenlabs · edge · mesolitica · kokoro · others
voicestringVoice ID from GET /voices. For ytl: voice_1/voice_2/voice_3
voiceSettings.stabilitynumberElevenLabs stability (0.0–1.0). Default: 0.45
voiceSettings.similaritynumberElevenLabs similarity boost. Default: 0.82
voiceSettings.speednumberSpeaking rate multiplier. Default: 0.96
voiceSettings.stylenumberElevenLabs style exaggeration. Default: 0.2
backgroundNoisestringFilename from sounds library (e.g. "office_ambient.mp3")
backgroundNoiseVolumenumberMix volume 0.0–1.0. Default: 0.10

Call behaviour

FieldTypeDefaultDescription
allowInterruptionbooleantrueCan the caller speak over the agent?
autoEndOnHangupbooleantrueEnd the agent room when SIP call disconnects
autoEndOnSilenceSecnumber60Hang up after N seconds of silence

Tools & Knowledge

FieldTypeDescription
selectedToolIdsstring[]List of toolId slugs the agent may use during calls
csvLabelstringHuman label for the campaign CSV (shown in dynamic context)
csvFileNamestringOriginal filename of the campaign CSV
promptDataobjectExtra key/value pairs merged into every call's template context

Assistants — REST API

GET /assistants List all accessible assistants

Returns assistants scoped to the authenticated user's organization (or all for admin).

Response 200
{
  "ok": true,
  "assistants": [
    {
      "id": "asst_abc123",
      "name": "Maya",
      "displayName": "Maya — Xanderia",
      "voiceProvider": "ytl",
      "voice": "voice_1",
      "llmProvider": "ytl",
      "firstMessage": "Selamat datang! Saya Maya...",
      "systemPrompt": "You are Maya...",
      "selectedToolIds": ["crm-lookup"],
      "createdAt": "2025-06-01T10:00:00.000Z",
      "updatedAt": "2025-06-15T08:22:00.000Z"
    }
  ]
}
POST /assistants Create a new assistant
Request body
{
  "name": "Maya",
  "displayName": "Maya — Loan Advisor",
  "llmProvider": "ytl",
  "llmModel": "ilmu-text-free-v2",
  "asrProvider": "ytl",
  "voiceProvider": "ytl",
  "voice": "voice_1",
  "language": "ms",
  "temperature": 0.4,
  "maxTokens": 220,
  "firstMessage": "Selamat datang! Saya Maya dari Xanderia.",
  "systemPrompt": "Anda adalah Maya...\n\n{{knowledge_base}}",
  "selectedToolIds": [],
  "allowInterruption": true,
  "autoEndOnSilenceSec": 60
}
Response 200
{ "ok": true, "assistant": { "id": "asst_abc123", "..." } }
PUT /assistants/:id Update an existing assistant

Send only the fields you want to change. All other fields are preserved.

Example — update system prompt only
{ "systemPrompt": "You are Maya v2. {{kb_xanderia_faq}}" }
POST /assistants/:id/duplicate Clone an assistant

Creates a copy with the name appended with (copy). Returns the new assistant object.

Response 200
{ "ok": true, "assistant": { "id": "asst_xyz789", "name": "Maya (copy)", "..." } }
DELETE /assistants/:id Delete an assistant
Response 200
{ "ok": true }

Template Variables

Use {{variable_name}} in systemPrompt and firstMessage. Variables are replaced at call-start with values from the campaign CSV row, the assistant's promptData, and injected system data.

💡
Variable names are case-insensitive and matched by the first key whose lowercase equals the placeholder.

Always-available variables

{{destination}}Dialed number (outbound)
{{customerNumber}}Alias for {{destination}}
{{customer_phone}}Caller number (inbound)
{{customer_name}}Customer name from CSV
{{channel}}voice
{{knowledge_base}}All uploaded KB files concatenated

Knowledge base per-file variables

Each file uploaded to the Knowledge tab also gets its own variable, named kb_ + filename (lowercased, non-alphanumeric chars become underscores).

{{kb_xanderia_faq}}From xanderia_faq.csv
{{kb_pricing}}From pricing.csv
{{kb_procedures}}From procedures.txt

Campaign CSV columns

Every column in the blast CSV row is injected automatically. If your CSV has a LoanAmount column:

System prompt snippet
The customer's outstanding loan amount is RM {{LoanAmount}}.
Their account reference is {{AccountRef}}.

Example — full system prompt with variables

Anda adalah Maya, penasihat pinjaman Xanderia.
Pelanggan: {{customer_name}} ({{destination}})

--- FAQ ---
{{kb_xanderia_faq}}

--- Senarai Harga ---
{{kb_pricing}}

Jawab dalam Bahasa Malaysia sahaja.

Knowledge Base — Overview

Upload CSV or TXT files per assistant. At call-start, all files are read from disk and injected into the LLM prompt as {{knowledge_base}} (all files) or {{kb_<filename>}} (individual file).

Supported formats

FormatParsingVariable name
.csv2-column → Q/A pairs. 3+ columns → key: value blocks per rowkb_<name>
.txtRaw text contentkb_<name>
.docx / .pdf / .xlsxStored but not currently parsed (binary)

CSV format — 2-column FAQ

Question,Answer
"What is the minimum salary?","RM 2,000 per month"
"Maximum loan amount?","Up to RM 150,000"
"How long to approve?","3–5 working days"

Gets rendered in the prompt as:

Q: What is the minimum salary?
A: RM 2,000 per month

Q: Maximum loan amount?
A: Up to RM 150,000

Knowledge Base — REST API

GET /knowledge/:assistantId List uploaded files
Response 200
{
  "files": [
    {
      "name": "xanderia_faq.csv",
      "size": 3840,
      "uploadedAt": "2025-06-20T08:00:00.000Z"
    }
  ]
}
POST /knowledge/:assistantId/upload Upload files (multipart)

Upload up to 20 files at once. Max 10 MB each. Field name: files.

JavaScript fetch example
const fd = new FormData();
fd.append("files", myFile); // can append multiple

const res = await fetch(`/knowledge/${assistantId}/upload`, {
  method: "POST",
  headers: { Authorization: `Bearer ${token}` },
  body: fd  // DO NOT set Content-Type manually
});
const { files } = await res.json();
DELETE /knowledge/:assistantId/:filename Delete a file
Response 200
{ "ok": true, "files": [ /* remaining files */ ] }

Using {{kb_*}} in System Prompts

The variable name is derived from the filename: lowercase, extension stripped, non-alphanumeric characters replaced with underscores.

Uploaded fileTemplate variable
xanderia_faq_knowledge_base.csv{{kb_xanderia_faq_knowledge_base}}
Pricing List 2025.csv{{kb_pricing_list_2025}}
procedures.txt{{kb_procedures}}
Product FAQ (EN).csv{{kb_product_faq_en_}}
💡
In the Knowledge tab, click the teal variable tag next to each file to copy it to your clipboard — then paste directly into the system prompt editor.

Multiple knowledge bases in one prompt

## PRODUCT FAQ
{{kb_xanderia_faq}}

## PRICING
{{kb_pricing_2025}}

## PROCEDURES
{{kb_procedures}}

Use the above context to answer customer questions accurately.
If the answer is not in the reference material, say you will check and call back.

Outbound — Single Live SIP Call

Places one AI-driven outbound call using a SIP trunk connected to a LiveKit room.

POST /outbound/live-sip Place one AI call
FieldRequiredDescription
destinationrequiredE.164 phone number or SIP URI to call
fromoptionalCaller ID to present
assistantrequiredFull assistant object (or reference by id)
rowoptionalCSV row object — keys injected as template variables
campaignIdoptionalAssociate call with a campaign
campaignNameoptionalCampaign label
Request body
{
  "destination": "+60123456789",
  "from": "+60312345678",
  "assistant": {
    "id": "asst_abc123",
    "name": "Maya",
    "voiceProvider": "ytl",
    "voice": "voice_1",
    "llmProvider": "ytl",
    "firstMessage": "Hello {{customer_name}}, this is Maya calling from Xanderia.",
    "systemPrompt": "You are Maya...\n{{kb_faq}}"
  },
  "row": {
    "customer_name": "Ahmad Zaki",
    "LoanAmount": "15000",
    "AccountRef": "XND-2025-0012"
  }
}
Response 200
{
  "success": true,
  "provider": "livekit-sip",
  "roomName": "voxpro-room-1719825600-xnd",
  "destination": "+60123456789",
  "from": "+60312345678",
  "participantIdentity": "sip-participant-abc",
  "trunkId": "ST_abc123",
  "trunkName": "voxpro-main-trunk",
  "callStatus": "dialing"
}

Blast Campaigns

A blast campaign dials a list of numbers concurrently, tracks each job, and can automatically retry unanswered / voicemail calls after a configurable interval.

POST /outbound/live-sip-blast Launch a blast campaign
FieldRequiredDescription
rowsrequiredArray of CSV row objects. Each must contain the destination phone number.
destinationColumnrequiredWhich CSV column holds the phone number
nameColumnoptionalColumn for customer display name
assistantrequiredFull assistant object
fromoptionalCaller ID
concurrencyoptionalSimultaneous calls (1–10). Default: 1
maxAttemptsoptionalTotal call attempts per number (1–10). Default: 1
retryIntervalMinutesoptionalMinutes between retries. Default: 15
campaignNameoptionalLabel shown in the UI
campaignTypeoptionale.g. "loan_collection" — free text tag
Request body
{
  "campaignName": "June Collections",
  "campaignType": "loan_collection",
  "destinationColumn": "PhoneNumber",
  "nameColumn": "CustomerName",
  "from": "+60312345678",
  "concurrency": 3,
  "maxAttempts": 2,
  "retryIntervalMinutes": 20,
  "assistant": { "id": "asst_abc123", "..." },
  "rows": [
    { "PhoneNumber": "+60123456789", "CustomerName": "Ahmad", "LoanAmount": "5000" },
    { "PhoneNumber": "+60198765432", "CustomerName": "Siti",  "LoanAmount": "12000" }
  ]
}
Response 200
{
  "success": true,
  "blast": {
    "id": "blast-1719825600-a1b2c3",
    "campaignName": "June Collections",
    "totalRows": 2,
    "queued": 2,
    "failed": 0,
    "maxAttempts": 2,
    "retryIntervalMinutes": 20
  },
  "results": [
    {
      "index": 0,
      "to": "+60123456789",
      "status": "started",
      "roomName": "voxpro-room-123-abc"
    }
  ]
}
GET /outbound/live-sip-blast/campaigns List all campaigns

Returns campaigns with live stats and recent call logs.

Response 200
{
  "campaigns": [
    {
      "id": "blast-1719825600-a1b2c3",
      "campaignName": "June Collections",
      "stats": {
        "totalTargets": 50,
        "called": 48,
        "active": 3,
        "completed": 32,
        "failed": 5,
        "pending": 2
      }
    }
  ]
}
GET /outbound/live-sip-blast/campaigns/:id Get campaign detail + job status

Returns per-job status, call outcomes, active rooms, and retry state.

Job status values

pending
started
live
completed
retrying
failed
max_retries_reached

Retryable endedReason values

Calls with these ended reasons are eligible for automatic retry:

  • voicemail
  • customer-did-not-answer
  • customer-busy
  • silence-timed-out
  • failed-to-connect
  • call.error-processing-failed

Inbound Routing

Map DIDs (Direct Inward Dialing numbers) to assistants. When your SIP provider routes an inbound call to VoxPro, it POSTs to /inbound/call — VoxPro looks up the DID, starts a LiveKit room, and connects the SIP call.

GET /inbound/numbers List DID → assistant mappings
Response 200
{
  "numbers": [
    {
      "id": "num_abc",
      "number": "+60312345678",
      "label": "Sales hotline",
      "enabled": true,
      "assistantId": "asst_abc123",
      "assistantName": "Maya"
    }
  ]
}
POST /inbound/numbers Create / update a DID mapping
Request body
{
  "number": "+60312345678",
  "label": "Sales hotline",
  "enabled": true,
  "assistantId": "asst_abc123"
}
POST /inbound/call SIP provider webhook — inbound call signal
⚠️
This endpoint is called by your SIP/PBX provider, not by your application. Configure Asterisk/FreeSWITCH to POST here when a call arrives.
FieldDescription
to / toNumber / calledThe DID that was dialed (any alias works)
from / fromNumber / callerCaller's number
callIdOptional call ID for logging
Response 200
{
  "ok": true,
  "roomName": "voxpro-inbound-room-xyz",
  "assistantId": "asst_abc123",
  "assistantName": "Maya",
  "sipUri": "sip:voxpro-inbound-room-xyz@livekit.example.com"
}

Call Lifecycle

Understanding the sequence of events helps you know when to query logs and what to expect from automations.

1
SIP call
dialed
2
LiveKit room
created
3
Agent speaks
first message
4
ASR → LLM
→ TTS loop
5
Call ends /
hangup
6
Log saved /
automations fire

endedReason values

ValueMeaning
customer-ended-callCaller hung up normally
customer-did-not-answerNo answer within ring timeout
customer-busyLine busy
voicemailCall reached voicemail
silence-timed-outAuto-hang-up after silence threshold
failed-to-connectSIP connection error
agent-ended-callAI agent intentionally ended the call
call.error-processing-failedInternal pipeline error

Webhook Tools — Overview

Webhook Tools let the AI call your HTTP endpoint during a conversation to look up live data, trigger actions, or transfer the call. The agent emits a special token in its reply; VoxPro intercepts it, calls your URL, and feeds the response back to the LLM before continuing.

Tool types

webhook

POST / GET to your URL with JSON arguments. You return a JSON or plain-text result. Best for CRM lookups, order status, real-time data.

query

VoxPro searches an uploaded CSV knowledge base locally — no external HTTP call needed. Returns matching rows as text.

transferCall

When the agent detects the trigger, the SIP call is transferred to a configured destination number.

Webhook Tools — Building a Tool

How the agent calls a tool

The LLM emits a token in this format. VoxPro intercepts it before sending TTS:

Agent output token format
[[tool:your-tool-id {"param1": "value", "param2": 123}]]

VoxPro then POSTs to your webhook URL:

Payload sent to your webhook
{
  "toolCallId": "tc_abc123",
  "toolId": "crm-lookup",
  "arguments": {
    "phone_number": "+60123456789",
    "account_ref": "XND-2025-0012"
  }
}

Your webhook response

Return any JSON or plain text. VoxPro converts it to a string and injects it as a TOOL_RESULT message to the LLM:

Your server response (JSON)
{
  "customerName": "Ahmad Zaki",
  "loanBalance": 14850.00,
  "dueDate": "2025-07-01",
  "status": "overdue"
}
💡
Keep responses short and factual — the LLM reads the entire result as context. Long JSON blobs waste tokens. Return only the fields the agent needs.

Configuring a tool

Tool config body — POST /tools/config
{
  "name": "CRM Lookup",
  "toolId": "crm-lookup",
  "description": "Look up a customer's loan status by phone number or account ref",
  "type": "webhook",
  "method": "POST",
  "webhookUrl": "https://yourapi.example.com/voxpro/crm-lookup",
  "bodySchemaJson": "{\"type\":\"object\",\"properties\":{\"phone_number\":{\"type\":\"string\"},\"account_ref\":{\"type\":\"string\"}}}",
  "headersJson": "{\"x-api-key\": \"secret123\"}"
}
📝
After creating the tool, assign its toolId to the assistant's selectedToolIds array. Also instruct the agent in the system prompt on when to call the tool.

System prompt instruction example

When the customer asks about their loan balance or due date,
call the crm-lookup tool with their phone number.
[[tool:crm-lookup {"phone_number": "their number here"}]]

Webhook Tools — REST API

GET /tools/config List all tools
Response 200
{
  "tools": [
    {
      "id": "tool_abc",
      "toolId": "crm-lookup",
      "name": "CRM Lookup",
      "type": "webhook",
      "method": "POST",
      "webhookUrl": "https://yourapi.example.com/crm",
      "description": "Look up customer loan status"
    }
  ]
}
POST /tools/test Test a tool call
Request body
{
  "toolId": "crm-lookup",
  "args": { "phone_number": "+60123456789" }
}
Response 200
{
  "ok": true,
  "toolId": "crm-lookup",
  "name": "CRM Lookup",
  "status": 200,
  "responseText": "{\"customerName\":\"Ahmad\",\"balance\":14850}"
}

Automations — Call Event Webhooks

Automations fire after a call ends (or on other events). Configure a webhook URL and select which fields to include — VoxPro POSTs a structured payload to your system automatically.

Supported trigger events

call.completed
call.escalated
appointment.booked
lead.captured
payment.promised
custom.event

Selectable fields

phoneNumberCaller / dialed number
contactIdContact identifier
roomNameLiveKit room ID
transcriptFull call transcript
replyLast agent reply
callDurationMsCall length in ms
outcomeCall outcome / disposition
transferTargetTransfer destination if escalated
expectedPaymentDatePTP date if captured
matchedCsvRowFull matched CSV row object
matchedCsvRowsAll matching CSV rows
csv.<ColumnName>Individual CSV column value

Automations — Payload Reference

When an automation fires, VoxPro POSTs this JSON to your webhook URL:

Automation webhook payload
{
  "meta": {
    "automationId": "auto_abc123",
    "automationName": "Post-call to CRM",
    "provider": "voxpro",
    "triggerEvent": "call.completed",
    "occurredAt": "2025-06-20T14:32:00.000Z"
  },
  "data": {
    "phoneNumber": "+60123456789",
    "callDurationMs": 185000,
    "outcome": "PTP",
    "transcript": "Agent: Hello Ahmad...\nCustomer: Yes I can pay..."
  },
  "assistant": {
    "id": "asst_abc123",
    "name": "Maya",
    "dataset": "June Collections"
  },
  "resolvedMatchContext": {
    "matchedPhoneNumber": "+60123456789",
    "matchedNric": null,
    "matchedName": "Ahmad Zaki",
    "detectedMonthYear": null
  },
  "matchedCsvRow": {
    "PhoneNumber": "+60123456789",
    "CustomerName": "Ahmad Zaki",
    "LoanAmount": "5000"
  },
  "csv": {
    "PhoneNumber": "+60123456789",
    "CustomerName": "Ahmad Zaki",
    "LoanAmount": "5000"
  }
}

Custom payload

Use customPayload (a JSON string) to restructure the payload for your system. Supports {{field}} interpolation from data:

customPayload example
{
  "contact_phone": "{{phoneNumber}}",
  "call_result": "{{outcome}}",
  "duration_seconds": "{{callDurationMs}}",
  "account": "{{csv.AccountRef}}"
}

Automations — REST API

POST /automations/config Create or update an automation
Request body
{
  "name": "Post-call to CRM",
  "triggerEvent": "call.completed",
  "enabled": true,
  "webhookUrl": "https://yourapp.example.com/webhooks/voxpro",
  "selectedFields": ["phoneNumber", "transcript", "outcome", "matchedCsvRow"],
  "customPayload": ""
}
POST /automations/test Test webhook delivery

Sends a sample payload to the URL without triggering a real call.

Request body
{
  "webhookUrl": "https://yourapp.example.com/webhooks/voxpro",
  "triggerEvent": "call.completed",
  "selectedFields": ["phoneNumber", "outcome"],
  "sampleData": { "phoneNumber": "+60123456789" }
}
POST /automations/trigger Manually fire automations for an event
Request body
{
  "triggerEvent": "call.completed",
  "payload": {
    "phoneNumber": "+60123456789",
    "roomName": "voxpro-room-xyz",
    "outcome": "PTP"
  }
}
Response 200
{
  "triggerEvent": "call.completed",
  "matched": 2,
  "results": [
    { "id": "auto_abc", "name": "Post-call CRM", "ok": true, "status": 200 },
    { "id": "auto_xyz", "name": "n8n Pipeline",  "ok": true, "status": 200 }
  ]
}

Call Logs — REST API

GET /logs List call logs
Response 200 — log object
{
  "logs": [
    {
      "id": "voxpro-room-1719825600-abc",
      "phoneNumber": "+60123456789",
      "contactName": "Ahmad Zaki",
      "assistantId": "asst_abc123",
      "assistantName": "Maya",
      "startedAt": "2025-06-20T14:00:00.000Z",
      "updatedAt": "2025-06-20T14:03:05.000Z",
      "endedReason": "customer-ended-call",
      "outcome": "PTP",
      "durationSeconds": 185,
      "recordingPath": "/recordings/voxpro-room-abc.wav",
      "campaignId": "blast-1719825600-a1b2c3",
      "campaignName": "June Collections",
      "structuredOutput": {
        "disposition": "PTP",
        "paymentDate": "2025-07-01",
        "amountPromised": 5000
      }
    }
  ]
}
GET /logs/:id Get a single call log

Returns the full log object including transcript and structured output.

Recordings

GET /logs/:id/recording Stream or download the call recording

Returns audio/wav. Supports HTTP Range requests for seeking. Add ?download=1 to force a file download instead of inline playback.

Stream in browser
// Authenticated streaming with fetch + Blob URL
const res = await fetch(`/logs/${logId}/recording`, {
  headers: { Authorization: `Bearer ${token}` }
});
const blob = await res.blob();
const url  = URL.createObjectURL(blob);
audioElement.src = url;
📝
Recordings are 16 kHz mono WAV, mixed from agent TTS and caller audio. They include a tanh soft limiter to prevent clipping and a noise gate to suppress SIP static.

Customer Allocation

Customer allocation controls how many concurrent calls and monthly minutes each client can consume. Enforced at blast campaign launch time.

POST /customers/config Create or update a customer limit
Request body
{
  "customerId": "acme-bpo",
  "customerName": "ACME BPO",
  "status": "active",
  "maxConcurrentCalls": 10,
  "enforceConcurrencyLimit": true,
  "monthlyMinuteLimit": 5000,
  "enforceMinuteLimit": true,
  "assistantIds": ["asst_abc123"]
}
GET /customers/usage/:customerId Get usage for a customer

Query param ?month=2025-06 for a specific month. Defaults to current month.

Response 200
{
  "customerId": "acme-bpo",
  "month": "2025-06",
  "totalMinutesUsed": 1248,
  "estimatedMinutesRemaining": 3752
}

Voices Catalogue

GET /voices List all available voices by provider

No auth required. Returns voices grouped by TTS provider. Use the id field as the voice value when configuring an assistant.

Response 200
{
  "ytl": [
    { "id": "voice_1", "name": "Voice 1" },
    { "id": "voice_2", "name": "Voice 2" },
    { "id": "voice_3", "name": "Voice 3" }
  ],
  "edge": [
    { "id": "ms-MY-YasminNeural", "name": "Yasmin — Malay Female" },
    { "id": "ms-MY-OsmanNeural",  "name": "Osman — Malay Male" },
    { "id": "en-SG-LunaNeural",   "name": "Luna — English SG Female" }
  ],
  "openai": [
    { "id": "alloy",   "name": "Alloy" },
    { "id": "shimmer", "name": "Shimmer" }
  ]
}
⚠️
YTL TTS only accepts voice_1, voice_2, or voice_3. Do not mix providers — if voiceProvider is ytl, the voice must be one of those three IDs.