zeroclaw-labs/zeroclaw · error
{method} by URL failed: {err}
Error message
{method} by URL failed: {err} What it means
The send-media-by-URL path posts a JSON body (method such as sendPhoto/sendDocument with a URL instead of an upload) and bails with Telegram's response body on non-2xx. Telegram's servers fetch the URL themselves, so rejections usually point at the URL or its content rather than the chat.
Source
Thrown at crates/zeroclaw-channels/src/telegram.rs:3037
if let Some(tid) = thread_id {
body["message_thread_id"] = serde_json::Value::String(tid.to_string());
}
if let Some(cap) = caption {
body["caption"] = serde_json::Value::String(cap.to_string());
}
let resp = self
.http_client()
.post(self.api_url(method))
.json(&body)
.send()
.await?;
if !resp.status().is_success() {
let err = resp.text().await?;
anyhow::bail!("{method} by URL failed: {err}");
}
::zeroclaw_log::record!(
INFO,
::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note).with_attrs(
::serde_json::json!({"method": method, "chat_id": chat_id, "url": url})
),
"sent to"
);
Ok(())
}
async fn send_attachment(
&self,
chat_id: &str,
thread_id: Option<&str>,
attachment: &TelegramAttachment,
) -> anyhow::Result<()> {View on GitHub (pinned to 88bb9c8533)
Solutions
- Fetch the URL yourself from a public network (curl with the same headers) — Telegram must be able to download it.
- Use the direct file URL with a Content-Type matching the method (image/jpeg for sendPhoto, etc.), not an HTML page.
- If the host is private, download the file locally and use the multipart path (send_photo/send_document) instead of the URL variant.
- Read the embedded `description` in the error body for the exact Bot API reason.
Defensive patterns
Strategy: fallback
Validate before calling
// prove the URL is publicly fetchable and the right content type before sending
let resp = reqwest::Client::new().head(url).send().await?;
anyhow::ensure!(resp.status().is_success(), "URL not fetchable: {}", resp.status());
anyhow::ensure!(resp.headers().get(CONTENT_TYPE).map_or(false, |v| v.to_str().unwrap_or("").starts_with("image/")), "URL is not an image"); Type guard
fn is_url_fetch_failure(err: &anyhow::Error) -> bool {
err.to_string().contains("failed to get HTTP URL content")
} Try / catch
if let Err(e) = channel.send_media_by_url(chat, url).await {
if is_url_fetch_failure(&e) {
// mirror the channel's own fallback: send the link as plain text
return channel.send_text_chunks(&format!("{label}: {url}"), chat, thread).await;
}
return Err(e);
} Prevention
- Never pass intranet/localhost URLs; download the file and use the multipart senders instead.
- Pre-check the URL returns the expected Content-Type with a HEAD request.
- Keep a text-link fallback for every URL send, as send_attachment already does.
When it happens
Trigger: 400 'failed to get HTTP URL content' when Telegram cannot fetch the URL (host unreachable from Telegram's network, wrong MIME type, redirect to an HTML page); malformed URL; 403 chat not found/blocked; content behind the URL exceeding the type's size limits.
Common situations: Links to localhost/intranet hosts Telegram cannot reach; expired signed URLs or hotlink protection; sending a web page URL where a direct file URL is required; content-type sniffing failing on unusual extensions.
Related errors
- Telegram sendDocument by URL failed: {err}
- Telegram sendPhoto by URL failed: {err}
- {method} failed: status={status}, body={body}
- Telegram file download failed: {}
- Telegram sendMessage (approval) failed ({status}): {err}
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/5d7e8a54c29d452c.
Report an issue: GitHub.