tonhowtf/omniget · error

LibreTranslate: HTTP

Error message

LibreTranslate: HTTP {} {}

What it means

This error is thrown by translate_batch_libre when the LibreTranslate server responds with a non-success HTTP status code. It formats the numeric status plus the optional "error" field from the parsed JSON body. Common statuses are 400 (bad source/target language), 403 (invalid or missing API key), 429 (rate limit), and 5xx (server failure).

Solutions

  1. Read the status code in the message: fix the language codes for 400, supply a valid api_key for 403, slow down / batch smaller for 429, retry later for 5xx
  2. Normalize language codes to what LibreTranslate expects (ISO 639-1, e.g. 'pt' not 'pt-BR') by querying /languages first
  3. If the server requires authentication, send the api_key field in the request body
  4. Split large subtitle batches into smaller requests to stay under rate and size limits
  5. Check the LibreTranslate server logs at the time of the request to confirm the server-side reason

Example fix

// before: single huge batch, no retry
translate_batch_libre(client, &url, &all_texts, src, dst, api_key).await?;
// after: chunked batches with backoff on 429/5xx
for chunk in texts.chunks(50) {
    match translate_batch_libre(client, &url, chunk, src, dst, api_key).await {
        Ok(v) => results.extend(v),
        Err(e) if is_retryable(&e) => { tokio::time::sleep(Duration::from_secs(5)).await;
            results.extend(translate_batch_libre(client, &url, chunk, src, dst, api_key).await?); }
        Err(e) => return Err(e),
    }
}
Defensive patterns

Strategy: retry

Validate before calling

// Validate language codes and API key before calling
let langs: Vec<serde_json::Value> = client.get(format!("{url}/languages"))
    .send().await?.json().await?;
if !langs.iter().any(|l| l["code"] == src) || !langs.iter().any(|l| l["code"] == dst) {
    return Err(anyhow!("idioma nao suportado pelo servidor"));
}
if api_key.is_none() && server_requires_key { return Err(anyhow!("api_key obrigatoria")); }

Type guard

fn is_retryable_status(err: &anyhow::Error) -> bool {
    let s = err.to_string();
    ["429", "500", "502", "503", "504"].iter().any(|c| s.contains(&format!("HTTP {c}")))
}

Try / catch

match translate_batch_libre(...).await {
    Ok(v) => v,
    Err(e) if is_retryable_status(&e) => {
        tokio::time::sleep(BACKOFF).await;
        translate_batch_libre(...).await? // retry once with smaller batch
    }
    Err(e) if e.to_string().contains("HTTP 403") => {
        return Err(anyhow!("chave de API invalida ou ausente: {e}"));
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling translate_cues when the LibreTranslate server rejects the batch request: unsupported language code, missing/invalid api_key when the server requires one, exceeding the character/request limit, or the server being overloaded and returning 5xx.

Common situations: Using language codes LibreTranslate does not recognize (e.g. 'pt-br' vs 'pt'); self-hosted instance with --api-keys enabled but no api_key sent; free public endpoint rate-limiting large SRT batches; hitting a fronting proxy's request-size limit with a huge batch.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/26da96f13dbf0afc. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/tools/srt_translate.rs:146

    let client = super::client()?;
    let url = format!("{}/translate", base_url.trim_end_matches('/'));
    let mut body = serde_json::json!({
        "q": lines,
        "source": if opts.source_lang.is_empty() { "auto" } else { opts.source_lang.as_str() },
        "target": opts.target_lang,
        "format": "text",
    });
    if !api_key.is_empty() {
        body["api_key"] = serde_json::Value::String(api_key.to_string());
    }
    let resp = client.post(&url).json(&body).send().await?;
    let status = resp.status();
    let v: serde_json::Value = resp
        .json()
        .await
        .map_err(|e| anyhow!("LibreTranslate: resposta invalida ({})", e))?;
    if !status.is_success() {
        return Err(anyhow!(
            "LibreTranslate: HTTP {} {}",
            status.as_u16(),
            v["error"].as_str().unwrap_or("")
        ));
    }
    let out = match &v["translatedText"] {
        serde_json::Value::Array(a) => a
            .iter()
            .map(|x| x.as_str().map(|s| s.to_string()))
            .collect(),
        serde_json::Value::String(s) => vec![Some(s.clone())],
        _ => vec![None; lines.len()],
    };
    Ok(out)
}

pub async fn translate_cues(
    cues: &[Cue],

View on GitHub (pinned to 8600b91f42)