Hmbown/CodeWhale · error · anyhow::Error

Failed to call Chat Completions API: HTTP

Error message

Failed to call {} Chat Completions API: HTTP {status}: {error_text}

What it means

A non-streaming Chat Completions request returned a non-success HTTP status. The bounded error body is sanitized with the provider display name and re-raised with the status, so this is the generic surface for every upstream Chat Completions API error (auth, quota, bad request, model not found). Raised in `create_message_chat` in `crates/tui/src/client/chat.rs` around line 1303, reached via `create_message_with_cache_policy`.

Solutions

  1. Read the sanitized provider `error_text` in the message and fix the named cause.
  2. Verify API key and model access for `self.api_provider`.
  3. For 429, add backoff/retry; for 5xx, retry later or switch provider.
  4. For 400, validate the messages/tools payload (often context-length or schema issues).
Defensive patterns

Strategy: retry

Validate before calling

fn chat_request_precheck(api_key: &str, model: &str, messages: &[Value]) -> Result<(), String> {
    if api_key.trim().is_empty() { return Err("missing API key".into()); }
    if messages.is_empty() { return Err("empty messages".into()); }
    Ok(())
}

Try / catch

match client.create_message(req).await {
    Err(e) if e.to_string().contains("Chat Completions API: HTTP") => {
        match extract_status(&e.to_string()) {
            429 | 500..=599 => retry_with_backoff().await,
            401 | 403 => Err(anyhow!("check provider key/model access: {e}")),
            _ => Err(e),
        }
    }
    other => other,
}

Prevention

When it happens

Trigger: Any non-2xx from POST `{base_url}/chat/completions`: 401 invalid API key, 403 no model access, 404 unknown model, 429 rate limit, 400 invalid request body (bad messages/tools/payload), 5xx provider outage.

Common situations: Wrong or expired API keys; model names not available on the configured provider/plan; payloads exceeding context limits; provider-side incidents; misconfigured base URLs hitting a path that 404s.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/c6093304699478cf. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/client/chat.rs:1303

            None
        };

        // The endpoint was resolved by the shared seam alongside the body, so
        // a route-shape decision (e.g. DeepSeek's strict-tools `/beta` path)
        // cannot be made twice with two different answers.
        let url = prepared.endpoint.url.as_str();
        let response = self.send_json_with_retry(url, body).await?;

        let status = response.status();
        crate::client::record_provider_response(self.api_provider, status.as_u16());
        if !status.is_success() {
            let raw_error_text = bounded_error_text(response, ERROR_BODY_MAX_BYTES).await;
            let error_text = sanitize_http_error_body(
                Some(self.api_provider.display_name()),
                status.as_u16(),
                &raw_error_text,
            );
            anyhow::bail!(
                "Failed to call {} Chat Completions API: HTTP {status}: {error_text}",
                self.api_provider.display_name()
            );
        }

        let response_text = response
            .text()
            .await
            .context("Failed to read Chat API response body")?;
        let value: Value =
            serde_json::from_str(&response_text).context("Failed to parse Chat API JSON")?;
        let parsed = parse_chat_message_for_route(&value, self.api_provider, &self.base_url)?;
        if let Some(key) = response_cache_key {
            crate::llm_response_cache::response_cache().put(key, parsed.clone());
        }
        Ok(parsed)
    }
}

View on GitHub (pinned to 73e0f67d83)