How to build a Telegram video downloader bot
A link-in, video-out bot is about forty lines once the resolving is somebody else's problem. This guide is the whole path: link to media URL, media URL to a Telegram message, and the three things that break in production — private posts, files Telegram refuses to fetch, and re-sending the same video a hundred times.
Updated
Install the two libraries
aiogram talks to Telegram, httpx makes the API call. Nothing else is needed on your side: there is no scraping stack, no headless browser and no cookie jar to keep alive.
Terminalpip install aiogram httpx
Resolve the link
One GET with your key in the Authorization header. The endpoint detects the platform from the URL itself, so the bot does not branch on the domain — an Instagram reel, a TikTok video and a Likee clip all go through this same function.
Pythonimport httpx
API = "https://api.saveapi.org/v1/download"
KEY = "sk_live_xxxx"
client = httpx.AsyncClient(timeout=30)
async def resolve(link: str) -> dict:
r = await client.get(
API,
params={"url": link},
headers={"Authorization": f"Bearer {KEY}"},
)
return r.json()Answer with the media
The response tells you what it is. Check medias[0].type instead of assuming a video: a TikTok slideshow and an Instagram carousel come back as images, and a Telegram bot has to call a different method for those. meta.title makes a reasonable caption.
Pythonfrom aiogram import Bot, Dispatcher, F
from aiogram.types import Message
dp = Dispatcher()
@dp.message(F.text.startswith("http"))
async def on_link(message: Message):
data = await resolve(message.text.strip())
if not data["success"]:
await message.answer(EXPLAIN.get(data["error"]["code"], "Try another link."))
return
media = data["medias"][0]
if media["type"] == "video":
await message.answer_video(media["url"], caption=data["meta"]["title"])
else:
await message.answer_photo(media["url"])Explain the errors people actually hit
Users paste private posts and dead links all day. Map the stable error codes to sentences a person understands — a bot that says "That account is private" gets a different link, a bot that says "Error 403" gets an angry message. A private post or one with no media costs 0.1 credits instead of the full 1.5.
PythonEXPLAIN = {
"PRIVATE_CONTENT": "That account is private — I can only fetch public posts.",
"MEDIA_NOT_FOUND": "That post has no media, or it was deleted.",
"UNSUPPORTED_PLATFORM": "I do not know that site yet.",
"QUOTA_EXCEEDED": "Out of credits for today.",
"UPSTREAM_TIMEOUT": "The source was slow. Send the link again.",
}Cache Telegram's file_id, not the media URL
Once Telegram has the file it gives you a file_id, and re-sending that costs nothing and uploads nothing. The media URL is the wrong thing to store: source CDNs sign their links and expire them within hours, so a cache of URLs turns into a cache of 403s overnight.
Pythonsent = await message.answer_video(media["url"]) file_id = sent.video.file_id # BU saqlanadi, media["url"] emas # Keyingi safar o'sha post so'ralsa: await message.answer_video(file_id)
Fall back to uploading bytes
Telegram fetches the URL itself, and sometimes the source CDN refuses it — most often for Instagram, and always in a media group. Catch that case, download the bytes yourself and upload them. The 50 MB bot upload limit still applies, which is why this is a fallback and not the default path.
Pythonfrom aiogram.types import BufferedInputFile
async def send_bytes(message: Message, url: str):
r = await client.get(url, follow_redirects=True)
file = BufferedInputFile(r.content, filename="video.mp4")
await message.answer_video(file)Questions before you build
- Can the bot send files larger than 50 MB?
- Not by uploading them: 50 MB is Telegram's ceiling for a bot upload. The usual way around it is to send the media URL and let Telegram fetch it, or to keep large files in a private channel and copy the message by file_id.
- Do I need a separate handler for each platform?
- No. /v1/download detects the platform from the link, so one handler covers every supported site. Per-platform endpoints exist for when you deliberately want to accept only one.
- What happens when a user pastes a private post?
- You get a 403 with the PRIVATE_CONTENT code and it costs 0.1 credits instead of the full 1.5. Turn it into a sentence: people paste private links constantly and they are not doing anything wrong.
Platform references used here
Other guides
Run this guide with your own key
Key in under a minute. 1,000 free credits, no card, no expiry.