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.
tracker_api.forward_call(
forward_call_id: str,
forward_call_ringing: int = 15,
sip_headers: dict | 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 |
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 |
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.")
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. During DTMF input, speech recognition is disabled—users must use their keypad only.
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"
)
)
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