Guide: Telegram bot

Build a Telegram Downloader Bot

A Telegram downloader bot is a small loop: read messages with getUpdates, send each link to POST /v1/resolve, and reply with sendVideo or sendPhoto. The complete bot below is one Node.js file with no packages, and our test suite runs it before every release. Telegram fetches a file by URL only up to 20 MB, so bigger videos go back as a link.

No card needed. Failed requests cost nothing.

A person holding a phone and reading a message

Key facts

Endpoint
POST /v1/resolve (one endpoint for every service)
Free trial
20 result credits after email verification, no card
Price
from $19 for 7,500 results ($1.33–$2.53 per 1,000)
Billing
prepaid credits, no subscription, no expiry; failed or empty requests cost 0
Credits per result
1 (Instagram, TikTok, X, Pinterest) or 2 (Stories, Highlights, Telegram)
Limits
up to 20 items per request; 120 requests per minute per account; 2 in progress at once on the trial, 8 on paid accounts

Steps

  1. Open @BotFather in Telegram, send /newbot and copy the token.
  2. Create a free account with a real email address and verify it. You get 20 free credits, no card needed.
  3. Create an API key in Dashboard, API keys and keep it on your server as GIS_KEY. Never put it in a browser or a mobile app.
  4. Save the code below as bot.mjs and run BOT_TOKEN=123:abc GIS_KEY=gis_live_... node bot.mjs.
  5. Send your bot a public Instagram, TikTok, X, Pinterest or Telegram link.

The complete bot

It answers one link per chat every 10 seconds, so one person cannot spend all your credits. Change WAIT_MS to suit your bot.

bot.mjs
// bot.mjs, Node.js 18+, no packages. Run: BOT_TOKEN=123:abc GIS_KEY=gis_live_... node bot.mjs
const API_URL = "https://developers.getinsaver.com/v1/resolve";
const TELEGRAM = `https://api.telegram.org/bot${process.env.BOT_TOKEN}`;
const HOSTS = { "instagram.com": "instagram", "tiktok.com": "tiktok", "x.com": "x", "twitter.com": "x", "pinterest.com": "pinterest", "pin.it": "pinterest", "t.me": "telegram" };
const HELP = "Send me a public Instagram, TikTok, X, Pinterest or Telegram link.";
const WAIT_MS = 10_000; // one link per chat every 10 seconds, so one person cannot spend all your credits
const lastLink = new Map();
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));

function serviceFor(link) {
  try {
    const url = new URL(link);
    const host = url.hostname.replace(/^(www|m|vm|vt)\./, "");
    if (host === "instagram.com" && url.pathname.startsWith("/stories/highlights/")) return "instagram-highlight";
    if (host === "instagram.com" && url.pathname.startsWith("/stories/")) return "instagram-story";
    return HOSTS[host];
  } catch {
    return undefined;
  }
}

async function telegram(method, body, timeoutMs = 30_000) {
  const response = await fetch(`${TELEGRAM}/${method}`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
    signal: AbortSignal.timeout(timeoutMs)
  });
  return response.json();
}

async function resolveLink(service, url, retries = 2) {
  for (let attempt = 0; ; attempt++) {
    const response = await fetch(API_URL, {
      method: "POST",
      headers: { Authorization: `Bearer ${process.env.GIS_KEY}`, "Content-Type": "application/json" },
      body: JSON.stringify({ service, url, max_results: 10 }),
      signal: AbortSignal.timeout(130_000)
    });
    const data = await response.json();
    if (response.ok) return data;
    // 429 = rate or concurrency limit, 502/504 = platform hiccup; none of them is charged
    if ([429, 502, 504].includes(response.status) && attempt < retries) {
      await sleep(Number(response.headers.get("retry-after") || 5) * 1000);
      continue;
    }
    throw new Error(data.message);
  }
}

