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

  1. Read the embedded Telnyx error body first — it names the exact rejected field or reason
  2. 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
  3. Confirm connection_id is a Call Control SIP connection in the same Telnyx project as the key
  4. Confirm from_number is purchased, E.164-formatted, and `to` is a dialable E.164 number
  5. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/7f5b64726e849220. Report an issue: GitHub.