FastSaverAPI moved. The API and dashboard now live at api.fastsaver.iowhat changed.

Telegram bot media API — audio without the re-upload

The Telegram bot media API turns a YouTube video ID into a Telegram file_id, so your bot answers with a string instead of a file. Millions of tracks are already cached and cost 7 credits; a track we have to fetch first is slower and costs 15. Charts, search and song recognition from the same API all hand you an ID this call accepts.

Last updated

Endpoint
POST /youtube/audio/tg-bot
Credits
7 on a cache hit, 15 on a miss
Returns
a Telegram file_id
Scope
valid only for the bot_username you send

the primitive

what a file_id is, precisely

When a bot sends a file, Telegram stores it and returns an identifier: a file_id, an opaque string like CQACAgIAAxkDAAIVKWk…. Not a URL, not a hash of the audio — a pointer into Telegram's storage, and the cheapest thing a bot can send, because sending it moves no bytes through your infrastructure.

Two properties shape the design. A file_id is bound to one bot; Telegram will not accept one bot's identifier from another. The same file also carries a file_unique_id that is stable across bots, but you cannot send or download with it — it only tells you two references point at the same audio.

The trade: pay once for a bot-scoped reference, and every later send of that track is one ordinary Telegram call carrying a string. Store the ones you get — the first resolve is the only part that costs credits, and it costs more than you might expect when the track is new to us.

why it exists

the loop this replaces

Otherwise, delivering one song means: resolve the track, stream it from a CDN to your server, hold it in memory or on disk, upload the same bytes to Telegram as multipart form data, then keep the file_id so the next user is cheaper.

  • Bandwidth is doubled. Every megabyte arrives at your server and leaves again. On metered egress that is the cost of the feature.
  • The upload leg is the slow one. Pulling from a CDN is fast; pushing to Telegram from a small VPS usually is not.
  • 50 MB is a hard ceiling. The Bot API refuses larger uploads, so long mixes cannot be delivered this way at all.
  • Concurrency becomes capacity planning. Twenty requests at once means twenty in-flight files, temp-file cleanup and a worker queue.

The tg-bot endpoint removes all four: the file never touches your process.

usage

the request

One POST, two fields. video_id is the YouTube or YouTube Music ID — the eleven characters, not the whole link — and bot_username is your bot, with the @.

POST https://api.fastsaver.io/v1/youtube/audio/tg-bot 7 cached · 15 on a miss
request
curl -X POST https://api.fastsaver.io/v1/youtube/audio/tg-bot \
  -H "X-Api-Key: fs_sk_•••••••••••" \
  -H "Content-Type: application/json" \
  -d '{"video_id": "vk6014HuxcE", "bot_username": "@your_bot"}'
200 OK · response
{
  "ok": true,
  "file_id": "CQACAgIAAxkDAAIVKWk..."
}

That is the entire response: a delivery handle, no title, no artist, no duration. Keep the metadata from whatever call gave you the video_id — you will want it for the caption.

The same response shape covers both prices. If we already hold the track the call is 7 credits and returns almost immediately; if we do not, the track is downloaded first and the call is billed at the YouTube download rate of 15. Nothing in the payload tells you which happened, so read your spend from GET /balance or your dashboard rather than assuming.

Treat ok as the success flag rather than the status code. A refusal arrives as a plain 400 with a detail string — an exhausted balance reads Insufficient credits. Please top up to your account., and there is no 402 to branch on. 401 means the key never arrived, 429 that you are over your plan's per-minute limit. A failed resolve is not free either: every failure path charges a flat 0.1 credits, so a retry loop over an ID that will never resolve still costs something.

code

the smallest bot that sends one

The endpoint reduces to two lines of bot code: POST the video ID, pass the returned string to answer_audio. Everything below is scaffolding around those two lines.

minimal aiogram 3 demo
import asyncio, os
import httpx
from aiogram import Bot, Dispatcher, F
from aiogram.types import Message

API = "https://api.fastsaver.io/v1"
BOT_USERNAME = "@your_bot"

bot = Bot(os.environ["BOT_TOKEN"])
dp = Dispatcher()
http = httpx.AsyncClient(
    headers={"X-Api-Key": os.environ["FASTSAVER_KEY"]}, timeout=120,
)


