zeroclaw-labs/zeroclaw · error

Telegram sendDocument by URL failed: {err}

Error message

Telegram sendDocument by URL failed: {err}

What it means

The URL variant of document sending posts a JSON body (sendDocument with a URL) and bails with Telegram's response body on non-2xx. Telegram fetches the URL server-side, so failures usually mean the URL was unreachable, the wrong content type, or the chat was undeliverable.

Source

Thrown at crates/zeroclaw-channels/src/telegram.rs:3504

        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("sendDocument"))
            .json(&body)
            .send()
            .await?;

        if !resp.status().is_success() {
            let err = resp.text().await?;
            anyhow::bail!("Telegram sendDocument by URL failed: {err}");
        }

        ::zeroclaw_log::record!(
            INFO,
            ::zeroclaw_log::Event::new(module_path!(), ::zeroclaw_log::Action::Note)
                .with_attrs(::serde_json::json!({"chat_id": chat_id, "url": url})),
            "document (URL) sent to"
        );
        Ok(())
    }

    /// Send a photo by URL (Telegram will download it)
    pub async fn send_photo_by_url(
        &self,
        chat_id: &str,
        thread_id: Option<&str>,
        url: &str,
        caption: Option<&str>,

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. curl the URL from a public network — Telegram must be able to fetch exactly that file.
  2. Use the direct file link with a document-appropriate Content-Type, not an HTML page.
  3. For private hosts, download locally and use send_document (multipart) instead.
  4. Read the embedded `description` for the specific Bot API reason.
Defensive patterns

Strategy: fallback

Validate before calling

let resp = reqwest::Client::new().head(url).send().await?;
anyhow::ensure!(resp.status().is_success(), "document URL not fetchable: {}", resp.status());
anyhow::ensure!(!url.starts_with("http://localhost") && !url.starts_with("http://127."), "URL must be reachable by Telegram");

Type guard

fn is_url_content_failure(err: &anyhow::Error) -> bool {
    err.to_string().contains("failed to get HTTP URL content")
}

Try / catch

if let Err(e) = channel.send_document_by_url(chat, url).await {
    if is_url_content_failure(&e) {
        let bytes = reqwest::get(url).await?.bytes().await?;
        return channel.send_document_bytes(chat, thread, bytes.to_vec(), name, None).await;
    }
    return Err(e);
}

Prevention

When it happens

Trigger: 400 'failed to get HTTP URL content' when Telegram cannot download the URL (private host, expired signed link, hotlink protection, HTML redirect instead of the file); malformed URL; 403 chat not found/blocked; document behind the URL over 20MB fetched by Telegram.

Common situations: Links to intranet/localhost artifact servers; presigned S3 URLs that expired before the send; CDN serving an HTML error page with 200; sending page links instead of direct file links.

Related errors


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