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
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}/fileroutes
| url | string | page url, required |
| playlist | bool | resolve the playlist when the url has a video and a list |
| raw | bool | untouched yt-dlp -J output |
{"count": 1800, "extractors": ["youtube", ...]}
json body (or form params). everything but url is optional.
| url | string | page or direct media url |
| quality | string | best · audio · max height like 1080 |
| codec | string | any · h264 · hevc · vp9 · av1, preference with fallback |
| container | string | mp4 · webm · mkv · mov |
| audio_only | bool | extract audio |
| audio_format | string | best · mp3 · m4a · aac · opus · vorbis · flac · wav · alac |
| format | string | raw yt-dlp -f, overrides quality/codec |
| sort | string | raw yt-dlp -S |
| start, end | string | trim, seconds or [hh:]mm:ss |
| subtitles | string[] | languages to embed, e.g. ["en","pt.*"] |
| embed_* | bool | embed_metadata · embed_thumbnail · embed_chapters |
| sponsorblock_remove | string[] | sponsor, selfpromo, intro, ... |
| playlist_item | int | 1-based item when the url is a playlist |
| args | string[] | 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"}}.