sigoden/aichat · error

{error}

Error message

{error}

What it means

This error is raised by `catch_error` (src/client/common.rs:528) when an upstream LLM API returns a non-2xx HTTP status whose JSON body contains an `error` field that is a plain string (not an object with type/message). The library surfaces that raw string verbatim as the error message. It is the fallback branch for APIs whose error payloads don't match the structured OpenAI/Anthropic shapes checked earlier.

Solutions

  1. Read the message text included in the error — it is the provider's own error string; fix what it names first (usually auth or model name).
  2. Verify the API key is valid and set (config or <CLIENT>_API_KEY env var) with a direct curl to the endpoint.
  3. Check that api_base points at a real OpenAI-compatible endpoint that returns the expected error shapes.
  4. If the provider returns structured errors, ensure the client type matches the provider so the structured branches of catch_error parse them instead.

Example fix

// before: hitting wrong endpoint
let client = Client::new("openai", api_base: "https://example.com/v1");
// after: correct endpoint and key
let client = Client::new("openai", api_base: "https://api.openai.com/v1", api_key: Some(key));
Defensive patterns

Strategy: try-catch

Validate before calling

// preflight: verify key/endpoint before calling
let models = fetch_models(api_base, Some(&api_key)).await?;

Type guard

fn is_upstream_error_str(err: &anyhow::Error) -> bool {
    err.to_string().contains("status:") || !err.to_string().is_empty()
}

Try / catch

match client.chat_completions(&req).await {
    Err(e) if e.to_string().contains("status: 401") => refresh_api_key_and_retry(),
    Err(e) => eprintln!("request failed: {e}"),
    Ok(r) => handle(r),
}

Prevention

When it happens

Trigger: Any call to chat_completions, chat_completions_streaming, embeddings, claude_chat_completions, openai_chat_completions, or openai_embeddings receiving a non-2xx response whose body parses as JSON with `data["error"]` being a JSON string, e.g. `{"error": "Invalid API key"}` with status 400/401/404/429/500.

Common situations: Wrong or expired API key; hitting a non-OpenAI-compatible endpoint; proxies/gateways (e.g. LiteLLM, nginx) returning simple `{"error": "..."}` bodies; deprecated model names rejected server-side; quota exhausted on a custom provider.

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 sigoden/aichat@82976d349a (2026-09-09). Data as JSON: /api/errors/211293869153f2e7. Report an issue: GitHub.

Appendix: source

Thrown at src/client/common.rs:528

    } else if let Some(error) = data["errors"][0].as_object() {
        if let (Some(code), Some(message)) = (
            error.get("code").and_then(|v| v.as_u64()),
            json_str_from_map(error, "message"),
        ) {
            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()

View on GitHub (pinned to 82976d349a)