How to Use ReClip API Endpoints: A Complete Guide to Video Download Automation

ReClip exposes five JSON-based HTTP endpoints that let you fetch video metadata, download videos or audio, and track asynchronous jobs using yt-dlp.

ReClip is a lightweight Flask microservice built by averygan/reclip that wraps yt-dlp into a programmable API. Whether you're building a video pipeline, a browser extension, or a media archiving tool, these endpoints let you automate video downloads without managing yt-dlp subprocesses directly. This guide covers every endpoint, shows working code examples in Python, and references the actual source at app.py.


Getting Started with the ReClip API

The service runs on a host and port defined by HOST and PORT environment variables, defaulting to 127.0.0.1:8899. See the startup block at the end of app.py (lines 107-110) to configure your deployment.

All endpoints return JSON except for /api/file/<job_id>, which streams binary data. Error responses use standard HTTP status codes: 400 for malformed requests, 404 for missing jobs, and 500 for processing failures.


How to Fetch Video Metadata with /api/info

The /api/info endpoint retrieves comprehensive metadata for a single video URL. Internally, it runs yt-dlp -j <url> and parses the output using parse_ytdlp_json (lines 16-28 in app.py).

The response includes title, thumbnail, duration, uploader, and available formats. The handler also computes the best format per resolution using best_by_height logic (lines 14-20) to simplify client-side selection.

import requests

BASE = "http://127.0.0.1:8899"

def get_info(video_url):
    resp = requests.post(f"{BASE}/api/info", json={"url": video_url})
    resp.raise_for_status()
    return resp.json()

# Example usage

info = get_info("https://www.youtube.com/watch?v=abc123")
print(info["title"], info["duration"])
print([f["id"] for f in info["formats"]])  # available format IDs

The format_id values returned here feed directly into the download endpoint below.


How to Extract Playlist URLs with /api/playlist

The /api/playlist endpoint flattens a playlist into individual video URLs. It executes yt-dlp --flat-playlist -J <url> (lines 50-58) and returns a simple list:

def get_playlist(playlist_url):
    resp = requests.post(f"{BASE}/api/playlist", json={"url": playlist_url})
    resp.raise_for_status()
    return resp.json()["urls"]  # list of video URLs

urls = get_playlist("https://www.youtube.com/playlist?list=...")
for url in urls:
    # Process each video individually

    info = get_info(url)

This avoids parsing complex playlist structures yourself—ReClip handles yt-dlp's JSON output and extracts only the url field from each entry.


How to Start Asynchronous Downloads with /api/download

The /api/download endpoint initiates a background download job and returns immediately. This is the core of the ReClip API workflow.

Key parameters:

  • url (required): The video URL
  • format: Either "video" or "audio" — audio forces MP3 output
  • format_id: A specific yt-dlp format ID from /api/info
  • title: Suggested filename (sanitized by safe_title logic at lines 79-81)

The handler generates a 10-character hex job ID using uuid.uuid4().hex[:10] (line 77), stores request data in the global jobs dictionary (lines 78-80), and spawns a daemon thread running run_download (lines 32-90):

def start_download(video_url, format_id=None, fmt="video", title=""):
    payload = {
        "url": video_url,
        "format": fmt,
        "format_id": format_id,
        "title": title
    }
    resp = requests.post(f"{BASE}/api/download", json=payload)
    resp.raise_for_status()
    return resp.json()["job_id"]  # e.g., "a3f7b2c9d1"

# Start with best format from /api/info

info = get_info(video_url)
best_format = info["formats"][0]["id"]
job_id = start_download(
    video_url,
    format_id=best_format,
    fmt="video",
    title=info["title"]
)

If no format_id is provided, run_download defaults to bestvideo+bestaudio/best for maximum quality.


How to Track Download Progress with /api/status/<job_id>

The /api/status/<job_id> endpoint polls job state. It looks up the job in the jobs dictionary and returns status, error (if any), and filename when complete (lines 89-96):

def check_status(job_id):
    resp = requests.get(f"{BASE}/api/status/{job_id}")
    resp.raise_for_status()
    return resp.json()

# Poll until completion

import time
while True:
    status = check_status(job_id)
    if status["status"] == "done":
        print(f"Ready: {status['filename']}")
        break
    elif status["status"] == "error":
        raise RuntimeError(status["error"])
    time.sleep(1)

Job states are: "starting", "downloading", "done", or "error".


