One API to fetch and download media from 18 social platforms.
Resolve a link, choose a format, and download. Media is streamed or prepared temporarily; metadata is stored.
Frontend repository · API reference · VPS deployment · OpenAPI
Tip
Want the web app? The ready-made frontend for this API is ssanaullahrais/blazfetch-web: start this backend, then run the frontend on top of it.
It takes a link from YouTube, TikTok, Instagram, X/Twitter, Facebook, Reddit, Vimeo, Dailymotion, Bluesky, Streamable, Rutube, SoundCloud, Snapchat, Twitch, Pinterest, Loom, Newgrounds or Tumblr and lets you download the video, audio or photos in it. It doesn't keep a copy of anyone's media: it fetches the file, checks that it actually works, sends it straight through to whoever asked, and deletes any temporary files afterward. (It does remember each link's details, such as title and formats, so repeat requests are instant. See Stored media.)
Click the animation to open the full video (demo.mp4).
- 18 platform adapters: metadata, formats and downloads. See sample status for known limitations.
- Three ways to download with a single request, and streaming always comes first (preparing a file puts load on
the server):
streamandautopipe the file straight through (merges and HLS included, preferring an H.264 file so it plays on phones) and fall back to preparing it on the server only if streaming fails before the first byte;preparebuilds a compatible H.264/AAC file first. - Audio as MP3 on request:
AUDIO_FORCE_MP3=truedelivers every audio download as MP3 (converted live, or through a temporary file that is deleted after sending). - Everything fetched is remembered forever (metadata only), with stable page paths like
/youtube/Cwkej79U3ek, a weekly check that notices deleted videos, and usage statistics. - Best-effort recovery: built-in platform fallbacks and an optional independent provider.
- Your choice of database: SQLite (default, nothing to install), PostgreSQL, MySQL/MariaDB or MongoDB.
- Optional bot protection: Cloudflare Turnstile can be switched on from
.env(off by default). - Temporary media only: prepared files are removed after delivery, cancellation, failure or expiry; cancellation stops download processes.
Needs Node.js 22+, current yt-dlp with EJS support, and ffmpeg (see Install).
npm install
npm run setup # pick a database (press Enter for SQLite); creates .env and the tables
npm run dev # http://localhost:4000# 1. look up a link (metadata, thumbnail, formats)
curl -X POST http://localhost:4000/api/v1/fetch -H "content-type: application/json" -d '{"url":"https://www.tiktok.com/@scout2015/video/6718335390845095173"}'
# 2. download it in one request (best quality; falls back automatically if it can't be streamed)
curl -G http://localhost:4000/api/v1/stream --data-urlencode "url=https://www.tiktok.com/@scout2015/video/6718335390845095173" -d mode=auto -o video.mp4Real responses for every platform are in docs/API.md.
Sample results are network-dependent, not a guarantee for every URL. See latest sample checks and response examples.
| Platform | Status | Notes |
|---|---|---|
| YouTube | ✅ Sample passed | Video/audio transfers and playlist listing; fallback availability varies |
| TikTok | ✅ Confirmed | yt-dlp primary, @tobyg74/tiktok-api-dl fallback |
| ✅ Confirmed | Posts and reels (profile listing is not supported) | |
| X / Twitter | ✅ Confirmed | Both x.com and legacy twitter.com. Posts whose media X hides from yt-dlp ("No video could be found in this tweet", often sensitive posts) are served by a fallback provider (FixTweet), like TikTok's |
| ✅ Confirmed | Reels and public videos. Posts that need a login return an error | |
| ✅ Confirmed | Including separate video+audio DASH streams | |
| Vimeo | ✅ Confirmed | Auto-retries via embed URL when watch page requires login. DRM-protected videos return a clear error |
| Dailymotion | ✅ Confirmed | Some clips have no audio at the source — not a bug |
| Bluesky | ✅ Confirmed | |
| Streamable | ✅ Confirmed | |
| Rutube | ✅ Confirmed | |
| SoundCloud | ✅ Confirmed (single track) | Profile/browse pages rejected with a clear error. DRM-protected (Go+) tracks can't be downloaded |
| Snapchat | ✅ Confirmed | |
| Twitch | ✅ Samples passed | Clip and complete 50-minute VOD at 160p; other qualities remain source-dependent |
| ✅ Confirmed (pins + boards) | Public API, no auth needed — see below | |
| Loom | ✅ Confirmed | Video and audio, including HLS streams |
| Newgrounds | Latest test network returned HTTP 403 | |
| Tumblr | ✅ Sample passed | Public video, including generic-extractor recovery |
1. System tools (install first; Node.js 22+ is required for the YouTube solver):
pip install -U "yt-dlp[default]"
# ffmpeg: sudo apt-get install ffmpeg (Linux) or https://ffmpeg.org/download.htmlVerify each:
node --version
yt-dlp --version
ffmpeg -version && ffprobe -version2. Project setup (one command picks and configures your database):
npm install
npm run setup # choose SQLite / PostgreSQL / MySQL / MongoDB; writes .env and creates the tables
npm run diagnostics # checks the database, yt-dlp and ffmpeg are all reachable
npm run dev # starts on http://localhost:4000npm run setup asks which database to use. SQLite is the default and needs nothing installed:
just press Enter and you're running. Pick another if you already have a server:
| Database | Status | You need | Connection URL example |
|---|---|---|---|
| SQLite (default) | ✅ Confirmed | nothing | none (the file is created automatically) |
| PostgreSQL 14+ | ✅ Confirmed | an empty database created first | postgres://user:password@localhost:5432/blazfetch |
| MySQL 8+ / MariaDB | ✅ Confirmed | an empty database created first | mysql://user:password@localhost:3306/blazfetch |
| MongoDB 6+ | ✅ Confirmed | a running server | mongodb://localhost:27017/blazfetch |
Confirmed = tested end to end from a fresh clone: setup, server start, fetch, download, and rows written to the jobs, cache and stats storage.
Setup creates the tables, not the database itself. For PostgreSQL run createdb -E UTF8 blazfetch first,
for MySQL CREATE DATABASE blazfetch CHARACTER SET utf8mb4;. All four store the same data (metadata cache, jobs, stats,
never the downloaded media) and the app behaves identically on each.
npm run setup starts from .env.example, so a fresh install gets the recommended settings, including
AUDIO_FORCE_MP3=true (every audio download is delivered as MP3; see Optional: everything as MP3).
Prefer to configure by hand? Copy .env.example to .env, set DATABASE_DRIVER (sqlite,
postgres, mysql or mongodb) and DATABASE_URL, then run npm run migrate. Setup can also run
non-interactively: npm run setup -- --driver=postgres --url=postgres://user:pass@host:5432/blazfetch.
npm run typecheck and npm test verify the project builds and passes its test suite.
See docs/API.md for every endpoint with full request/response examples (including a real response for each platform) and a frontend walkthrough. The endpoints:
POST /api/v1/fetch resolve metadata + formats (rangeStart/rangeEnd for Pinterest boards)
POST /api/v1/fetch/audio resolve audio options
GET /api/v1/media/<platform>/<id> stored media by stable path (e.g. /youtube/Cwkej79U3ek)
GET /api/v1/media/<platform>/<id>/logs that media's download logs (public, persisted, YouTube only for now)
GET /api/v1/stream one-request download: ?mode=stream (default) | prepare | auto
POST /api/v1/download start a download job (formatId defaults to "best")
GET /api/v1/jobs/:id poll job status/progress
GET /api/v1/downloads/:id stream the file once the job is ready
DELETE /api/v1/downloads/:id cancel + clean up temp files
DELETE /api/v1/jobs/:id cancel a job
POST /api/v1/playlist/download prepare a bounded playlist batch
GET /api/v1/playlist/downloads/:id poll batch and per-item results
DELETE /api/v1/playlist/downloads/:id cancel unfinished playlist work
GET /api/v1/platforms list supported platforms + domains
GET /api/v1/stats public all-time totals: successful fetches and downloads (total and per platform)
GET /api/v1/config public settings (is Turnstile on, and its site key)
POST /api/v1/turnstile/verify swap a solved Turnstile token for a pass cookie (only when Turnstile is on)
GET /health, /health/ready liveness / readiness (DB, yt-dlp, ffmpeg)
| You want | Use |
|---|---|
| It to work for every source in one request, streaming when it can (recommended, default) | GET /stream?mode=auto |
| Fastest start, nothing on the server's disk (may not play on some phones for VP9/AV1/HEVC sources) | GET /stream?mode=stream |
| Compatible H.264/AAC output | GET /stream?mode=prepare |
| Server-side progress for a long file | POST /download, then poll GET /jobs/:id |
Set TURNSTILE_ENABLED=true with a site key and secret key (see .env.example) and fetching, streaming and
downloading need a passed check. The official frontend handles it by itself and only shows the widget when Cloudflare needs a
click. Details and setup: docs/API.md and
docs/REQUIREMENTS.md. If you build your own frontend, call
GET /config, render the widget, then POST /turnstile/verify when the visitor first does something protected.
Set AUDIO_FORCE_MP3=true in .env (restart the server) and every audio download is delivered as an MP3. /fetch,
/fetch/audio and /media/... then list all audio options as MP3 (the format ids stay the same), and any source that
is not MP3 already is converted with ffmpeg: piped live in stream/auto mode with no file on disk, or, in
prepare and POST /download mode, saved to a temporary file, converted, sent and deleted. The shipped .env.example turns it on (recommended); if the variable is left out it is off.
The official one already exists: blazfetch-web. To build your own:
- Send
credentials: 'include'on every request: the server identifies visitors with a guest cookie (rate limits and concurrency use it). - Put your frontend's exact origin in
CORS_ALLOWED_ORIGINS(*does not work with cookies). - Use
stored.pathfrom a fetch response for your own page URLs, and callGET /media/<platform>/<id>to render them.
Everything fetched is kept in the database forever (metadata only, never the media files), so repeat
requests are answered in milliseconds and every item gets a stable path such as
/youtube/Cwkej79U3ek (playlists: /youtube/Cwkej79U3ek/playlist/RDCwkej79U3ek). Each item is
re-checked every 7 days, so deleted or private videos are noticed and answer a clear
410 MEDIA_UNAVAILABLE instead of stale data, and usage statistics are stored per item and per event.
See docs/API.md.
npm run setup writes the database settings; everything else has a working default. The settings you
are most likely to change (all in .env, full list with comments in .env.example):
| Setting | Default | What it does |
|---|---|---|
DATABASE_DRIVER, DATABASE_URL |
sqlite |
Which database, and where (npm run setup sets these) |
CORS_ALLOWED_ORIGINS |
http://localhost:3000 |
Your frontend's origin(s), comma separated. * is refused in production |
TRUST_PROXY |
0 |
Set to 1 behind Nginx so real visitor IPs are seen |
HOST |
0.0.0.0 |
Set 127.0.0.1 behind same-host Nginx; keep the backend port private |
API_AUTH_ENABLED |
false |
Require a manual server-to-server key; see API protection |
API_AUTH_KEY |
empty | Private 32-256 character key sent by the trusted proxy in X-API-Key, never frontend code |
DEFAULT_DOWNLOAD_MODE |
stream |
Default for GET /stream when a request has no ?mode= |
STREAM_MODE_ENABLED |
true |
Turn GET /stream off entirely |
REVALIDATE_AFTER_SECONDS |
604800 (7 days) |
How often each stored item is re-checked |
TURNSTILE_ENABLED, TURNSTILE_SITE_KEY, TURNSTILE_SECRET_KEY, TURNSTILE_SESSION_SECONDS |
false, 1800 s |
Optional Cloudflare Turnstile bot check in front of fetch, stream and download |
YOUTUBE_FALLBACK_ENABLED |
true |
Use a fallback provider when YouTube blocks the server |
MAX_PLAYLIST_ITEMS / MAX_PLAYLIST_DOWNLOAD_ITEMS |
1000 / 200 |
Separate listing and bulk preparation limits |
PLAYLIST_DOWNLOAD_CONCURRENCY |
1 |
Bulk workers (1–4), also bounded by download limits |
MAX_CONCURRENT_DOWNLOADS_GLOBAL / _PER_GUEST / _PER_IP |
10 / 1 / 5 |
Concurrency limits (guests on one IP share _PER_IP) |
MAX_CONCURRENT_CONVERSIONS / FFMPEG_THREADS |
0 / 0 (auto) |
H.264/MP3 conversions allowed at once (auto: half the CPU cores) and the threads each gets; the rest queue, so a burst of conversions cannot exhaust the server |
MEDIA_PROCESS_NICE |
10 |
yt-dlp and ffmpeg run at this lower priority, so the API keeps answering under load |
MIN_FREE_DISK_MB |
1024 |
Downloads prepared on disk are refused ("server busy") while TEMP_DIR has less free space |
RATE_LIMIT_MAX_GUEST / RATE_LIMIT_MAX_DOWNLOAD |
30 / 10 per minute |
Rate limits per visitor |
RATE_LIMIT_IP_MULTIPLIER |
10 |
Per-IP ceiling, as a multiple of the per-visitor limits (only when TRUST_PROXY is set correctly behind a proxy) |
MAX_DOWNLOAD_SIZE_BYTES, TEMP_DIR |
2 GB, ./tmp |
Size cap and temporary folder |
For existing deployments, update MAX_PLAYLIST_ITEMS explicitly, restart, and force-refresh cached playlists. See playlist and fallback setup for bulk jobs and optional providers.
See SECURITY.md for how to report a problem, what the project already protects against, and what to do when you deploy.
| Document | What's in it |
|---|---|
| docs/API.md | Every endpoint, error codes, real responses per platform, stored media, modes |
| docs/examples/ | Each platform's response as a JSON file |
| docs/openapi.yaml | OpenAPI 3.0 definition (for generating a typed client) |
| docs/REQUIREMENTS.md | Step-by-step VPS deployment: Nginx, HTTPS, PM2, firewall, backups |
| docs/api-protection.md | Manual API key, frontend proxy and access checks |
| docs/playlist-and-fallback.md | Playlist limits, bulk jobs, optional fallback and smoke checks |
npm ci && npm run build && npm run migrate && npm startnpm run migrate upgrades the database in place and does nothing when it is already up to date, so run it on
every deploy. Full VPS deployment guide (Nginx, PM2, SSL, firewall, backups): docs/REQUIREMENTS.md.
Run one app instance per database: the weekly re-check job and the concurrency limits live inside the process (several instances would just repeat some checks).
Open source under the BlazFetch License: free to use, modify and deploy for personal, educational or other non-commercial purposes, but not to sell or run as a paid/SaaS product without permission, with a required credit. You may run it on your own server (including a VPS) and change it, but you may not sell the software or bundle it into anything sold, and modified versions stay under the same license. The official frontend's footer credit ("Open source on GitHub" and "Developed with ♥ by Sanaullah Rais") must stay visible on public deployments, and the license and copyright notices must be kept. These terms also apply when you use an AI assistant to change or deploy it, and AGENTS.md tells AI assistants to refuse to remove them or to help sell it or set it up as a SaaS. Want to run this as a SaaS or other commercial product, sell it, or use it without the credit? Get in touch to discuss it in the GitHub Discussions — a fee may apply.
Unit and integration tests cover the API, stream modes, media storage and database drivers.
npm test # unit + integration tests (no network needed)
npm run typechecknpm run diagnosticsshows database/yt-dlp/ffmpeg status and versions.GET /health/readydoes the same over HTTP.- Update yt-dlp regularly (
pip install -U yt-dlp): platforms change how they serve video. - YouTube says "confirm you're not a bot": check the exact URL from the deployment host, update yt-dlp/EJS, and verify proxy/fallback availability.
fallbackUsedidentifies successful recovery. A cooldown reduces repeated requests; block duration is not predictable. - Newgrounds, Facebook or Rutube fail with a
403-style error: the site blocks some server IPs. Try again later or from another network. LOGIN_REQUIRED,PRIVATE_MEDIA,AGE_RESTRICTED,MEDIA_UNAVAILABLE: the media itself needs a login, is private or age-gated, or was deleted. Nothing to fix on the server.- Extractor errors return a normalized code (
EXTRACTOR_FAILED, ...); raw yt-dlp output is only in the server logs. - Sources that cannot be streamed (Loom, Reddit) are prepared on the server instead, in every mode.
