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
- 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
- Normalize language codes to what LibreTranslate expects (ISO 639-1, e.g. 'pt' not 'pt-BR') by querying /languages first
- If the server requires authentication, send the api_key field in the request body
- Split large subtitle batches into smaller requests to stay under rate and size limits
- 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
- Normalize language codes to LibreTranslate's ISO 639-1 set (query /languages first)
- Always send api_key when the server runs with --api-keys
- Chunk large SRT batches to stay under request-size and rate limits
- Back off exponentially on 429/5xx instead of hammering the server
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)