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()
            .await

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. 401/403 — fix the api_key under [transcription.assemblyai]
  2. 400 — re-encode the audio to a mainstream format (mp3, wav, flac, ogg) and retry; verify the file is not truncated
  3. 429/5xx — retry the transcribe() call with exponential backoff
  4. 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

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


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/9f2402b6884352a6. Report an issue: GitHub.