sigoden/aichat · error
Invalid response data
Error message
Invalid response data: {data} (status: {status}) What it means
The final fallback of `catch_error` (src/client/common.rs:532): when a non-2xx response body matches none of the recognized error shapes, the library bails with the raw response JSON and HTTP status. It means the upstream returned a failure the client could not interpret as a known provider error format.
Solutions
- Inspect the raw JSON and status embedded in the message — it contains the full upstream body.
- Verify api_base URL is the correct API endpoint (should return JSON, not HTML).
- Check whether a proxy/load balancer is intercepting the request (502/503 HTML pages).
- Retry if the status was 5xx — could be a transient upstream failure.
- Update the library if the provider changed its error format, or file an issue to add the new error shape to catch_error.
Example fix
// before: api_base points at the web UI api_base = "https://myserver.com" // after: point at the API route api_base = "https://myserver.com/v1"
Defensive patterns
Strategy: fallback
Validate before calling
// preflight: endpoint must return JSON
let body = reqwest::get(format!("{api_base}/models")).await?.text().await?;
assert!(body.trim_start().starts_with('{'), "endpoint returned non-JSON"); Type guard
fn looks_like_html(body: &str) -> bool {
body.trim_start().starts_with('<')
} Try / catch
match client.chat_completions(&req).await {
Err(e) if e.to_string().starts_with("Invalid response data") => {
// unparseable upstream body; retry once, then surface raw body
retry_or_report_raw(e)
}
other => other,
} Prevention
- Never route api_base through HTML-serving proxies; check for 502/503 HTML pages.
- Confirm the URL scheme and path (/v1) are correct.
- Treat 5xx as transient and retry with backoff.
- Keep the library updated so new provider error shapes are recognized.
When it happens
Trigger: Any API call (chat_completions, chat_completions_streaming, embeddings, claude_chat_completions, openai_chat_completions, openai_embeddings) receiving a non-2xx status whose JSON body lacks `error`, `errors[0]`, `data[0].error`, `detail`+`status`, string `error`, or string `message` fields — or a non-JSON/HTML body serialized as a JSON string value.
Common situations: Hitting an HTML error page (502/503 from nginx/Cloudflare) behind a reverse proxy; wrong api_base pointing at a non-API URL; server returning empty object `{}` with 500; Cloudflare auth redirects; provider API version mismatch.
Understand the failure class
Background: "invalid response format", "malformed payload", "missing data field": when an API returns 200 but the response shape is wrong — this error's family across 23 libraries.
Related errors
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/12413ac12b81b473.
Report an issue: GitHub.
Appendix: source
Thrown at src/client/common.rs:532
) {
bail!("{message} (status: {code})")
}
} else if let Some(error) = data[0]["error"].as_object() {
if let (Some(status), Some(message)) = (
json_str_from_map(error, "status"),
json_str_from_map(error, "message"),
) {
bail!("{message} (status: {status})")
}
} else if let (Some(detail), Some(status)) = (data["detail"].as_str(), data["status"].as_i64())
{
bail!("{detail} (status: {status})");
} else if let Some(error) = data["error"].as_str() {
bail!("{error}");
} else if let Some(message) = data["message"].as_str() {
bail!("{message}");
}
bail!("Invalid response data: {data} (status: {status})");
}
pub fn json_str_from_map<'a>(
map: &'a serde_json::Map<String, Value>,
field_name: &str,
) -> Option<&'a str> {
map.get(field_name).and_then(|v| v.as_str())
}
async fn set_client_models_config(client_config: &mut Value, client: &str) -> Result<String> {
if let Some(provider) = ALL_PROVIDER_MODELS.iter().find(|v| v.provider == client) {
let models: Vec<String> = provider
.models
.iter()
.filter(|v| v.model_type == "chat")
.map(|v| v.name.clone())
.collect();
let model_name = select_model(models)?;View on GitHub (pinned to 82976d349a)