Phone API Reference
This reference documents the tracker_api methods available for phone bots. These methods allow you to control calls, configure STT/TTS, manage recordings, and more.
Overview
Phone-specific methods are available through the tracker_api object in your Action nodes and custom scripts when the phone channel is enabled.
from shared import RemoteLLMTrackerApi
async def run(tracker_api: RemoteLLMTrackerApi):
# Phone methods are available on tracker_api
tracker_api.hangup()
Call Control
hangup()
Ends the phone call immediately.
tracker_api.hangup()
Parameters: None
Returns: None
Behavior:
- Triggers a
HangupEventto the phone system - The call is terminated immediately
- The
signalhangupflow will be triggered after this
Example:
async def run(tracker_api: RemoteLLMTrackerApi):
await tracker_api.fixed_utter("Thank you for calling. Goodbye!")
tracker_api.hangup()
forward_call()
Forwards the current call to another phone number by bridging a new call leg. The botario phoneserver stays in the loop and the bot can react to the outcome (answered, busy, no answer, etc.).
tracker_api.forward_call(
forward_call_id: str,
forward_call_ringing: int = 15,
sip_headers: dict | None = None,
sip_endpoint: str | None = None
)
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
forward_call_id | str | Yes | - | Phone number to forward to |
forward_call_ringing | int | No | 15 | Seconds to ring before timeout |
sip_headers | dict | No | None | Custom SIP headers for the forwarded call |
sip_endpoint | str | No | None | SIP endpoint to use for forwarding. Defaults to the inbound endpoint |
Returns: None
Recommended: Use the Forwarding Node in the flow editor instead of calling
forward_call()directly. The Forwarding Node handles the asynchronous nature of call forwarding automatically.
Important: Asynchronous Behavior
Call forwarding is processed asynchronously by the PhoneServer. When you call forward_call():
- The forward request is sent to the PhoneServer
- The PhoneServer initiates the forwarded call
- The forwarded call completes (answered, busy, no answer, etc.)
- The dial status is returned with the next user input from the PhoneServer
This means: You cannot read the sys_phone_dialstatus slot in the same Action node that calls forward_call(). The dial status will not be available yet.
Using forward_call() in Action Nodes
If you must use forward_call() instead of the Forwarding Node, you need two separate Action nodes:
Action Node 1 - Initiate Forward:
async def run(tracker_api: RemoteLLMTrackerApi):
await tracker_api.fixed_utter("I'll connect you to a support agent now.")
tracker_api.forward_call(
forward_call_id="+49123456789",
forward_call_ringing=30,
sip_headers={"X-Customer-ID": "12345"}
)
# Do NOT try to read dial_status here - it's not available yet!
Action Node 2 - Handle Result (connected after the forward completes):
async def run(tracker_api: RemoteLLMTrackerApi):
# Now the dial status is available
dial_status = tracker_api.get_slot("sys_phone_dialstatus")
if dial_status == "ANSWER":
await tracker_api.fixed_utter("I hope that helped. Is there anything else?")
elif dial_status == "NOANSWER":
await tracker_api.fixed_utter("Sorry, no one was available. Would you like to leave a message?")
elif dial_status == "BUSY":
await tracker_api.fixed_utter("The line is busy. Would you like to try again?")
else:
await tracker_api.fixed_utter("The call could not be connected. How else can I help?")
How the Forwarding Node Works
The built-in Forwarding Node handles this automatically by:
- Sending the
ForwardEventto the PhoneServer - Yielding a
RequestUserInputEventto wait for the PhoneServer response - On the next conversation turn (after the forward completes), reading the dial status and transitioning to the appropriate branch
This is why using the Forwarding Node is recommended - it encapsulates this two-phase pattern.
Dial Status Results:
After the forwarded call ends, the result is stored in the sys_phone_dialstatus slot:
| Status | Description |
|---|---|
ANSWER | Call was answered successfully |
BUSY | Called party was busy |
NOANSWER | Called party did not answer |
CHANUNAVAIL | Endpoint unreachable or not registered |
CONGESTION | Channel/switching congestion |
CANCEL | Call cancelled before being answered |
DONTCALL | Privacy mode rejection |
TORTURE | Privacy mode rejection (torture script) |
INVALIDARGS | Invalid syntax in forwarding request |
transfer_call()
Transfers the current call directly to another phone number via SIP REFER and immediately exits the conversation. Unlike forward_call(), the bot has no knowledge of whether the transfer was successful and cannot react to the outcome.
tracker_api.transfer_call(
forward_call_id: str
)
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
forward_call_id | str | Yes | Phone number to transfer the call to |
Returns: None
Behavior:
- Sends a
TransferEventto the phone system via SIP REFER - The bot immediately exits the conversation — there is no dial status, no callback, and no further turns
- Use this when you want to completely hand off the call without staying in the loop
forward_call() vs transfer_call()
forward_call() | transfer_call() | |
|---|---|---|
| Mechanism | Bridges a new call leg | SIP REFER |
| Bot stays in loop | Yes | No |
| Dial status available | Yes (sys_phone_dialstatus) | No |
| Can react to busy/no-answer | Yes | No |
Example:
async def run(tracker_api: RemoteLLMTrackerApi):
await tracker_api.fixed_utter("I'll transfer you now. Goodbye!")
tracker_api.transfer_call(forward_call_id="+49123456789")
STT Configuration
update_stt_config()
Dynamically updates the Speech-to-Text configuration during a call.
from botario_gpt_events.phone_events import (
BotarioSTTConfigUpdatePayload,
AzureSTTConfigUpdatePayload,
GoogleSTTConfigUpdatePayload,
)
tracker_api.update_stt_config(
update_payload: BotarioSTTConfigUpdatePayload | AzureSTTConfigUpdatePayload | GoogleSTTConfigUpdatePayload,
provider_title: str | None = None
)
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
update_payload | Payload class | Yes | Provider-specific configuration payload |
provider_title | str | No | Switch to a different provider by its title |
Important: Provider Compatibility
The update_payload type must match the provider type:
| Provider Type | Required Payload Class |
|---|---|
| botario | BotarioSTTConfigUpdatePayload |
| Azure | AzureSTTConfigUpdatePayload |
GoogleSTTConfigUpdatePayload |
If provider_title is not provided:
- The currently active STT provider is used
- The payload type must match the active provider's type
- If you switched providers earlier in the conversation and use the wrong payload type, a
ValueErrorwill be raised
If provider_title is provided:
- The system looks for an enabled provider with that title
- The payload type must match that provider's type
- If the provider is not found, a
ValueErrorwill be raised
Example - Correct usage with active provider:
# If your active provider is Azure, use AzureSTTConfigUpdatePayload
tracker_api.update_stt_config(
AzureSTTConfigUpdatePayload(language_code="de-DE")
)
# This would raise ValueError if active provider is Azure:
# tracker_api.update_stt_config(
# BotarioSTTConfigUpdatePayload(language="de") # Wrong payload type!
# )
Example - Switching to a specific provider:
# Switch to a provider named "My Azure STT"
tracker_api.update_stt_config(
AzureSTTConfigUpdatePayload(language_code="en-US"),
provider_title="My Azure STT"
)
BotarioSTTConfigUpdatePayload
from botario_gpt_events.phone_events import BotarioSTTConfigUpdatePayload
tracker_api.update_stt_config(
BotarioSTTConfigUpdatePayload(
transcription_mode="alpha_num", # "alpha_num" or "default"
language="de",
silero_vad_min_score=0.5, # VAD confidence threshold (0.0-1.0)
min_speech_len_in_s=0.3, # Minimum speech duration
start_listening_offset=0.5, # Start listening early
keep_audio_after_speech_in_s=0.75, # Wait time after speech
hotwords="botario, IBAN", # Comma-separated hotwords to boost
hotword_boosting_tree_alpha=1.0, # Boosting aggression (default: 1.0)
hotword_context_score=1.0 # Additive bonus for hotwords (1.0-5.0)
)
)
Example - Switch to Alpha-Numeric Mode:
async def capture_iban(tracker_api: RemoteLLMTrackerApi):
# Switch to alpha-numeric mode for IBAN capture
tracker_api.update_stt_config(
BotarioSTTConfigUpdatePayload(
transcription_mode="alpha_num"
)
)
await tracker_api.fixed_utter("Please spell out your IBAN now.")
# User input will be optimized for alphanumeric sequences
Hotword Boosting
Hotword boosting improves recognition of specific words or terms that are important to your use case. This is useful when the STT model might confuse domain-specific terms with similar-sounding common words.
| Parameter | Type | Default | Description |
|---|---|---|---|
hotwords | str | None | None | Comma-separated list of words to boost (e.g., 'botario, IBAN'). Case-sensitive - each word should appear only once in its expected casing |
hotword_boosting_tree_alpha | float | None | 1.0 | Scaling factor controlling how aggressively the system trusts the hotword list vs. the acoustic model |
hotword_context_score | float | None | 1.0 | Additive bonus for tokens in the hotword list. Higher values (1.0–5.0) steer the model to prefer hotwords over similar-sounding alternatives. Increase if hotwords are ignored, decrease if they are falsely detected |
Important:
hotword_boosting_tree_alphaandhotword_context_scoreonly work withtranscription_mode='alpha_num'. Using them withtranscription_mode='default'will raise aValueError.
Example - Boost IBAN Recognition:
async def capture_iban(tracker_api: RemoteLLMTrackerApi):
tracker_api.update_stt_config(
BotarioSTTConfigUpdatePayload(
transcription_mode="alpha_num",
hotwords="IBAN",
hotword_context_score=1.5,
hotword_boosting_tree_alpha=1.0,
)
)
await tracker_api.fixed_utter("Please spell out your IBAN now.")
Smart Turn and Speculative Transcription
Smart Turn enables server-side end-of-turn detection for botario STT. Instead of waiting only for the fixed VAD silence window (keep_audio_after_speech_in_s), the STT service can run a Smart Turn model during silence and decide earlier whether the caller has finished speaking. This reduces perceived latency in phone conversations while keeping the regular VAD timeout as a fallback.
Speculative transcription is the softer path of the same feature: when Smart Turn predicts that an utterance is complete, but the confidence is not high enough for immediate finalization, the STT service may start transcribing the current audio segment in the background. If the normal VAD ceiling confirms the end of speech afterwards, the already running transcription can be reused instead of starting from scratch. If the caller resumes speaking, the speculative result is discarded.
| Parameter | Type | Default | Description |
|---|---|---|---|
smart_turn_enabled | bool | None | None | Enables or disables Smart Turn for the current botario STT configuration. When enabled, the STT deployment must have Smart Turn support available. |
smart_turn_speculative_threshold | float | None | None | Minimum Smart Turn probability for starting speculative transcription. This should be lower than or equal to smart_turn_finalize_threshold. |
smart_turn_finalize_threshold | float | None | None | Minimum Smart Turn probability for immediately finalizing the current speech segment before the regular VAD silence timeout. 1.0 deactivates the feature. |
smart_turn_min_silence_s | float | None | None | Minimum amount of silence after speech before Smart Turn is queried. This must be smaller than keep_audio_after_speech_in_s so Smart Turn can run before the regular VAD finalization. |
Important: Smart Turn only works with botario STT deployments that have Smart Turn enabled server-side. Invalid timing is rejected:
smart_turn_min_silence_smust be smaller thankeep_audio_after_speech_in_s, andsmart_turn_speculative_thresholdmust not be higher thansmart_turn_finalize_threshold.
Example - Enable Smart Turn for faster turn endings:
tracker_api.update_stt_config(
BotarioSTTConfigUpdatePayload(
smart_turn_enabled=True,
keep_audio_after_speech_in_s=0.75,
smart_turn_min_silence_s=0.2,
smart_turn_speculative_threshold=0.5,
smart_turn_finalize_threshold=0.7,
)
)
AzureSTTConfigUpdatePayload
from botario_gpt_events.phone_events import AzureSTTConfigUpdatePayload
tracker_api.update_stt_config(
AzureSTTConfigUpdatePayload(
language_code="de-DE", # or list for auto-detect: ["de-DE", "en-US"]
profanity_filter="masked", # "raw", "masked", or "removed"
enable_dictation=True, # Better for longer speech
region="germanywestcentral",
semantic_segmentation=True, # Better sentence structure
keep_audio_after_speech_in_s=1.0
)
)
GoogleSTTConfigUpdatePayload
from botario_gpt_events.phone_events import GoogleSTTConfigUpdatePayload
tracker_api.update_stt_config(
GoogleSTTConfigUpdatePayload(
language_code="de-DE",
profanity_filter=True,
enable_automatic_punctuation=True,
enable_spoken_punctuation=True,
model="telephony" # "phone_call", "telephony", "telephony_short", "command_and_search"
)
)
TTS Configuration
update_tts_config()
Dynamically updates the Text-to-Speech configuration during a call.
from botario_gpt_events.api_config.tts_api_config import (
BotarioTTSConfigUpdatePayload,
AzureTTSConfigUpdatePayload,
GoogleTTSConfigUpdatePayload,
ElevenLabsTTSConfigUpdatePayload,
)
tracker_api.update_tts_config(
update_payload: BotarioTTSConfigUpdatePayload | AzureTTSConfigUpdatePayload | GoogleTTSConfigUpdatePayload | ElevenLabsTTSConfigUpdatePayload,
provider_title: str | None = None
)
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
update_payload | Payload class | Yes | Provider-specific configuration payload |
provider_title | str | No | Switch to a different provider by its title |
Important: Provider Compatibility
The update_payload type must match the provider type:
| Provider Type | Required Payload Class |
|---|---|
| botario | BotarioTTSConfigUpdatePayload |
| Azure | AzureTTSConfigUpdatePayload |
GoogleTTSConfigUpdatePayload | |
| ElevenLabs | ElevenLabsTTSConfigUpdatePayload |
If provider_title is not provided:
- The currently active TTS provider is used
- The payload type must match the active provider's type
- If you switched providers earlier in the conversation and use the wrong payload type, a
ValueErrorwill be raised
If provider_title is provided:
- The system looks for an enabled provider with that title
- The payload type must match that provider's type
- If the provider is not found, a
ValueErrorwill be raised
Example - Correct usage with active provider:
# If your active provider is Google, use GoogleTTSConfigUpdatePayload
tracker_api.update_tts_config(
GoogleTTSConfigUpdatePayload(speaking_rate=0.9)
)
# This would raise ValueError if active provider is Google:
# tracker_api.update_tts_config(
# AzureTTSConfigUpdatePayload(speaking_rate=0.9) # Wrong payload type!
# )
Example - Switching to a specific provider:
# Switch to a provider named "My ElevenLabs Voice"
tracker_api.update_tts_config(
ElevenLabsTTSConfigUpdatePayload(
voice="custom_voice_id",
stability=0.8
),
provider_title="My ElevenLabs Voice"
)
AzureTTSConfigUpdatePayload
from botario_gpt_events.api_config.tts_api_config import AzureTTSConfigUpdatePayload
tracker_api.update_tts_config(
AzureTTSConfigUpdatePayload(
language_code="de-DE",
voice="de-DE-KatjaNeural",
speaking_rate=0.9, # 0.25-4.0
pitch=0.0 # -20.0 to 20.0
)
)
GoogleTTSConfigUpdatePayload
from botario_gpt_events.api_config.tts_api_config import GoogleTTSConfigUpdatePayload
tracker_api.update_tts_config(
GoogleTTSConfigUpdatePayload(
language_code="de-DE",
voice="de-DE-Chirp3-HD-Puck",
speaking_rate=1.0, # 0.25-2.0
gender="FEMALE" # "MALE", "FEMALE", "NEUTRAL"
)
)
ElevenLabsTTSConfigUpdatePayload
from botario_gpt_events.api_config.tts_api_config import ElevenLabsTTSConfigUpdatePayload
tracker_api.update_tts_config(
ElevenLabsTTSConfigUpdatePayload(
voice="voice_id_here",
speaking_rate=1.0, # 0.7-1.2
similarity_boost=0.7, # 0.0-1.0
stability=0.7, # 0.0-1.0
model="eleven_turbo_v2_5"
)
)
Example - Adjust for Elderly Callers:
async def adjust_for_accessibility(tracker_api: RemoteLLMTrackerApi):
# Slow down speech for better comprehension
tracker_api.update_tts_config(
GoogleTTSConfigUpdatePayload(
speaking_rate=0.8,
gender="NEUTRAL"
)
)
Recording Control
enable_recording()
Enables or disables call recording.
tracker_api.enable_recording(enable: bool = True)
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
enable | bool | No | True | True to enable, False to disable |
Requirements:
- Phone channel recording must be set to "When Requested" or "Always"
- If set to "Never", this method has no effect
Example:
async def start_recording_with_consent(tracker_api: RemoteLLMTrackerApi):
await tracker_api.fixed_utter(
"This call may be recorded for quality purposes. "
"Do you consent to being recorded?"
)
# ... get user response ...
if user_consented:
tracker_api.enable_recording(enable=True)
await tracker_api.fixed_utter("Thank you. Recording has started.")
else:
await tracker_api.fixed_utter("Understood. This call will not be recorded.")
DTMF Input
enable_dtmf()
Enables DTMF (touch-tone) input from the user for exactly one turn. This is useful when you want users to interact with your bot using their phone keypad, such as navigating menus, entering PINs, or selecting options. DTMF mode is automatically disabled after the user input is received, so you need to enable it again if you want to capture more digits.
When DTMF is enabled, speech recognition (STT) remains active in parallel - both channels listen simultaneously. However, the inputs are never combined: whichever channel produces a result first wins. If any DTMF digits are detected, they are used as the input and the speech result is discarded entirely. If speech recognition finishes before any DTMF input arrives, the transcribed speech is used as the result.
await tracker_api.enable_dtmf(
termination_symbol: str = "#",
single_digit_mode: bool = False,
time_out: float | None = None
)
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
termination_symbol | str | No | "#" | The symbol that the user must enter to indicate that their input is complete |
single_digit_mode | bool | No | False | If True, only a single digit is expected and no termination symbol is required. The input is automatically completed after one digit |
time_out | float | No | None | Seconds to wait for more digits after the last digit was received. 0 means no timeout. Beware of possible deadlock situations with unlimited digit length and no timeout |
Warning: Using unlimited digit input without a timeout can cause deadlock. Always set at least a timeout value.
Example - Menu Selection (Single Digit):
async def phone_menu(tracker_api: RemoteLLMTrackerApi):
await tracker_api.enable_dtmf(single_digit_mode=True, time_out=10)
await tracker_api.fixed_utter(
"Press 1 for Sales, 2 for Support, or 3 to speak with an agent."
)
# User's DTMF input will be captured as text
Ambience Sounds
enable_ambience_channel()
Enables background ambience sounds during the call.
await tracker_api.enable_ambience_channel(
sound_name: Literal["tastatur", "copy_machine", "office", "background_music"],
volume: int = 0
)
Parameters:
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sound_name | str | Yes | - | Ambience sound to play |
volume | int | No | 0 | Volume adjustment (-10 to 20) |
Available Sounds:
| Sound | Description |
|---|---|
tastatur | Keyboard typing sounds |
copy_machine | Copy machine/printer sounds |
office | General office ambience |
background_music | Hold music |
disable_ambience_channel()
Disables a specific ambience sound.
await tracker_api.disable_ambience_channel(
sound_name: Literal["tastatur", "copy_machine", "office", "background_music"]
)
Example - Hold Music:
async def put_on_hold(tracker_api: RemoteLLMTrackerApi):
await tracker_api.fixed_utter("Please hold while I look that up.")
await tracker_api.enable_ambience_channel("background_music", volume=5)
# ... perform lookup ...
await tracker_api.disable_ambience_channel("background_music")
await tracker_api.fixed_utter("Thank you for holding. I found your information.")
Example - Office Atmosphere:
async def create_office_feel(tracker_api: RemoteLLMTrackerApi):
# Create background office sounds for a more human feel
await tracker_api.enable_ambience_channel("office", volume=-5)
await tracker_api.enable_ambience_channel("tastatur", volume=-8)
Contact Information
get_contact_info()
Retrieves contact information associated with the caller.
contact = tracker_api.get_contact_info()
Returns: ContactInfo | None
class ContactInfo:
category: str | None # Contact category
number: str # Phone number
meta: str | None # Additional metadata
Example:
async def greet_caller(tracker_api: RemoteLLMTrackerApi):
contact = tracker_api.get_contact_info()
if contact and contact.category == "VIP":
await tracker_api.fixed_utter(
f"Welcome back! I see you're calling from {contact.number}."
)
else:
await tracker_api.fixed_utter("Welcome! How can I help you today?")
save_contact_info()
Saves or updates contact information for the caller.
from shared.tracker.phone import ContactInfo
tracker_api.save_contact_info(
contact_info: ContactInfo | dict
)
Parameters:
| Parameter | Type | Required | Description |
|---|---|---|---|
contact_info | ContactInfo or dict | Yes | Contact data to save |
Example:
async def save_caller_info(tracker_api: RemoteLLMTrackerApi):
from shared.tracker.phone import ContactInfo
caller_number = tracker_api.get_slot("sys_phone_caller_id")
tracker_api.save_contact_info(
ContactInfo(
number=caller_number,
category="Customer",
meta="Registered on 2024-01-15"
)
)
Phone Statistics API
Retrieve aggregated per-call statistics for a bot over a date range. This is a REST endpoint (unlike the tracker_api methods above) and mirrors the authentication scheme used by Outbound Calls.
API Endpoint
GET /api/bots/{botId}/phone-statistics
Request Headers
x-api-key: {your-api-key}
Requires an API key with the Read scope and the Channel Manager role (see API Keys).
Query Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
start | string (date) | No | Start of the range, interpreted in UTC and normalized to the start of the day. Defaults to the start of the current month. |
end | string (date) | No | End of the range, interpreted in UTC and normalized to the end of the day. Defaults to the end of the current month. |
Statistics are rolled up periodically, so very recent calls may not appear immediately.
Response
{
"totals": {
"totalCalls": 128,
"totalDuration": 20480,
"avgDuration": 160,
"totalTimeWithBot": 15360,
"totalTimeWithHuman": 5120,
"totalTtsDurationInS": 4300,
"totalSttDurationInS": 3100,
"totalTimeWaitingForHandover": 640,
"forwardedCalls": 42,
"successfulForwardedCalls": 38,
"acceptedWithinServiceLevel20s": 30,
"containmentRate": 0.66,
"transferRate": 0.33,
"avgTimeWithBot": 120,
"avgTimeWithHuman": 40,
"avgHandoverWaitTime": 15
},
"byHangup": [
{ "_id": "assistant", "count": 85 },
{ "_id": "user", "count": 40 },
{ "_id": "error", "count": 3 }
],
"byProvider": [
{ "_id": "sip_endpoint_a", "totalCalls": 90, "totalDuration": 14400 }
],
"daily": [
{ "date": "2026-07-01", "totalCalls": 12, "totalDuration": 1920, "totalTimeWithBot": 1440 }
],
"handoverWaitDistribution": { "0_5s": 20, "5_10s": 10, "10_15s": 5, "15_20s": 3, "20_25s": 2, "30s_plus": 2 },
"dropOutWaitDistribution": { "0_5s": 1, "5_10s": 0, "10_15s": 1, "15_20s": 0, "20_25s": 0, "30s_plus": 0 }
}
| Field | Type | Description |
|---|---|---|
totals | object | null | Aggregated totals for the range. null when there are no calls in the range. |
byHangup | array | Calls per hangup reason. _id is assistant (bot ended the call), user (caller hung up), or error (abnormal termination). |
byProvider | array | Per SIP endpoint / provider breakdown (totalCalls, totalDuration). _id is unknown when no provider is recorded. |
daily | array | One entry per day in the range (zero-filled for days with no calls). |
handoverWaitDistribution | object | Count of forwarded calls bucketed by how long the caller waited before a human answered. |
dropOutWaitDistribution | object | Count of calls where the caller dropped out while waiting, bucketed by wait time. |
Distribution buckets: 0_5s, 5_10s, 10_15s, 15_20s, 20_25s, 30s_plus. All durations are in seconds.
totals fields
| Field | Description |
|---|---|
totalCalls / totalDuration / avgDuration | Call count, summed duration, and average duration |
totalTimeWithBot / avgTimeWithBot | Total and average time callers spent with the bot |
totalTimeWithHuman / avgTimeWithHuman | Total and average time callers spent with a human agent |
totalTtsDurationInS / totalSttDurationInS | Summed TTS audio duration and STT inference time across utterances |
totalTimeWaitingForHandover / avgHandoverWaitTime | Total and average time callers waited for handover |
forwardedCalls / successfulForwardedCalls | Calls with a handover wait, and those that actually reached a human |
acceptedWithinServiceLevel20s | Successful forwards answered within 20 seconds |
containmentRate | Fraction of calls the bot handled to completion (assistant hangups ÷ total calls) |
transferRate | Fraction of calls the caller ended (user hangups ÷ total calls) |
Example Request (cURL)
curl -X GET "https://your-instance.com/api/bots/507f191e810c19729de860ea/phone-statistics?start=2026-07-01&end=2026-07-31" \
-H "x-api-key: 507f1f77bcf86cd799439011.a3b2c1d4-e5f6-7890-abcd-ef1234567890"
Error Handling
| Status | Meaning |
|---|---|
403 Forbidden | Missing/invalid API key, missing Read scope, or key lacks the Channel Manager role (Forbidden. Required roles: channel-manager). A bot-bound key also returns 403 if {botId} doesn't match its bound bot. |
Related Documentation
- Speech-to-Text Configuration - UI configuration for STT
- Text-to-Speech Configuration - UI configuration for TTS
- Phone Events - Event flow configuration
- Outbound Calls - API for initiating calls
- Scripts - Custom scripts and actions