Kuberwastaken/claurst · error

Whisper API returned

Error message

Whisper API returned {}: {}

What it means

transcribe() POSTs the recorded WAV to the OpenAI Whisper transcription endpoint (or WHISPER_ENDPOINT_URL). If the HTTP response status is not a success (2xx), it reads the response body and returns an anyhow error embedding the status code and body. This means the upload completed but the server rejected the transcription request.

Solutions

  1. Verify OPENAI_API_KEY is set to a valid OpenAI key with billing enabled (the fallback to ANTHROPIC_API_KEY will not work against api.openai.com).
  2. Check the response body in the error message for the exact reason (e.g. invalid_api_key, quota_exceeded).
  3. If WHISPER_ENDPOINT_URL is set, confirm it points to a Whisper-compatible endpoint that accepts multipart form fields 'file' and 'model'.
  4. Reduce recording length/compression to stay under the 25 MB upload limit.
  5. Retry with backoff if the status was 429 or 5xx (transient rate limit or server error).

Example fix

// before
match voice_capture::transcribe(wav, &key).await {
    Ok(text) => use(text),
    Err(e) => eprintln!("{}", e), // opaque
}
// after
match voice_capture::transcribe(wav, &key).await {
    Ok(text) => use(text),
    Err(e) => {
        let msg = format!("{e:#}");
        if msg.contains("401") { prompt_for_api_key(); }
        else if msg.contains("429") { schedule_retry(); }
        else { show_error(&msg); }
    }
}
Defensive patterns

Strategy: try-catch

Validate before calling

let key = std::env::var("OPENAI_API_KEY").ok().filter(|k| !k.trim().is_empty());
if key.is_none() { return Err(anyhow!("OPENAI_API_KEY required for Whisper transcription")); }
if wav_bytes.len() > 25 * 1024 * 1024 { return Err(anyhow!("audio exceeds OpenAI 25 MB transcription limit")); }

Type guard

fn is_success_status(status: u16) -> bool { (200..300).contains(&status) }

Try / catch

match transcribe(wav, &key).await {
    Ok(text) => handle(text),
    Err(e) => {
        let msg = format!("{e:#}");
        if msg.contains("401") { reauth(); } else if msg.contains("429") { retry_with_backoff(); } else { log_error(&msg); }
    }
}

Prevention

When it happens

Trigger: Calling transcribe() with an invalid/expired OPENAI_API_KEY (401), a wrong model name or endpoint (404), audio too large or malformed (400), or rate/quota limits (429) — any non-2xx from the transcription endpoint.

Common situations: Key rotated or revoked; using an ANTHROPIC_API_KEY fallback that Whisper does not accept; WHISPER_ENDPOINT_URL pointing to a non-OpenAI service with different auth; uploads exceeding the 25 MB audio limit; exhausted monthly quota.

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 Kuberwastaken/claurst@b0637c97ec (2026-09-10). Data as JSON: /api/errors/e03f0213ae2bc7d8. Report an issue: GitHub.

Appendix: source

Thrown at src-rust/crates/tui/src/voice_capture.rs:246

    let file_part = reqwest::multipart::Part::bytes(wav_bytes)
        .file_name("audio.wav")
        .mime_str("audio/wav")?;

    let form = reqwest::multipart::Form::new()
        .text("model", "whisper-1")
        .part("file", file_part);

    let response = client
        .post(&url)
        .bearer_auth(api_key)
        .multipart(form)
        .send()
        .await?;

    let status = response.status();
    if !status.is_success() {
        let body = response.text().await.unwrap_or_default();
        return Err(anyhow::anyhow!(
            "Whisper API returned {}: {}",
            status,
            body
        ));
    }

    let json: serde_json::Value = response.json().await?;
    let text = json["text"].as_str().unwrap_or("").trim().to_string();
    Ok(text)
}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

/// Resolve the API key to use for transcription.
///
/// Priority: `OPENAI_API_KEY` env var → `ANTHROPIC_API_KEY` env var → `None`.

View on GitHub (pinned to b0637c97ec)