zeroclaw-labs/zeroclaw · error · anyhow::Error
API error: {status}
Error message
API error: {status} What it means
Linq channel (api.linqapp.com partner API v3): send() first POSTs to /chats/{recipient}/messages treating the recipient as a chat_id; on 404 it falls back to creating a new chat (POST /chats with from_phone and the recipient), and this error means that chat-creation call itself returned non-2xx. The bail message carries only the HTTP status — the detailed error body is recorded as the error_body attribute of the zeroclaw log event on the line above, so check the logs for the real reason. A sibling bail at linq.rs:369 covers non-404 send failures.
Source
Thrown at crates/zeroclaw-channels/src/linq.rs:352
"value": message.content
}]
}
});
let create_resp = self
.client
.post(format!("{LINQ_API_BASE}/chats"))
.bearer_auth(&self.api_token)
.header("Content-Type", "application/json")
.json(&new_chat_body)
.send()
.await?;
if !create_resp.status().is_success() {
let status = create_resp.status();
let error_body = create_resp.text().await.unwrap_or_default();
::zeroclaw_log::record!(ERROR, ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail).with_outcome(::zeroclaw_log::EventOutcome::Failure).with_attrs(::serde_json::json!({"status": status.to_string(), "error_body": error_body})), "create chat failed:");
anyhow::bail!("API error: {status}");
}
return Ok(());
}
let status = resp.status();
let error_body = resp.text().await.unwrap_or_default();
::zeroclaw_log::record!(
ERROR,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)
.with_outcome(::zeroclaw_log::EventOutcome::Failure)
.with_attrs(
::serde_json::json!({"status": status.to_string(), "error_body": error_body})
),
"send failed:"
);
anyhow::bail!("API error: {status}");
}View on GitHub (pinned to 88bb9c8533)
Solutions
- Open the zeroclaw log for the matching 'create chat failed:' event and read attrs.error_body — it contains Linq's actual error message.
- 401 -> re-issue the partner api_token and update the linq channel config.
- 400 -> verify from_phone is a valid registered number and the recipient is a well-formed E.164 phone or existing chat id.
- Improve the error surface: include error_body in the bail message so callers see the cause without log access.
Example fix
// before (crates/zeroclaw-channels/src/linq.rs:350-352)
let error_body = create_resp.text().await.unwrap_or_default();
::zeroclaw_log::record!(ERROR, /* ... */, "create chat failed:");
anyhow::bail!("API error: {status}");
// after: surface the upstream reason to the caller
let error_body = create_resp.text().await.unwrap_or_default();
::zeroclaw_log::record!(ERROR, /* ... */, "create chat failed:");
anyhow::bail!("create chat failed: status={status}, body={error_body}"); Defensive patterns
Strategy: try-catch
Validate before calling
// Cheap credential probe before relying on the linq channel
async fn linq_token_ok(client: &reqwest::Client, token: &str) -> bool {
client.get("https://api.linqapp.com/api/partner/v3/phonenumbers")
.bearer_auth(token).send().await
.map(|r| r.status().is_success()).unwrap_or(false)
} Type guard
fn is_linq_api_status_error(err: &anyhow::Error) -> bool {
err.to_string().trim_start_matches("Caused by:").contains("API error: ")
} Try / catch
if let Err(e) = linq_channel.send(msg).await {
if is_linq_api_status_error(&e) {
// the bail omits the body: pull attrs.error_body from the zeroclaw log event first
tracing::error!(error = %e, "linq send failed; consult error_body in log attrs");
if e.to_string().contains("API error: 401") { alert("linq api_token expired"); }
return Ok(()); // treat as channel-level degradation, keep conversation alive
}
return Err(e);
} Prevention
- Correlate 'API error: {status}' with the error_body attribute in zeroclaw logs — the bail message omits it.
- Pre-flight the api_token against /phonenumbers at startup.
- Validate from_phone registration and E.164 recipient format before enabling the channel.
When it happens
Trigger: 401 with an invalid or expired api_token; 400 when from_phone is missing/unverified or the recipient phone number is malformed; 429/5xx from Linq rate limits or incidents — all hit after the initial chat send already 404'd (typical when the recipient is a phone number with no existing chat).
Common situations: Expired partner API token; from_phone not registered/verified with the Linq account; recipient numbers not in E.164; wrong token for the environment; the recipient string being a phone number (by design) so every first send goes through the create-chat path.
Related errors
- Failed to initiate call: {}
- tenant_access_token request failed: status={status}, body={d
- bot info request failed after token refresh: status={retry_s
- bot info request failed: status={status}, body={body}
- audio download failed: {}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/dc68b9146bec68cf.
Report an issue: GitHub.