{"record":{"id":"c708d426853a7e7e","repo":"zeroclaw-labs/zeroclaw","slug":"telnyx-call-failed-body","errorCode":null,"errorMessage":"Telnyx call failed: {body}","messagePattern":"Telnyx call failed: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/voice_call.rs","lineNumber":191,"sourceCode":"            VoiceProvider::Telnyx => {\n                let url = format!(\"{}/calls\", self.api_base_url());\n                let resp = self\n                    .client\n                    .post(&url)\n                    .bearer_auth(&self.config.auth_token)\n                    .json(&serde_json::json!({\n                        \"connection_id\": self.config.account_id,\n                        \"to\": to_number,\n                        \"from\": self.config.from_number,\n                        \"webhook_url\": webhook_url,\n                        \"timeout_secs\": self.config.max_call_duration_secs,\n                    }))\n                    .send()\n                    .await?;\n\n                if !resp.status().is_success() {\n                    let body = resp.text().await.unwrap_or_default();\n                    bail!(\"Telnyx call failed: {body}\");\n                }\n\n                let json: serde_json::Value = serde_json::from_str(&resp.text().await?)?;\n                let call_id = json[\"data\"][\"call_control_id\"]\n                    .as_str()\n                    .unwrap_or(\"unknown\")\n                    .to_string();\n                ::zeroclaw_log::record!(\n                    INFO,\n                    ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)\n                        .with_attrs(::serde_json::json!({\"call_id\": call_id, \"to\": to_number})),\n                    \"outbound call placed via Telnyx\"\n                );\n                Ok(call_id)\n            }\n            VoiceProvider::Plivo => {\n                let url = format!(\n                    \"{}/Account/{}/Call/\",","sourceCodeStart":173,"sourceCodeEnd":209,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/voice_call.rs#L173-L209","documentation":"Raised by VoiceCallChannel::execute_outbound_call when the Telnyx Call Control API returns a non-2xx status for POST {api_base_url}/calls. The request authenticates with a bearer token (config.auth_token), sends connection_id (from config.account_id), to, from, webhook_url, and timeout_secs, and the response body is embedded verbatim in the error so the Telnyx error envelope (errors[0].code/detail) tells you the exact cause. It means Telnyx refused to create the call; the raw body is the authoritative diagnosis.","triggerScenarios":"POST https://api.telnyx.com/v2/calls with (1) an expired/rotated V2 API token -> 401; (2) a connection_id that is not a valid Call Control Application ID (e.g. an account SID pasted instead) -> 422 'no object found for connection_id'; (3) from/to numbers not in E.164 (spaces, dashes, missing +) -> 422; (4) a from number not purchased/rented in the Telnyx project; (5) require_outbound_approval=false and any of the above fields wrong. Note webhook_url defaults to http://localhost:{webhook_port} when channels.voice.webhook_base_url is unset, which Telnyx will reject as unreachable.","commonSituations":"Rotating the Telnyx API key but not updating zeroclaw config; pasting the Telnyx Account ID into account_id (which must hold the Call Control connection/application id); copying a phone number from a contact card with formatting characters; running the daemon locally without webhook_base_url so Telnyx cannot reach status callbacks.","solutions":["Read the embedded Telnyx error body first — it names the failing field or token (e.g. \"10111\\\" no object found for connection_id\").","Verify auth_token is a current Telnyx V2 API token and account_id is the Call Control application/connection id, not the account SID.","Ensure from and to are E.164 (+15551234567, no spaces/dashes) and the from number is owned by the Telnyx project.","Set channels.voice.webhook_base_url to a public https base so the generated webhook_url is reachable by Telnyx.","Reproduce outside the agent with curl -X POST https://api.telnyx.com/v2/calls -H \"Authorization: Bearer $TOKEN\" to isolate config vs. code."],"exampleFix":"# before — config.toml\n[channels.voice]\nmodel_provider = \"telnyx\"\naccount_id = \"SIM-xxxx\"            # wrong: account SID, not a Call Control connection id\nfrom_number = \"+1 (555) 123-4567\" # wrong: not E.164\n\n# after\n[channels.voice]\nmodel_provider = \"telnyx\"\naccount_id = \"12345678-1234-1234-1234-123456789012\"  # Call Control Application/Connection id\nfrom_number = \"+15551234567\"                        # E.164\nwebhook_base_url = \"https://bot.example.com\"         # public, not localhost","handlingStrategy":"try-catch","validationCode":"fn is_e164(n: &str) -> bool {\n    let digits: String = n.chars().filter(|c| c.is_ascii_digit()).collect();\n    n.starts_with('+') && (8..=15).contains(&digits.len())\n}\n\n// before placing a Telnyx call\nif !is_e164(to_number) || !is_e164(&voice_config.from_number) {\n    anyhow::bail!(\"refusing to dial non-E.164 number: {to_number}\");\n}\nif voice_config.webhook_base_url.is_none() {\n    anyhow::bail!(\"channels.voice.webhook_base_url unset; Telnyx cannot reach status callbacks\");\n}","typeGuard":null,"tryCatchPattern":"match voice.place_call(to_number).await {\n    Ok(call_id) => { /* track id */ }\n    Err(e) if e.to_string().starts_with(\"Telnyx call failed:\") => {\n        // e carries the raw Telnyx error body: log it, surface to operator,\n        // and treat as non-retryable (4xx auth/validation) until config changes.\n    }\n    Err(e) => { /* transport errors (reqwest) may be transient: safe to retry */ }\n}","preventionTips":["Validate E.164 format for both from_number and destination before calling place_call.","Smoke-test credentials at boot with a cheap authenticated GET so a rotated token fails fast with a clear message.","Always set channels.voice.webhook_base_url in any non-local deployment.","Log and alert on the full error string — the embedded Telnyx body is the actionable diagnosis."],"tags":["telnyx","voice-call","http-api","config","e164"],"backgroundTag":"voice-provider-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}