{"record":{"id":"d7a65d0c59d69ee2","repo":"zeroclaw-labs/zeroclaw","slug":"assemblyai-transcription-error","errorCode":null,"errorMessage":"AssemblyAI transcription error ({}): {}","messagePattern":"AssemblyAI transcription error \\((.+?)\\): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-channels/src/transcription.rs","lineNumber":538,"sourceCode":"\n        let create_resp = client\n            .post(\"https://api.assemblyai.com/v2/transcript\")\n            .header(\"Authorization\", &self.api_key)\n            .json(&transcript_req)\n            .timeout(std::time::Duration::from_secs(TRANSCRIPTION_TIMEOUT_SECS))\n            .send()\n            .await\n            .context(\"Failed to create AssemblyAI transcription\")?;\n\n        let create_status = create_resp.status();\n        let create_body: serde_json::Value = create_resp\n            .json()\n            .await\n            .context(\"Failed to parse AssemblyAI create response\")?;\n\n        if !create_status.is_success() {\n            let error_msg = create_body[\"error\"].as_str().unwrap_or(\"unknown error\");\n            bail!(\n                \"AssemblyAI transcription error ({}): {}\",\n                create_status,\n                error_msg\n            );\n        }\n\n        let transcript_id = create_body[\"id\"]\n            .as_str()\n            .context(\"AssemblyAI response missing 'id'\")?;\n\n        // Step 3: Poll for completion.\n        let poll_url = format!(\"https://api.assemblyai.com/v2/transcript/{transcript_id}\");\n        let poll_interval = std::time::Duration::from_secs(3);\n        let poll_deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(180);\n\n        while tokio::time::Instant::now() < poll_deadline {\n            tokio::time::sleep(poll_interval).await;\n","sourceCodeStart":520,"sourceCodeEnd":556,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-channels/src/transcription.rs#L520-L556","documentation":"Step two of the AssemblyAI pipeline: after a successful upload, the provider POSTs to /v2/transcript with the returned upload_url to create the transcription job, and that call returned non-2xx. The status and body 'error' field are embedded. The audio bytes were already uploaded, but no transcript job exists, so polling never starts.","triggerScenarios":"POST /v2/transcript (with audio_url from the upload step and language/model options) returns non-success: 401 invalid api_key, 400 unsupported audio_url or invalid transcription options, 429 rate limited.","commonSituations":"Invalid AssemblyAI key that only fails at job creation (e.g. key valid for upload but restricted); mismatched language configuration for the audio; API changes in AssemblyAI request options between server versions.","solutions":["401/403 — verify the api_key in [transcription.assemblyai]","400 — read the embedded 'error' text; usually an invalid option value or unsupported audio — simplify options and retry","429/5xx — retry transcribe() after a short backoff; upload and create are both retried from scratch"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"match provider.transcribe(&audio, name).await {\n    Ok(text) => Ok(text),\n    Err(e) if e.to_string().contains(\"AssemblyAI transcription error (4\") => {\n        // 401/403 = credentials; 400 = read embedded error text; 429/5xx = retry\n        log_and_classify(e)\n    }\n    other => other,\n}","preventionTips":["Pin the AssemblyAI options you send to well-documented values — 400s at create time are usually option mistakes","Verify the key at startup with a GET /v2/transcript list call"],"tags":["assemblyai","http-status","api-key","transcription","job-create"],"backgroundTag":"speech-to-text-api-error","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}