YouTube to MP3 with an API — two calls, one signed link
There is no “give me the MP3” endpoint that hides the work, because the file does not exist until it is built. Instead there are two honest calls: info tells you what the audio weighs, create builds it and returns a link with an expiry. This guide walks through both and what to do with the link.
Updated
Ask what the audio weighs
info answers with audio_formats[]: an m4a row with the exact byte size and an mp3 row whose size is an estimate. Title, duration and thumbnail come along, so a bot can show the user what it is about to send before spending 10 credits on the file.
Terminalcurl -s "https://api.saveapi.org/v1/youtube/info" \ -H "Authorization: Bearer sk_live_xxxx" \ -G --data-urlencode "url=https://www.youtube.com/watch?v=dQw4w9WgXcQ"
Build the file
The same link plus quality=mp3 (or m4a). The response carries url, filename with the title already in it, mode "audio", file_size and expires. Nothing is stored on our side: the bytes are produced as you read the link.
Terminalcurl -s "https://api.saveapi.org/v1/youtube/create" \ -H "Authorization: Bearer sk_live_xxxx" \ -G --data-urlencode "url=https://www.youtube.com/watch?v=aqz-KE-bpKQ" \ --data-urlencode "quality=mp3"
Read the link correctly
It is a stream, not a stored file: no Content-Length, no Range, one pass from start to end. After the expires timestamp it answers 410 — call create again rather than keeping links around. Treat it as a download that has to happen now.
Fetch it and hand it on
Stream the response to disk under the filename you were given, then send the file from your bot or store it where you need it. Send the file, not the link: Telegram fetches URLs itself with its own limits (20 MB for audio by URL), and a stream without a declared size is a poor fit for that.
Pythonimport httpx
def fetch_audio(made: dict, dest: str) -> str:
"""Pull the file behind a /v1/youtube/create link onto disk."""
path = f"{dest}/{made['filename']}"
with httpx.stream("GET", made["url"], timeout=None) as r, open(path, "wb") as f:
if r.status_code == 410: # past expires: call create again
raise RuntimeError("link expired")
for chunk in r.iter_bytes(): # no Content-Length: read to the end
f.write(chunk)
return path
# made = youtube_link(url, "mp3") # from the yt-dlp guide
# audio = fetch_audio(made, "/tmp")
# bot.send_audio(chat_id, open(audio, "rb"), title=made["title"])MP3 or M4A
M4A is the AAC track YouTube publishes: exact size, no transcoding, the stream starts immediately. MP3 is built from it with ffmpeg, so it takes longer to begin and its size is estimated. Pick M4A unless the player on the other end insists on MP3 — Telegram plays both.
Questions before you build
- Do I have to call info first?
- No. create works on its own with quality=mp3 or m4a — 10 credits. info is for showing the size or the title before you commit, and it costs 2.
- Can I choose the bitrate?
- No. One audio rendition per format: the best M4A the source publishes and a single MP3 encode. The quality parameter accepts mp3 or m4a.
- What if the video is unavailable?
- The response has success: false and a stable code from the same error table as every other platform. A link YouTube itself refuses costs 0.1 credit; a failure on our side costs nothing and is safe to retry.
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.