zeroclaw-labs/zeroclaw · error
AssemblyAI upload error ({}): {}
Error message
AssemblyAI upload error ({}): {} What it means
AssemblyAI transcription is a three-step pipeline (upload bytes, create transcript, poll), and this error is step one failing: the POST that uploads raw audio to AssemblyAI's upload endpoint returned non-2xx. The status and the 'error' field of the response body are embedded. Nothing was queued — the failure happened before a transcript was created.
Source
Thrown at crates/zeroclaw-channels/src/transcription.rs:509
let upload_resp = client
.post("https://api.assemblyai.com/v2/upload")
.header("Authorization", &self.api_key)
.header("Content-Type", "application/octet-stream")
.body(audio_data.to_vec())
.timeout(std::time::Duration::from_secs(TRANSCRIPTION_TIMEOUT_SECS))
.send()
.await
.context("Failed to upload audio to AssemblyAI")?;
let upload_status = upload_resp.status();
let upload_body: serde_json::Value = upload_resp
.json()
.await
.context("Failed to parse AssemblyAI upload response")?;
if !upload_status.is_success() {
let error_msg = upload_body["error"].as_str().unwrap_or("unknown error");
bail!("AssemblyAI upload error ({}): {}", upload_status, error_msg);
}
let upload_url = upload_body["upload_url"]
.as_str()
.context("AssemblyAI upload response missing 'upload_url'")?;
// Step 2: Create transcription job.
let transcript_req = serde_json::json!({
"audio_url": upload_url,
});
let create_resp = client
.post("https://api.assemblyai.com/v2/transcript")
.header("Authorization", &self.api_key)
.json(&transcript_req)
.timeout(std::time::Duration::from_secs(TRANSCRIPTION_TIMEOUT_SECS))
.send()
.awaitView on GitHub (pinned to 88bb9c8533)
Solutions
- 401/403 — fix the api_key under [transcription.assemblyai]
- 400 — re-encode the audio to a mainstream format (mp3, wav, flac, ogg) and retry; verify the file is not truncated
- 429/5xx — retry the transcribe() call with exponential backoff
- If it persists, check status.assemblyai.com for an upload-endpoint incident
Defensive patterns
Strategy: try-catch
Try / catch
match provider.transcribe(&audio, name).await {
Ok(text) => Ok(text),
Err(e) if e.to_string().contains("AssemblyAI upload error") => {
// transport/upload step failed: safe to retry the whole call once or twice
retry_with_backoff(|| provider.transcribe(&audio, name)).await
}
other => other,
} Prevention
- Keep the AssemblyAI key in a secret source that rotates cleanly
- Pre-encode audio to mp3/wav/flac so the upload step never rejects the payload
- Retry once with backoff — upload failures are frequently transient
When it happens
Trigger: POST to AssemblyAI's /v2/upload with the audio bytes returns non-success: 401 invalid [transcription.assemblyai] api_key, 400 malformed or undecodable audio payload, 429 rate limited, 5xx upstream incident.
Common situations: Wrong or rotated AssemblyAI API key; audio passed the 25 MB validate_audio() check but is corrupt or in a container AssemblyAI's uploader rejects; free-tier throttling on bursts of voice notes.
Related errors
- AssemblyAI transcription error ({}): {}
- Deepgram API error ({}): {}
- AssemblyAI poll error ({}): {}
- AssemblyAI transcription failed: {}
- AssemblyAI transcription timed out after 180s
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/9f2402b6884352a6.
Report an issue: GitHub.