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.

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
- Open
@BotFatherin Telegram, send/newbotand copy the token. - Create a free account with a real email address and verify it. You get 20 free credits, no card needed.
- 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. - Save the code below as
bot.mjsand runBOT_TOKEN=123:abc GIS_KEY=gis_live_... node bot.mjs. - 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, 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 sends | Photos | Other files |
|---|---|---|
| By URL (this bot) | 5 MB | 20 MB |
| Uploading the file | 10 MB | 50 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.
| Pack | Price | Credits | Per 1,000 results |
|---|---|---|---|
| Launch | $19 | 7,500 | $2.53 |
| Growth | $79 | 40,000 | $1.98 |
| Scale | $299 | 200,000 | $1.50 |
| Business | $999 | 750,000 | $1.33 |
Keeping it running
- Run one copy only. Telegram refuses a second
getUpdatescall 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
| HTTP | Code | What to do |
|---|---|---|
| 402 | INSUFFICIENT_CREDITS | Top up, or lower max_results: a request reserves max_results times the credits per result first. |
| 422 | UPSTREAM_REJECTED | The post is private, deleted, expired or login-only. Show the message to your user. Nothing was charged. |
| 429 | RATE_LIMITED, CONCURRENCY_LIMIT | Wait for Retry-After seconds and retry. 120 requests per minute per account; 2 at once on the trial, 8 after buying credits. |
| 502, 504 | UPSTREAM_* | 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.
Related
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