DEV Community

Ahror
Ahror

Posted on Originally published at saveapi.org

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.

1. 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.

pip install aiogram httpx
Enter fullscreen mode Exit fullscreen mode

2. 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.

import 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()
Enter fullscreen mode Exit fullscreen mode

3. 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.

from 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"])
Enter fullscreen mode Exit fullscreen mode

4. 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.

EXPLAIN = {
    "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.",
}
Enter fullscreen mode Exit fullscreen mode

5. 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.

sent = 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)
Enter fullscreen mode Exit fullscreen mode

6. 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.

from 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)
Enter fullscreen mode Exit fullscreen mode

FAQ

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.


This guide was first published on saveapi.org. The API used here has a free tier — 1,000 credits, no card: get a key. Docs: saveapi.org/en/docs.

Prefer a client library? pip install saveapi (Python) or npm install saveapi (JavaScript/TypeScript) — github.com/akhroruz/saveapi-sdk.

Related: Instagram Downloader API · TikTok Video Downloader API · Likee Video Downloader API

Top comments (0)