How to Retrieve Finished Files with /api/file/<job_id>

The /api/file/<job_id> endpoint streams the completed file. It validates that the job exists and status == "done", then uses Flask's send_file with the sanitized download name (lines 100-105):

def download_file(job_id, dest_path):
    resp = requests.get(f"{BASE}/api/file/{job_id}", stream=True)
    resp.raise_for_status()
    with open(dest_path, "wb") as f:
        for chunk in resp.iter_content(chunk_size=8192):
            f.write(chunk)

# Complete workflow

status = check_status(job_id)
if status["status"] == "done":
    download_file(job_id, f"./{status['filename']}")

The Content-Disposition header ensures the browser receives a clean filename without illegal characters.


Complete ReClip API Integration Example

Here's a full workflow combining all five ReClip API endpoints:

import requests
import time

BASE = "http://127.0.0.1:8899"

def full_download(video_url, output_dir="./"):
    # 1. Get metadata

    info = requests.post(f"{BASE}/api/info", json={"url": video_url}).json()
    
    # 2. Select best format

    best = info["formats"][0]  # highest resolution per /api/info logic

    
    # 3. Start download

    job = requests.post(f"{BASE}/api/download", json={
        "url": video_url,
        "format": "video",
        "format_id": best["id"],
        "title": info["title"]
    }).json()["job_id"]
    
    # 4. Poll status

    while True:
        status = requests.get(f"{BASE}/api/status/{job}").json()
        if status["status"] == "done":
            break
        elif status["status"] == "error":
            raise RuntimeError(status["error"])
        time.sleep(0.5)
    
    # 5. Download file

    r = requests.get(f"{BASE}/api/file/{job}", stream=True)
    path = f"{output_dir}/{status['filename']}"
    with open(path, "wb") as f:
        for chunk in r.iter_content(8192):
            f.write(chunk)
    return path

Architecture and Implementation Details

Understanding how ReClip handles requests helps you build robust integrations.

In-Memory Job State — Jobs live in a global jobs dictionary. The service is stateless across requests; only this shared dict maintains download progress. Restarting the Flask process clears all job history.

Background Threading — The run_download function executes in a daemon threading.Thread, allowing /api/download to return instantly with a job ID. This prevents HTTP timeouts on slow downloads.

Filename Sanitization — The safe_title transform (lines 79-81) strips characters that would break filesystems or HTTP headers.

Format Selection Logic — When clients request "audio", ReClip forces MP3 conversion via yt-dlp's format arguments. For video, explicit format_id values override the default bestvideo+bestaudio/best selector.


Key Files in the ReClip Repository

File Purpose
app.py Core Flask application containing all five API route handlers and download logic
templates/index.html Interactive web UI that consumes the same API endpoints
requirements.txt Python dependencies (flask, yt-dlp, etc.)
Dockerfile / docker-compose.yml Container deployment configuration

Summary

  • /api/info — POST a URL, get metadata and format options
  • /api/playlist — POST a playlist URL, get flat video URL list
  • /api/download — POST download parameters, receive job ID for async processing
  • /api/status/<job_id> — GET current job state, error messages, and final filename
  • /api/file/<job_id> — GET the finished file as a streamed attachment

The ReClip API abstracts yt-dlp's complexity into five predictable HTTP endpoints. All state management happens through the jobs dictionary in app.py, with background threads handling long-running downloads without blocking clients.


Frequently Asked Questions

Does ReClip require authentication?

No. The ReClip API is unauthenticated by default. Deploy behind a reverse proxy or VPN if you need access control. The Flask server binds to 127.0.0.1 by default (line 110), which limits exposure to localhost only.

What happens if I restart the ReClip server during a download?

Active downloads terminate because job state lives only in the in-memory jobs dictionary. The partially downloaded file may remain in the temporary directory. Re-query /api/status for any job ID will return 404 after restart.

Can I download audio-only files with ReClip?

Yes. Pass "format": "audio" to /api/download. This triggers yt-dlp with audio-specific parameters that force MP3 output. You may still specify a format_id from /api/info if you need a particular audio quality.

How do I deploy ReClip in production?

Use the provided Dockerfile and docker-compose.yml for containerized deployment. Set the HOST and PORT environment variables to control binding. The default 127.0.0.1:8899 is suitable for local development; production deployments typically use 0.0.0.0 behind a reverse proxy.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →