Simple and powerful REST API for downloading videos from YouTube.
7.2K
Open Source REST API for downloading YouTube videos and getting direct video links using yt-dlp.
β οΈ PUBLIC VERSION: This is the free, limited version with hardcoded limits (2 workers, 24h TTL, 256MB Redis). π Want more? Check out YouTube Downloader API Proβ - PostgreSQL storage, configurable TTL, processing results cache, and more!
English | Π ΡΡΡΠΊΠΈΠΉβ
Public version features:
docker pull alexbic/youtube-downloader-api:latest
docker run -d -p 5000:5000 --name yt-downloader alexbic/youtube-downloader-api:latest
# Health check
curl http://localhost:5000/health
# Download video (sync)
curl -X POST http://localhost:5000/download_video \
-H "Content-Type: application/json" \
-d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
β οΈ Note: The public version has embedded Redis. This example is for custom deployments only.
version: '3.8'
services:
youtube-downloader:
image: alexbic/youtube-downloader-api:latest
ports:
- "5000:5000"
volumes:
- ./tasks:/app/tasks
# - ./cookies.txt:/app/cookies.txt # optional
environment:
# Public base URL for external links (https://yourdomain.com/api)
PUBLIC_BASE_URL: ${PUBLIC_BASE_URL}
# API Key for authentication (Bearer token)
API_KEY: ${API_KEY}
restart: unless-stopped
git clone https://github.com/alexbic/youtube-downloader-api.git
cd youtube-downloader-api
pip install -r requirements.txt
python app.py
GET /health
Response:
{
"status": "healthy",
"timestamp": "2024-01-15T10:30:00.123456",
"auth": "enabled|disabled",
"storage": "redis|memory"
}
POST /download_video
Content-Type: application/json
Request (async):
{
"url": "https://www.youtube.com/watch?v=VIDEO_ID",
"async": true,
"quality": "best[height<=720]",
"webhook": {
"url": "https://your-webhook.com/callback",
"headers": {
"X-API-Key": "your-secret-key"
}
},
"client_meta": {"user_id": 123, "project": "demo"}
}
Request (sync):
{
"url": "https://www.youtube.com/watch?v=VIDEO_ID",
"quality": "best[height<=720]",
"client_meta": {"user_id": 123}
}
Parameters:
url (required, string) - YouTube video URLasync (optional, boolean) - async mode (default: false)quality (optional, string) - video quality (default: best[height<=720])
best[height<=480] - 480pbest[height<=720] - 720pbest[height<=1080] - 1080pbest - maximum qualitywebhook (optional, object) - webhook configuration (async mode only)
url (required, string) - webhook callback URLheaders (optional, object) - custom headers for webhook authenticationclient_meta (optional, object) - arbitrary JSON metadata (max 16KB)Response (sync) - Unified metadata structure:
{
"task_id": "abc123...",
"status": "completed",
"created_at": "2025-01-16T12:00:00.123456",
"completed_at": "2025-01-16T12:00:10.123456",
"expires_at": "2025-01-17T12:00:00.123456",
"input": {
"video_url": "https://www.youtube.com/watch?v=VIDEO_ID",
"operations": ["download_video"],
"operations_count": 1,
"video_id": "VIDEO_ID",
"title": "Video Title",
"duration": 180,
"resolution": "1280x720",
"ext": "mp4"
},
"output": {
"output_files": [
{
"filename": "video_20250116_120000.mp4",
"download_path": "/download/abc123.../video_20250116_120000.mp4",
"download_url_internal": "http://service.local:5000/download/abc123.../video_20250116_120000.mp4",
"download_url": "http://public.example.com/download/abc123.../video_20250116_120000.mp4",
"expires_at": "2025-01-17T12:00:00.123456"
}
],
"total_files": 1,
"metadata_url": "http://public.example.com/download/abc123.../metadata.json",
"metadata_url_internal": "http://service.local:5000/download/abc123.../metadata.json",
"ttl_seconds": 86400,
"ttl_human": "24h"
},
"webhook": null,
"client_meta": {"user_id": 123}
}
Note on URLs:
download_url_internalandmetadata_url_internal- always present (Docker network URLs)download_urlandmetadata_url- only present when bothPUBLIC_BASE_URLandAPI_KEYare configured
Response (async) - Minimal tracking structure:
{
"task_id": "abc123...",
"status": "processing",
"check_status_url": "http://public.example.com/task_status/abc123...",
"metadata_url": "http://public.example.com/download/abc123.../metadata.json",
"check_status_url_internal": "http://service.local/task_status/abc123...",
"metadata_url_internal": "http://service.local/download/abc123.../metadata.json",
"webhook": {
"url": "https://your-webhook.com/callback",
"headers": {"X-API-Key": "***"}
},
"client_meta": {"user_id": 123}
}
Important: In async mode, errors (private video, deleted, blocked, etc.) are returned only via /task_status/<task_id>. The initial POST response always contains only task_id and status.
GET /task_status/<task_id>
Response (processing):
{
"task_id": "abc123...",
"status": "processing"
}
Response (completed) - Same unified structure as sync mode:
{
"task_id": "abc123...",
"status": "completed",
"created_at": "2025-01-16T12:00:00.123456",
"completed_at": "2025-01-16T12:00:10.123456",
"expires_at": "2025-01-17T12:00:00.123456",
"input": {
"video_url": "https://www.youtube.com/watch?v=VIDEO_ID",
"operations": ["download_video"],
"operations_count": 1,
"video_id": "VIDEO_ID",
"title": "Video Title",
"duration": 180,
"resolution": "1280x720",
"ext": "mp4"
},
"output": {
"output_files": [
{
"filename": "video_20250116_120000.mp4",
"download_path": "/download/abc123.../video_20250116_120000.mp4",
"download_url_internal": "http://service.local:5000/download/abc123.../video_20250116_120000.mp4",
"download_url": "http://public.example.com/download/abc123.../video_20250116_120000.mp4",
"expires_at": "2025-01-17T12:00:00.123456"
}
],
"total_files": 1,
"metadata_url": "http://public.example.com/download/abc123.../metadata.json",
"metadata_url_internal": "http://service.local:5000/download/abc123.../metadata.json",
"ttl_seconds": 86400,
"ttl_human": "24h"
},
"webhook": {
"url": "https://your-webhook.com/callback",
"headers": {"X-API-Key": "***"},
"status": "delivered",
"attempts": 1,
"last_attempt": "2025-01-16T12:00:11.123456",
"last_status": 200,
"task_id": "abc123..."
},
"client_meta": {"user_id": 123}
}
Response (error):
{
"task_id": "abc123...",
"status": "error",
"operation": "download_video_async",
"error_type": "private_video|unavailable|deleted|...",
"error_message": "Error description",
"user_action": "Recommended action",
"raw_error": "...",
"failed_at": "2025-01-16T12:00:00.123456",
"client_meta": {"user_id": 123}
}
GET /download/<task_id>/<filename>
GET /download/<task_id>/metadata.json
| Variable | Default | Description |
|---|---|---|
API_KEY | β | Enables public mode (Bearer required). When unset, internal mode (no auth). |
PUBLIC_BASE_URL | β | External base for absolute URLs (https://domain.com/apiβ ). Used only if API_KEY is set. |
INTERNAL_BASE_URL | β | Base for background URL generation (webhooks, Docker network). |
LOG_LEVEL | INFO | Logging level (DEBUG, INFO, WARNING, ERROR, CRITICAL). |
Internal mode (auth=disabled):
API_KEY and no PUBLIC_BASE_URLrequest.host_url or INTERNAL_BASE_URLPublic mode (auth=enabled):
PUBLIC_BASE_URL and API_KEY are setPUBLIC_BASE_URLINTERNAL_BASE_URL or request.host_urlAuthorization: Bearer <API_KEY>/app/tasks/{task_id}/
βββ video_*.mp4 # Downloaded video files (TTL: 24 hours in public version)
βββ metadata.json # Task metadata (TTL: 24 hours in public version)
Cleanup (Public Version):
The public version includes a background webhook resender service that automatically retries failed webhook deliveries:
How it works:
completed or error that haven't received successful delivery (HTTP 200-299)/task/{task_id} response)Delivery attempts:
Configuration:
webhook.url and optional webhook.headers in each requestLOG_LEVEL=DEBUG for detailed webhook payload preview)YouTube may block downloads requiring authentication. Use cookies to bypass this.
Important:
Step 1: Enable extension in incognito mode
Chrome:
chrome://extensions/Firefox:
about:addonsStep 2: Export cookies
https://www.youtube.com/robots.txtyoutube.com using the extensionhttps://www.youtube.com/robots.txtcopy(document.cookie.split('; ').map(c => {
const [name, ...v] = c.split('=');
return `.youtube.com\tTRUE\t/\tTRUE\t0\t${name}\t${v.join('=')}`;
}).join('\n'))
cookies.txt# Netscape HTTP Cookie Filecookies.txt next to docker-compose.ymlvolumes:
- ./cookies.txt:/app/cookies.txt
docker-compose up -dDone! API automatically uses cookies and updates timestamp before each request.
YouTube is gradually requiring "PO Token" for downloads. If cookies don't help:
mweb client with PO TokenAdditional Resources:
If webhook.url is provided in POST /download_video, the service POSTs to the URL on task completion.
You can specify custom headers for each webhook using the webhook.headers field in the request body.
{
"url": "https://youtube.com/watch?v=...",
"async": true,
"webhook": {
"url": "https://your-webhook.com/endpoint",
"headers": {
"X-API-Key": "your-secret-key",
"Authorization": "Bearer token123",
"X-Custom-Header": "custom-value"
}
}
}
Validation rules:
Content-Type is always application/json and cannot be overriddenUse cases:
Example request with webhook headers:
{
"url": "https://youtube.com/watch?v=...",
"async": true,
"webhook": {
"url": "http://webhook:9001/webhook",
"headers": {
"Authorization": "Bearer local-test-token",
"X-Source": "ytdl"
}
}
}
Resulting webhook request headers:
Content-Type: application/json
Authorization: Bearer local-test-token
X-Source: ytdl
Note: Delivery uses hardcoded retry policy (3 attempts, 5s interval, 8s timeout). Failures never abort the main download process.
Success payload:
{
"task_id": "...",
"status": "completed",
"video_id": "...",
"title": "...",
"filename": "...mp4",
"download_endpoint": "/download/.../...mp4",
"storage_rel_path": ".../...mp4",
"duration": 213,
"resolution": "640x360",
"ext": "mp4",
"created_at": "2025-01-16T06:18:46.629918",
"completed_at": "2025-01-16T06:18:56.338989",
"expires_at": "2025-01-17T06:18:46.629918",
"task_download_url_internal": "http://service.local:5000/download/...",
"metadata_url_internal": "http://service.local:5000/download/.../metadata.json",
"client_meta": {"your":"meta"},
"webhook": {
"url": "http://n8n:5678/webhook/...",
"headers": {"X-API-Key": "secret123"},
"status": "delivered",
"attempts": 1,
"last_attempt": "2025-01-16T06:18:56.500000",
"last_status": 200,
"last_error": null,
"next_retry": null
}
}
Error payload:
{
"task_id": "...",
"status": "error",
"operation": "download_video_async",
"error_type": "private_video|unavailable|deleted|...",
"error_message": "...",
"user_action": "...",
"failed_at": "2025-01-16T06:20:00.000000",
"client_meta": {"your":"meta"}
}
Configuration:
webhook.url must start with http(s):// and be < 2048 characters# Download video
curl -X POST http://localhost:5000/download_video \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $API_KEY" \
-d '{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "quality": "best[height<=480]"}'
import requests
# Download video
response = requests.post('http://localhost:5000/download_video', json={
'url': 'https://www.youtube.com/watch?v=dQw4w9WgXcQ',
'client_meta': {'project': 'demo', 'user_id': 123}
})
data = response.json()
print(f"Download URL: {data['task_download_url']}")
const axios = require('axios');
// Download video (async mode)
async function downloadVideo(videoUrl) {
const response = await axios.post('http://localhost:5000/download_video', {
url: videoUrl,
async: true,
client_meta: {project: 'demo'}
});
const taskId = response.data.task_id;
console.log('Task started:', taskId);
// Poll status
while (true) {
const status = await axios.get(`http://localhost:5000/task_status/${taskId}`);
if (status.data.status === 'completed') {
console.log('Download URL:', status.data.task_download_url);
break;
} else if (status.data.status === 'error') {
console.error('Error:', status.data.error_message);
break;
}
await new Promise(r => setTimeout(r, 2000)); // wait 2s
}
}
Recommended Schema:
Step 0: Configure n8n for large files
Add to your n8n docker-compose.yml:
services:
n8n:
environment:
- N8N_DEFAULT_BINARY_DATA_MODE=filesystem
Option A (sync, simpler):
http://youtube_downloader:5000/download_video with body {"url": "..."}task_download_url from response to download file (Response Format: File, Binary Property: data)Option B (async, more reliable):
/download_video with {"url":"...","async":true} - get task_id/task_status/{{task_id}} until status=completed{{ $json.task_download_url }} (Response Format: File, Binary Property: data)Critical:
N8N_DEFAULT_BINARY_DATA_MODE=filesystemProblem: Sign in to confirm you're not a bot or Private video
Solutions:
Problem: n8n tries to load large video into memory
Solutions:
N8N_DEFAULT_BINARY_DATA_MODE=filesystem (recommended)dataProblem: Webhook payload not arriving
Solutions:
docker logs youtube-downloaderProblem: Direct URL expired or blocked
Solutions:
/download_video instead for reliable downloadsProblem: Could not connect to Redis
Note: Public version has embedded Redis - this error should not occur. If you see this error:
docker restart yt-downloaderdocker logs yt-downloaderProblem: 404 File not found
Solutions:
status: "completed"Problem: 401 Unauthorized or Invalid API key
Solutions:
API_KEY is set, all protected endpoints require Authorization: Bearer <key>/download_video/health, /task_status, /downloadAPI_KEY entirelyProblem: client_meta validation failed or client_meta too large
Solutions:
View container logs:
# Real-time logs
docker logs -f youtube-downloader
# Last 100 lines
docker logs --tail 100 youtube-downloader
# With timestamps
docker logs -t youtube-downloader
Log levels:
DEBUG - verbose logging including yt-dlp optionsINFO - standard logging (default)WARNING - warnings onlyERROR - errors onlyCRITICAL - critical errors onlyProgress logging modes:
off (default) - no progress spamcompact - compact progress every N% (configurable via PROGRESS_STEP)full - detailed yt-dlp progress (very verbose)git clone https://github.com/alexbic/youtube-downloader-api.git
cd youtube-downloader-api
docker build -t youtube-downloader:local .
docker run -p 5000:5000 youtube-downloader:local
pip install -r requirements.txt
python app.py
GitHub Actions automatically builds and publishes Docker images on every push to main:
alexbic/youtube-downloader-apighcr.io/alexbic/youtube-downloader-apiBuild status: GitHub Actionsβ
MIT License - see LICENSEβ file
Coming Soon! The Pro version is currently in development and will be available shortly.
The Pro version will include:
Content type
Image
Digest
sha256:30f5075b5β¦
Size
543.5 MB
Last updated
6 months ago
docker pull alexbic/youtube-downloader-api