{"record":{"id":"dc68b9146bec68cf","repo":"zeroclaw-labs/zeroclaw","slug":"api-error-status","errorCode":null,"errorMessage":"API error: {status}","messagePattern":"API error: (.+?)","errorType":"http","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/linq.rs","lineNumber":352,"sourceCode":"                        \"value\": message.content\n                    }]\n                }\n            });\n\n            let create_resp = self\n                .client\n                .post(format!(\"{LINQ_API_BASE}/chats\"))\n                .bearer_auth(&self.api_token)\n                .header(\"Content-Type\", \"application/json\")\n                .json(&new_chat_body)\n                .send()\n                .await?;\n\n            if !create_resp.status().is_success() {\n                let status = create_resp.status();\n                let error_body = create_resp.text().await.unwrap_or_default();\n                ::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:\");\n                anyhow::bail!(\"API error: {status}\");\n            }\n\n            return Ok(());\n        }\n\n        let status = resp.status();\n        let error_body = resp.text().await.unwrap_or_default();\n        ::zeroclaw_log::record!(\n            ERROR,\n            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Fail)\n                .with_outcome(::zeroclaw_log::EventOutcome::Failure)\n                .with_attrs(\n                    ::serde_json::json!({\"status\": status.to_string(), \"error_body\": error_body})\n                ),\n            \"send failed:\"\n        );\n        anyhow::bail!(\"API error: {status}\");\n    }","sourceCodeStart":334,"sourceCodeEnd":370,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/linq.rs#L334-L370","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before (crates/zeroclaw-channels/src/linq.rs:350-352)\nlet error_body = create_resp.text().await.unwrap_or_default();\n::zeroclaw_log::record!(ERROR, /* ... */, \"create chat failed:\");\nanyhow::bail!(\"API error: {status}\");\n\n// after: surface the upstream reason to the caller\nlet error_body = create_resp.text().await.unwrap_or_default();\n::zeroclaw_log::record!(ERROR, /* ... */, \"create chat failed:\");\nanyhow::bail!(\"create chat failed: status={status}, body={error_body}\");","handlingStrategy":"try-catch","validationCode":"// Cheap credential probe before relying on the linq channel\nasync fn linq_token_ok(client: &reqwest::Client, token: &str) -> bool {\n    client.get(\"https://api.linqapp.com/api/partner/v3/phonenumbers\")\n        .bearer_auth(token).send().await\n        .map(|r| r.status().is_success()).unwrap_or(false)\n}","typeGuard":"fn is_linq_api_status_error(err: &anyhow::Error) -> bool {\n    err.to_string().trim_start_matches(\"Caused by:\").contains(\"API error: \")\n}","tryCatchPattern":"if let Err(e) = linq_channel.send(msg).await {\n    if is_linq_api_status_error(&e) {\n        // the bail omits the body: pull attrs.error_body from the zeroclaw log event first\n        tracing::error!(error = %e, \"linq send failed; consult error_body in log attrs\");\n        if e.to_string().contains(\"API error: 401\") { alert(\"linq api_token expired\"); }\n        return Ok(()); // treat as channel-level degradation, keep conversation alive\n    }\n    return Err(e);\n}","preventionTips":["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."],"tags":["linq","linqapp","http","chat-creation","partner-api"],"backgroundTag":"http-error-response","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}