zeroclaw-labs/zeroclaw · error · anyhow::Error
Failed to initiate call: {}
Error message
Failed to initiate call: {} What it means
initiate_call POSTs to Telnyx /v2/calls with the configured connection_id, from_number and Bearer api_key; any non-2xx response body is embedded verbatim in this error. The diagnosis (bad key, invalid connection_id, unprovisioned from_number, insufficient credit) lives in Telnyx's own JSON inside the braces. Transport-level failures (DNS, TLS, the client's 30s timeout) instead surface earlier as reqwest errors via the ? on send(), not as this message.
Source
Thrown at crates/zeroclaw-channels/src/clawdtalk.rs:92
mode: "premium".to_string(),
}),
webhook_url: None,
// AI voice settings via Telnyx Call Control
command_id: None,
};
let response = self
.client
.post(format!("{}/calls", Self::TELNYX_API_URL))
.header("Authorization", format!("Bearer {}", self.api_key))
.header("Content-Type", "application/json")
.json(&request)
.send()
.await?;
if !response.status().is_success() {
let error = response.text().await?;
anyhow::bail!("Failed to initiate call: {}", error);
}
let call_response: CallResponse = response.json().await?;
Ok(CallSession {
call_control_id: call_response.call_control_id,
call_leg_id: call_response.call_leg_id,
call_session_id: call_response.call_session_id,
})
}
/// Send audio or TTS to an active call
pub async fn speak(&self, call_control_id: &str, text: &str) -> anyhow::Result<()> {
let request = SpeakRequest {
payload: text.to_string(),
payload_type: "text".to_string(),
service_level: "premium".to_string(),
voice: "female".to_string(),View on GitHub (pinned to 88bb9c8533)
Solutions
- Read the embedded Telnyx error body first — it names the exact rejected field or reason
- Verify the V2 key by calling GET /v2/phone_numbers with the same Bearer token (this is what health_check does); it must return 200
- Confirm connection_id is a Call Control SIP connection in the same Telnyx project as the key
- Confirm from_number is purchased, E.164-formatted, and `to` is a dialable E.164 number
- Check Telnyx account balance and voice enablement for the destination country
Defensive patterns
Strategy: try-catch
Validate before calling
// Fail fast on bad credentials before any send:
if !channel.health_check().await {
// GET /v2/phone_numbers rejected the key — fix [channels.clawdtalk.<alias>] first
} Try / catch
match channel.send(&msg).await {
Err(e) if e.to_string().starts_with("Failed to initiate call") => {
// Telnyx rejected POST /v2/calls — parse the embedded body; do not retry blindly
}
r => r?,
} Prevention
- Run health_check() at channel startup to catch credential drift early
- Keep api_key, connection_id and from_number from one Telnyx project in one config block
- Always log the full error string — the Telnyx body inside the braces carries the diagnosis
When it happens
Trigger: 401 from a revoked or mistyped api_key; 4xx from a connection_id that is not a valid Call Control SIP connection on that account; from_number not purchased or not E.164; `to` in a format the connection rejects; account out of credit or voice not enabled for the destination country.
Common situations: API key rotated in the Telnyx portal but not in [channels.clawdtalk.<alias>]; pasting the Telnyx public key instead of the API key; connection_id belonging to a different Telnyx project; from_number released back to the number pool; sandbox credentials pointed at production endpoints.
Related errors
- Destination {} is not in allowed list
- Failed to speak: {}
- Failed to start AI conversation: {}
- OpenAI TTS API error ({}): {}
- ElevenLabs TTS API error ({}): {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/7f5b64726e849220.
Report an issue: GitHub.