zeroclaw-labs/zeroclaw · error

AssemblyAI transcription error ({}): {}

Error message

AssemblyAI transcription error ({}): {}

What it means

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.

Source

Thrown at crates/zeroclaw-channels/src/transcription.rs:538

        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
            .context("Failed to create AssemblyAI transcription")?;

        let create_status = create_resp.status();
        let create_body: serde_json::Value = create_resp
            .json()
            .await
            .context("Failed to parse AssemblyAI create response")?;

        if !create_status.is_success() {
            let error_msg = create_body["error"].as_str().unwrap_or("unknown error");
            bail!(
                "AssemblyAI transcription error ({}): {}",
                create_status,
                error_msg
            );
        }

        let transcript_id = create_body["id"]
            .as_str()
            .context("AssemblyAI response missing 'id'")?;

        // Step 3: Poll for completion.
        let poll_url = format!("https://api.assemblyai.com/v2/transcript/{transcript_id}");
        let poll_interval = std::time::Duration::from_secs(3);
        let poll_deadline = tokio::time::Instant::now() + std::time::Duration::from_secs(180);

        while tokio::time::Instant::now() < poll_deadline {
            tokio::time::sleep(poll_interval).await;

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. 401/403 — verify the api_key in [transcription.assemblyai]
  2. 400 — read the embedded 'error' text; usually an invalid option value or unsupported audio — simplify options and retry
  3. 429/5xx — retry transcribe() after a short backoff; upload and create are both retried from scratch
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 transcription error (4") => {
        // 401/403 = credentials; 400 = read embedded error text; 429/5xx = retry
        log_and_classify(e)
    }
    other => other,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


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