Skip to main content

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 HangupEvent to the phone system
  • The call is terminated immediately
  • The signalhangup flow 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:

ParameterTypeRequiredDefaultDescription
forward_call_idstrYes-Phone number to forward to
forward_call_ringingintNo15Seconds to ring before timeout
sip_headersdictNoNoneCustom 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():

  1. The forward request is sent to the PhoneServer
  2. The PhoneServer initiates the forwarded call
  3. The forwarded call completes (answered, busy, no answer, etc.)
  4. 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:

  1. Sending the ForwardEvent to the PhoneServer
  2. Yielding a RequestUserInputEvent to wait for the PhoneServer response
  3. 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:

StatusDescription
ANSWERCall was answered successfully
BUSYCalled party was busy
NOANSWERCalled party did not answer
CHANUNAVAILEndpoint unreachable or not registered
CONGESTIONChannel/switching congestion
CANCELCall cancelled before being answered
DONTCALLPrivacy mode rejection
TORTUREPrivacy mode rejection (torture script)
INVALIDARGSInvalid 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:

ParameterTypeRequiredDescription
update_payloadPayload classYesProvider-specific configuration payload
provider_titlestrNoSwitch to a different provider by its title

Important: Provider Compatibility

The update_payload type must match the provider type:

Provider TypeRequired Payload Class
botarioBotarioSTTConfigUpdatePayload
AzureAzureSTTConfigUpdatePayload
GoogleGoogleSTTConfigUpdatePayload

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 ValueError will 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 ValueError will 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.

ParameterTypeDefaultDescription
hotwordsstr | NoneNoneComma-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_alphafloat | None1.0Scaling factor controlling how aggressively the system trusts the hotword list vs. the acoustic model
hotword_context_scorefloat | None1.0Additive 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_alpha and hotword_context_score only work with transcription_mode='alpha_num'. Using them with transcription_mode='default' will raise a ValueError.

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:

ParameterTypeRequiredDescription
update_payloadPayload classYesProvider-specific configuration payload
provider_titlestrNoSwitch to a different provider by its title

Important: Provider Compatibility

The update_payload type must match the provider type:

Provider TypeRequired Payload Class
botarioBotarioTTSConfigUpdatePayload
AzureAzureTTSConfigUpdatePayload
GoogleGoogleTTSConfigUpdatePayload
ElevenLabsElevenLabsTTSConfigUpdatePayload

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 ValueError will 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 ValueError will 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:

ParameterTypeRequiredDefaultDescription
enableboolNoTrueTrue 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:

ParameterTypeRequiredDefaultDescription
termination_symbolstrNo"#"The symbol that the user must enter to indicate that their input is complete
single_digit_modeboolNoFalseIf True, only a single digit is expected and no termination symbol is required. The input is automatically completed after one digit
time_outfloatNoNoneSeconds 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:

ParameterTypeRequiredDefaultDescription
sound_namestrYes-Ambience sound to play
volumeintNo0Volume adjustment (-10 to 20)

Available Sounds:

SoundDescription
tastaturKeyboard typing sounds
copy_machineCopy machine/printer sounds
officeGeneral office ambience
background_musicHold 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:

ParameterTypeRequiredDescription
contact_infoContactInfo or dictYesContact 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"
)
)