@dp.message(F.text.regexp(r"^[\w-]{11}$"))
async def on_video_id(msg: Message) -> None:
    r = await http.post(
        f"{API}/youtube/audio/tg-bot",
        json={"video_id": msg.text, "bot_username": BOT_USERNAME},
    )
    data = r.json()
    if not data.get("ok"):
        await msg.answer(data.get("detail", "could not resolve that ID"))
        return
    await msg.answer_audio(data["file_id"])          # a string, not a file


if __name__ == "__main__":
    asyncio.run(dp.start_polling(bot))

On python-telegram-bot the send becomes await update.message.reply_audio(audio=file_id). Either way, the argument that would normally be an open file object is a string.

This is deliberately the endpoint and nothing else. Search, an inline keyboard, storing the strings and the error paths belong to a real bot rather than to the call — the Telegram music bot guide builds that end to end.

composition

what else fits a bot

A bot is rarely just a downloader. Most other endpoints hand you a video_id that goes straight into the tg-bot call.

  • GET /youtube/search — 2 credits. Ten results per page, pages 1 to 3, each with video_id, title, duration and thumbnails: enough to label a row without a second lookup.
  • GET /shazam/top — 1 credit. Charts by country code or worldwide; results already carry video_id.
  • POST /shazam/identify — 5 credits, mp3/m4a/ogg/mp4 up to 50 MB. Bots may download from Telegram up to 20 MB, so a forwarded voice note fits: pull it with getFile, post it, offer the results back.
  • GET /shazam/lyrics — 2 credits, by title and artist. Both are required query parameters — there is no lookup by ID — and the reply carries ok and lyrics and nothing else, so keep the title and artist you already have for the header of your "lyrics" button.
  • POST /youtube/download with format: "audio" — 15 credits, when you need the file rather than a reference.
  • GET /balance — free. Poll it from a health check; learn about an empty balance before your users do.

GET /fetch covers the other platforms with a direct download_url. Telegram will fetch a remote URL for you, but its ceiling there is lower than the upload one — a convenience, not a strategy.

honesty

what a file_id will not do for you

  • One bot, one file_id. Swap or add a bot and every reference must be re-requested, and each re-request is charged again. Keep bot_username alongside the string.
  • Public content only. Public YouTube and YouTube Music IDs resolve. Private, members-only and region-blocked videos do not.
  • A miss is slower and costs 15 instead of 7. A cache hit hands back a track we already hold; a miss means downloading it first, and that is billed at the YouTube download rate. Storing the file_id yourself therefore saves real money and not just latency: every re-resolve you avoid is 7 credits you keep, and the first resolve of a track nobody has asked for yet is 15.
  • Two rate limits apply. Ours is per plan — 10 requests a minute on Free, 60 on Pro, up to 900 on Mega. Telegram runs its own flood control on top of that.
  • References are not permanent. A file_id points into storage you do not own. On a failed send, request a fresh one.
  • Platforms move. When YouTube changes something the fix ships on our side; your request shape stays put.

faq

questions about Telegram bot delivery

What exactly is a Telegram file_id?

An opaque string Telegram issues for a file already sitting on its servers. A handle, not a URL: nothing to open, nothing to download. You put it where the file would go in sendAudio and Telegram serves the bytes.

Can I reuse the same file_id in a second bot?

No. It is scoped to one bot; hand it to another and Telegram rejects the send with a bad-request error about the file identifier. Hence bot_username being required. Two bots means requesting the track twice and keeping two strings.

What does sending a track through a Telegram bot cost?

7 credits per call to POST /youtube/audio/tg-bot when we already hold the track, 15 when we do not and have to download it first — a miss is billed as a YouTube download. Add 2 for GET /youtube/search if that is how you found the ID. Hold on to the returned string and later sends are ordinary Telegram calls that spend nothing.

What happens when the track is not already cached?

It is downloaded fresh and you still get a file_id — but the call costs 15 credits instead of 7, because there is nothing to reference until the download finishes. It is slower for the same reason. Millions of tracks are pre-cached, so misses are the exception — but budget for them, and give the HTTP call a read timeout measured in minutes, not the handful of seconds most clients default to.

Can a Telegram bot send audio larger than 50 MB?

Not by uploading it — the Bot API caps a bot upload at 50 MB, which a long mix or podcast episode exceeds. A file_id sidesteps that: your bot uploads nothing, it references a file Telegram already holds. Self-hosting a Bot API server raises the ceiling, but that server is then yours to keep alive.

Send your first file_id

Free tier is 1,000 credits — around 140 cached tracks, or 66 if every one is new to the cache.