async function handle(message) {
  const chat_id = message.chat.id;
  const link = message.text?.match(/https?:\/\/\S+/)?.[0];
  const service = link && serviceFor(link);
  if (!service) return telegram("sendMessage", { chat_id, text: HELP });
  if (Date.now() - (lastLink.get(chat_id) || 0) < WAIT_MS) {
    return telegram("sendMessage", { chat_id, text: "One link every 10 seconds, please." });
  }
  lastLink.set(chat_id, Date.now());
  await telegram("sendChatAction", { chat_id, action: "typing" });
  try {
    const { items } = await resolveLink(service, link);
    for (const item of items) {
      const [method, field] = item.type === "image" ? ["sendPhoto", "photo"]
        : item.type === "video" ? ["sendVideo", "video"] : ["sendDocument", "document"];
      const sent = await telegram(method, { chat_id, [field]: item.url });
      // Telegram fetches a URL only up to 5 MB for photos and 20 MB for other files.
      if (!sent.ok) await telegram("sendMessage", { chat_id, text: item.url });
    }
  } catch (error) {
    await telegram("sendMessage", { chat_id, text: error.message });
  }
}

let offset = 0;
for (;;) {
  try {
    const { result = [] } = await telegram("getUpdates", { offset, timeout: 50, allowed_updates: ["message"] }, 60_000);
    for (const update of result) {
      offset = update.update_id + 1;
      if (update.message) handle(update.message).catch(console.error);
    }
  } catch (error) {
    console.error(error.message);
    await sleep(3000);
  }
}

Telegram file limits

These come from Telegram’s Bot API documentation:

How the bot sendsPhotosOther files
By URL (this bot)5 MB20 MB
Uploading the file10 MB50 MB

When Telegram refuses a file, the bot sends the media link instead, so the user still gets it. To send files up to 50 MB, download the file on your server and upload it as multipart/form-data. Media links expire within minutes to hours.

What it costs to run

One credit per returned Instagram, TikTok, X or Pinterest file and two per Story, Highlight or Telegram file. A carousel costs one credit per slide. Failed links cost nothing. The smallest pack is $19 for 7,500 results.

PackPriceCreditsPer 1,000 results
Launch$197,500$2.53
Growth$7940,000$1.98
Scale$299200,000$1.50
Business$999750,000$1.33

Keeping it running

  • Run one copy only. Telegram refuses a second getUpdates call with the same token.
  • Keep it alive with systemd or pm2, and keep both secrets in the environment.
  • Tell your users to download only what they have the right to keep.

Or we build it for you

We set up a branded Telegram bot as a $149 add-on to our website packages.

Errors

HTTPCodeWhat to do
402INSUFFICIENT_CREDITSTop up, or lower max_results: a request reserves max_results times the credits per result first.
422UPSTREAM_REJECTEDThe post is private, deleted, expired or login-only. Show the message to your user. Nothing was charged.
429RATE_LIMITED, CONCURRENCY_LIMITWait for Retry-After seconds and retry. 120 requests per minute per account; 2 at once on the trial, 8 after buying credits.
502, 504UPSTREAM_*Temporary platform failure. Safe to retry. Nothing was charged.

Every error body is JSON with a stable code and a readable message. The API reference has the full list.

FAQ

Why do long videos arrive as a link?

Telegram fetches a file from a URL only up to 20 MB, or 5 MB for photos. When it refuses, the bot sends the media link instead.

Can the bot send files bigger than 20 MB?

Yes, up to 50 MB, if your server downloads the file and uploads it to Telegram. Telegram's docs say a local Bot API server can upload up to 2000 MB.

Can it download private channels or accounts?

No. Only public content works. Private, deleted and login-only posts return an error, and the bot passes the message to the user.

Do I need to pay to test it?

No. You get 20 free results after you verify your email, with no card.

Which languages can I build this in?

Any language that can send HTTPS requests. The Python and PHP guides show the same API call.

Updated 2026-11-04. The code on this page is run by our test suite against the API server before every release.

Make your first call in five minutes

20 free results when you verify your email. No card needed.

Create a free account