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

ParameterTypeRequiredDefaultDescription
forward_call_idstrYes-Phone number to forward to
forward_call_ringingintNo15Seconds to ring before timeout
sip_headersdictNoNoneCustom SIP headers for the forwarded call
sip_endpointstrNoNoneSIP 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():

  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

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:

ParameterTypeRequiredDescription
forward_call_idstrYesPhone number to transfer the call to

Returns: None

Behavior:

  • Sends a TransferEvent to 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()
MechanismBridges a new call legSIP REFER
Bot stays in loopYesNo
Dial status availableYes (sys_phone_dialstatus)No
Can react to busy/no-answerYesNo

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:

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.")

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.

ParameterTypeDefaultDescription
smart_turn_enabledbool | NoneNoneEnables or disables Smart Turn for the current botario STT configuration. When enabled, the STT deployment must have Smart Turn support available.
smart_turn_speculative_thresholdfloat | NoneNoneMinimum Smart Turn probability for starting speculative transcription. This should be lower than or equal to smart_turn_finalize_threshold.
smart_turn_finalize_thresholdfloat | NoneNoneMinimum 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_sfloat | NoneNoneMinimum 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_s must be smaller than keep_audio_after_speech_in_s, and smart_turn_speculative_threshold must not be higher than smart_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:

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.

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:

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"
)
)

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​

ParameterTypeRequiredDescription
startstring (date)NoStart of the range, interpreted in UTC and normalized to the start of the day. Defaults to the start of the current month.
endstring (date)NoEnd 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 }
}
FieldTypeDescription
totalsobject | nullAggregated totals for the range. null when there are no calls in the range.
byHanguparrayCalls per hangup reason. _id is assistant (bot ended the call), user (caller hung up), or error (abnormal termination).
byProviderarrayPer SIP endpoint / provider breakdown (totalCalls, totalDuration). _id is unknown when no provider is recorded.
dailyarrayOne entry per day in the range (zero-filled for days with no calls).
handoverWaitDistributionobjectCount of forwarded calls bucketed by how long the caller waited before a human answered.
dropOutWaitDistributionobjectCount 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​

FieldDescription
totalCalls / totalDuration / avgDurationCall count, summed duration, and average duration
totalTimeWithBot / avgTimeWithBotTotal and average time callers spent with the bot
totalTimeWithHuman / avgTimeWithHumanTotal and average time callers spent with a human agent
totalTtsDurationInS / totalSttDurationInSSummed TTS audio duration and STT inference time across utterances
totalTimeWaitingForHandover / avgHandoverWaitTimeTotal and average time callers waited for handover
forwardedCalls / successfulForwardedCallsCalls with a handover wait, and those that actually reached a human
acceptedWithinServiceLevel20sSuccessful forwards answered within 20 seconds
containmentRateFraction of calls the bot handled to completion (assistant hangups ÷ total calls)
transferRateFraction 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​

StatusMeaning
403 ForbiddenMissing/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.