{"record":{"id":"e03f0213ae2bc7d8","repo":"Kuberwastaken/claurst","slug":"whisper-api-returned","errorCode":null,"errorMessage":"Whisper API returned {}: {}","messagePattern":"Whisper API returned (.+?): (.+?)","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"src-rust/crates/tui/src/voice_capture.rs","lineNumber":246,"sourceCode":"    let file_part = reqwest::multipart::Part::bytes(wav_bytes)\r\n        .file_name(\"audio.wav\")\r\n        .mime_str(\"audio/wav\")?;\r\n\r\n    let form = reqwest::multipart::Form::new()\r\n        .text(\"model\", \"whisper-1\")\r\n        .part(\"file\", file_part);\r\n\r\n    let response = client\r\n        .post(&url)\r\n        .bearer_auth(api_key)\r\n        .multipart(form)\r\n        .send()\r\n        .await?;\r\n\r\n    let status = response.status();\r\n    if !status.is_success() {\r\n        let body = response.text().await.unwrap_or_default();\r\n        return Err(anyhow::anyhow!(\r\n            \"Whisper API returned {}: {}\",\r\n            status,\r\n            body\r\n        ));\r\n    }\r\n\r\n    let json: serde_json::Value = response.json().await?;\r\n    let text = json[\"text\"].as_str().unwrap_or(\"\").trim().to_string();\r\n    Ok(text)\r\n}\r\n\r\n// ---------------------------------------------------------------------------\r\n// Helpers\r\n// ---------------------------------------------------------------------------\r\n\r\n/// Resolve the API key to use for transcription.\r\n///\r\n/// Priority: `OPENAI_API_KEY` env var → `ANTHROPIC_API_KEY` env var → `None`.\r","sourceCodeStart":228,"sourceCodeEnd":264,"githubUrl":"https://github.com/Kuberwastaken/claurst/blob/b0637c97ec34144387cbf2f74f65df6d16a6cef1/src-rust/crates/tui/src/voice_capture.rs#L228-L264","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["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).","Check the response body in the error message for the exact reason (e.g. invalid_api_key, quota_exceeded).","If WHISPER_ENDPOINT_URL is set, confirm it points to a Whisper-compatible endpoint that accepts multipart form fields 'file' and 'model'.","Reduce recording length/compression to stay under the 25 MB upload limit.","Retry with backoff if the status was 429 or 5xx (transient rate limit or server error)."],"exampleFix":"// before\nmatch voice_capture::transcribe(wav, &key).await {\n    Ok(text) => use(text),\n    Err(e) => eprintln!(\"{}\", e), // opaque\n}\n// after\nmatch voice_capture::transcribe(wav, &key).await {\n    Ok(text) => use(text),\n    Err(e) => {\n        let msg = format!(\"{e:#}\");\n        if msg.contains(\"401\") { prompt_for_api_key(); }\n        else if msg.contains(\"429\") { schedule_retry(); }\n        else { show_error(&msg); }\n    }\n}","handlingStrategy":"try-catch","validationCode":"let key = std::env::var(\"OPENAI_API_KEY\").ok().filter(|k| !k.trim().is_empty());\nif key.is_none() { return Err(anyhow!(\"OPENAI_API_KEY required for Whisper transcription\")); }\nif wav_bytes.len() > 25 * 1024 * 1024 { return Err(anyhow!(\"audio exceeds OpenAI 25 MB transcription limit\")); }","typeGuard":"fn is_success_status(status: u16) -> bool { (200..300).contains(&status) }","tryCatchPattern":"match transcribe(wav, &key).await {\n    Ok(text) => handle(text),\n    Err(e) => {\n        let msg = format!(\"{e:#}\");\n        if msg.contains(\"401\") { reauth(); } else if msg.contains(\"429\") { retry_with_backoff(); } else { log_error(&msg); }\n    }\n}","preventionTips":["Always set OPENAI_API_KEY; do not rely on the ANTHROPIC_API_KEY fallback — it will be rejected by api.openai.com.","Keep recordings short enough to stay under the 25 MB multipart limit.","Trim and validate WHISPER_ENDPOINT_URL when overriding it.","Treat 429/5xx as retryable with exponential backoff.","Log the response body from the error message; it names the exact failure reason."],"tags":["network","http","api","whisper","audio"],"backgroundTag":"http-error-response","analyzedSha":"b0637c97ec34144387cbf2f74f65df6d16a6cef1","analyzedAt":"2026-09-10T00:24:58.650Z","contentChangedAt":"2026-09-10T00:24:58.650Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}