yt

small http api over yt-dlp. metadata, download jobs with live progress over sse, any of the ~1800 sites yt-dlp supports. plus x.mnl.rocks/<link> to embed anything anywhere.

try it

any codec
h264
vp9
av1
native
mp4
mkv
webm

quick start

# one shot
curl -OJ '/v1/download?url=https://youtu.be/jNQXAC9IVRw&quality=1080'

# job + live progress
curl -X POST /v1/jobs -H 'content-type: application/json' \
  -d '{"url":"https://youtu.be/jNQXAC9IVRw","codec":"h264","container":"mp4"}'
curl -N /v1/jobs/{id}/events
curl -OJ /v1/jobs/{id}/file

routes

urlstringpage url, required
playlistboolresolve the playlist when the url has a video and a list
rawbooluntouched yt-dlp -J output

{"count": 1800, "extractors": ["youtube", ...]}

json body (or form params). everything but url is optional.

urlstringpage or direct media url
qualitystringbest · audio · max height like 1080
codecstringany · h264 · hevc · vp9 · av1, preference with fallback
containerstringmp4 · webm · mkv · mov
audio_onlyboolextract audio
audio_formatstringbest · mp3 · m4a · aac · opus · vorbis · flac · wav · alac
formatstringraw yt-dlp -f, overrides quality/codec
sortstringraw yt-dlp -S
start, endstringtrim, seconds or [hh:]mm:ss
subtitlesstring[]languages to embed, e.g. ["en","pt.*"]
embed_*boolembed_metadata · embed_thumbnail · embed_chapters
sponsorblock_removestring[]sponsor, selfpromo, intro, ...
playlist_itemint1-based item when the url is a playlist
argsstring[]extra raw yt-dlp args; filesystem/exec/output flags are rejected

event: job with the full job on every change, event: end when finished, : ping every 15s.

const es = new EventSource(`/v1/jobs/${id}/events`)
es.addEventListener('job', (e) => {
  const job = JSON.parse(e.data)
  // job.status: queued | running | done | failed | canceled
  // job.stage: resolving | downloading | processing
  // job.progress: { percent, downloaded_bytes, total_bytes, speed, eta, part, parts }
})
es.addEventListener('end', () => es.close())
{
  "id": "8bf05386f9952365df",
  "status": "running",
  "stage": "downloading",
  "media": { "title": "Me at the zoo", "extractor": "Youtube", "duration": 19, ... },
  "progress": { "percent": 55.6, "downloaded_bytes": 74865810, "total_bytes": 134589086, "speed": 40995599, "eta": 1, "part": 1, "parts": 2 },
  "file": { "name": "Me at the zoo [jNQXAC9IVRw].mp4", "size": 744412, "content_type": "video/mp4" },
  "error": { "code": "ytdlp_error", "message": "..." },
  "links": { "self": "...", "events": "...", "file": "..." }
}

409 while not done, ?inline=1 for inline disposition. files are deleted 5 minutes after the job finishes.

204. kills yt-dlp if running and removes the file.

same options as query params. lists comma-separated, raw args as repeated arg. closing the connection cancels the job.

everything needed to embed a link: title, author, thumbnail, size and a player. youtube reuses youtube’s own player; x, tiktok and everything else get a progressive mp4 served by this instance.

or just put a link after the slash on x.mnl.rocks and paste that anywhere that unfurls links (discord, telegram, slack…):

https://x.mnl.rocks/https://x.com/jack/status/20
https://x.mnl.rocks/youtu.be/jNQXAC9IVRw
{
  "type": "video",                      youtube | video
  "provider": "Twitter", "title": "…", "author": "…", "thumbnail": "…", "width": 1280, "height": 720,
  "embed_url":  "https://x.mnl.rocks/https://x.com/…",   page with og:video / twitter:player tags
  "player_url": "…",                                       iframe-able player
  "video_url":  "https://x.mnl.rocks/media?url=…",         progressive mp4, range requests ok
  "native": true,                                          resolved without yt-dlp
  "html": "<video …>"
}

{"ok": true, "yt_dlp": "2026.08.19"}

errors are always {"error": {"code", "message"}}.