---
title: 'yt-dlp API: Use yt-dlp from Python, Node.js or an HTTP Service (2026)'
description: Does yt-dlp have an API? Yes, a Python one. How to use YoutubeDL for metadata and downloads, call yt-dlp from Node.js, wrap it in a REST API, and what it takes to run that in production.
url: https://vidkraken.com/blog/yt-dlp-api
author: Vid Kraken
published: '2026-10-28'
---

# yt-dlp API: Use yt-dlp from Python, Node.js or an HTTP Service (2026)

> Does yt-dlp have an API? Yes, a Python one. How to use YoutubeDL for metadata and downloads, call yt-dlp from Node.js, wrap it in a REST API, and what it takes to run that in production.

Vid Kraken · Published 2026-10-28

Source: https://vidkraken.com/blog/yt-dlp-api

yt-dlp is a command-line tool, but you're writing code, and you want to call it like an API: pass a URL, get metadata or a file back.

**Short version:** yt-dlp has a Python API, the `YoutubeDL` class, which is what the command line runs on. From any other language, you run the command and read its JSON output. There is no official HTTP API. If you want "POST a URL, get a file", you either wrap yt-dlp in a service yourself or use a managed API that does the running for you.

| You want to                                    | What exists                                                   |
| ---------------------------------------------- | ------------------------------------------------------------- |
| Call yt-dlp from Python                        | `import yt_dlp` and the `YoutubeDL` class (official)          |
| Call it from Node.js, Go, PHP or anything else | Run the `yt-dlp` command, read JSON from `-J` or `--print`    |
| Call it over HTTP                              | Nothing official. Write a small service, or use a managed API |

## The Python API: YoutubeDL

Install it with the `[default]` extra, which includes the scripts yt-dlp needs to solve YouTube's JavaScript challenges. You also need [Deno](https://deno.com) installed, as you do for the command line.

```bash
python3 -m pip install -U "yt-dlp[default]"
```

### Get metadata without downloading

```python
import yt_dlp

URL = "https://www.youtube.com/watch?v=dQw4w9WgXcQ"

with yt_dlp.YoutubeDL({"quiet": True}) as ydl:
    info = ydl.sanitize_info(ydl.extract_info(URL, download=False))

print(info["title"], info["duration"], len(info["formats"]))
```

`extract_info(..., download=False)` returns the same dictionary `yt-dlp -J` prints: title, duration, uploader, thumbnails, and every available format. `sanitize_info` turns it into plain data you can pass to `json.dumps`.

### Download a video or an MP3

**MP4 up to 1080p**

```python
import yt_dlp

def on_progress(d):
  if d["status"] == "finished":
      print("downloaded", d["filename"])

opts = {
  "format": "bv*[height<=1080]+ba/b[height<=1080]",
  "merge_output_format": "mp4",
  "outtmpl": "%(id)s.%(ext)s",
  "progress_hooks": [on_progress],
  "quiet": True,
  "noprogress": True,
}

with yt_dlp.YoutubeDL(opts) as ydl:
  info = ydl.extract_info("https://www.youtube.com/watch?v=dQw4w9WgXcQ")

print(info["requested_downloads"][0]["filepath"])
```

**MP3**

```python
import yt_dlp

opts = {
  "format": "ba/b",
  "outtmpl": "%(id)s.%(ext)s",
  "postprocessors": [{
      "key": "FFmpegExtractAudio",
      "preferredcodec": "mp3",
      "preferredquality": "320",
  }],
  "quiet": True,
  "noprogress": True,
}

with yt_dlp.YoutubeDL(opts) as ydl:
  info = ydl.extract_info("https://www.youtube.com/watch?v=dQw4w9WgXcQ")

print(info["requested_downloads"][0]["filepath"])
```

`extract_info` downloads by default. The final path, after merging or converting, is in `info["requested_downloads"][0]["filepath"]`. Merging and MP3 conversion need ffmpeg installed.

A few things that save time:

- **Option names aren't the flag names.** `--merge-output-format` becomes `merge_output_format`, but many don't map that neatly. yt-dlp's [`devscripts/cli_to_api.py`](https://github.com/yt-dlp/yt-dlp/blob/master/devscripts/cli_to_api.py) prints the options dictionary for any command line.
- **Errors raise.** In the Python API, a failed download raises `yt_dlp.utils.DownloadError`. The CLI would print the error and carry on. The error is a message string, not a code, so to tell "private video" from "blocked IP" you match on the text. [yt-dlp not working](https://vidkraken.com/blog/yt-dlp-not-working) lists the common messages.
- **Pick a JavaScript runtime in code.** If you use Node instead of Deno, pass `"js_runtimes": {"node": {}}`.
- **Use one `YoutubeDL` per job.** Don't share an instance across threads.

## From Node.js (or any other language)

There's no official yt-dlp package for other languages. Run the command, ask it for JSON, and parse that. Never parse its normal log output, which is meant for people and changes between versions.

```js
import { execFile } from "node:child_process";
import { promisify } from "node:util";

const run = promisify(execFile);
const url = "https://www.youtube.com/watch?v=dQw4w9WgXcQ";

// Metadata: the same JSON as the Python API
const { stdout } = await run("yt-dlp", ["-J", "--no-warnings", "--", url], {
  maxBuffer: 64 * 1024 * 1024,
});
const info = JSON.parse(stdout);
console.log(info.title, info.duration);

// Download, then print only the final file path
const { stdout: path } = await run("yt-dlp", [
  "-f", "bv*[height<=1080]+ba/b[height<=1080]",
  "--merge-output-format", "mp4",
  "-o", "%(id)s.%(ext)s",
  "--print", "after_move:filepath",
  "--", url,
]);
console.log(path.trim());
```

Pass arguments as an array, as `execFile` does here, and never build a shell string from a URL a user gave you. The `--` stops a URL that starts with `-` from being read as an option. `--print after_move:filepath` prints only the final path and still downloads; a plain `--print` would only simulate.

## Wrapping yt-dlp in a REST API

The code for a basic HTTP service is short. Downloads take seconds to minutes, so the service starts a job and returns its ID straight away rather than holding the request open:

```python
import uuid
import yt_dlp
from fastapi import BackgroundTasks, FastAPI

app = FastAPI()
jobs = {}

def run(job_id, url):
    opts = {
        "format": "bv*[height<=1080]+ba/b[height<=1080]",
        "merge_output_format": "mp4",
        "outtmpl": f"/data/{job_id}.%(ext)s",
        "quiet": True,
        "noprogress": True,
    }
    try:
        with yt_dlp.YoutubeDL(opts) as ydl:
            info = ydl.extract_info(url)
        jobs[job_id] = {"status": "COMPLETED", "file": info["requested_downloads"][0]["filepath"]}
    except yt_dlp.utils.DownloadError as e:
        jobs[job_id] = {"status": "FAILED", "error": str(e)}

@app.post("/download")
def download(url: str, tasks: BackgroundTasks):
    job_id = uuid.uuid4().hex
    jobs[job_id] = {"status": "IN_PROGRESS"}
    tasks.add_task(run, job_id, url)
    return {"jobId": job_id}

@app.get("/download/{job_id}")
def status(job_id: str):
    return jobs.get(job_id, {"status": "NOT_FOUND"})
```

That works on your laptop. Here's what it's missing before real users can rely on it:

- **A real job queue.** The `jobs` dictionary disappears on restart and lives in one process. You need a persistent queue, retries, and a limit on how many downloads run at once.
- **Storage and delivery.** Files fill the disk. You need object storage, links your users can download from, and cleanup.
- **An IP YouTube doesn't block.** On cloud servers, YouTube answers with "[Sign in to confirm you're not a bot](https://vidkraken.com/blog/yt-dlp-sign-in-to-confirm-not-a-bot)" and [403 errors](https://vidkraken.com/blog/yt-dlp-403-forbidden). Most teams end up paying for residential proxies.
- **PO tokens and a JavaScript runtime** on every worker, plus a PO Token provider when YouTube asks for one.
- **Updates without outages.** YouTube changes often, and fixes arrive in new yt-dlp releases. Update too slowly and downloads fail; update too eagerly and a bad release can break them.
- **Error codes.** Your users need "this video is private" or "not available in your country", not a yt-dlp traceback. You have to map messages to codes yourself.
- **Input checks.** yt-dlp downloads from thousands of sites, including arbitrary web pages. Accept only the URLs you mean to support.

None of these is hard alone. Together they make a service you have to keep running, and it needs attention every time YouTube changes something.

## Or use a managed API

If downloading YouTube is a feature of your product rather than the product itself, a managed API gives you the HTTP endpoint without the list above. With Vid Kraken, you send a URL and a format and get back a link to the file on our CDN:

**cURL**

```bash
curl -X POST https://vidkraken.com/api/v2/download \
-H "Authorization: Bearer $VIDKRAKEN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"url": "https://youtu.be/dQw4w9WgXcQ", "format": "1080"}'
```

**CLI**

```bash
npx vidkraken download "https://youtu.be/dQw4w9WgXcQ" -f 1080 -o ./ --json
```

Formats are `1080`, `720`, `480`, `360`, `audio-mp3` (320 kbps) and `audio` (the original stream). Bot detection, proxies and YouTube's changes are handled on our side. When a video can't be downloaded, you get a [typed error code](https://vidkraken.com/blog/youtube-download-api-error-codes) such as `AGE_RESTRICTED` or `GEO_RESTRICTED`, and failed downloads aren't charged. You pay for bandwidth only. The [YouTube downloader API guide](https://vidkraken.com/blog/youtube-downloader-api) has the full walkthrough.

What you give up compared with yt-dlp:

- **YouTube only.** yt-dlp supports thousands of sites.
- **Less metadata.** yt-dlp's info dictionary has views, description, chapters and every format. Vid Kraken's [metadata endpoint](https://vidkraken.com/blog/youtube-metadata-api) returns the title, duration, region restrictions and audio languages.
- **A cost per GB** instead of free software plus your own servers and proxies.

## Which should you use?

| Situation                                           | Use                                                                                                         |
| --------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
| A script for yourself, a few videos                 | The yt-dlp command or the Python API. See [how to use yt-dlp](https://vidkraken.com/blog/how-to-use-yt-dlp) |
| A Python project running from a home connection     | `YoutubeDL` directly                                                                                        |
| An internal tool where occasional failures are fine | Your own wrapper service, on a proxy                                                                        |
| A product feature where users paste YouTube links   | A managed API                                                                                               |
| Sites other than YouTube                            | yt-dlp                                                                                                      |

More options in [yt-dlp alternatives](https://vidkraken.com/blog/yt-dlp-alternatives).

> **Skip building the service.**
>
> Send a YouTube URL, get a direct MP4 or MP3 link. No proxies, cookies or yt-dlp
> updates to maintain, and failed downloads are free.
>
> [Start free](https://vidkraken.com/login)

## Frequently asked questions

**Does yt-dlp have an API?**

Yes, a Python one. Import yt\_dlp and use the YoutubeDL class: extract\_info(url, download=False) returns metadata, and extract\_info(url) downloads. There is no official HTTP or REST API, and no official library for other languages.

**Is there a REST API for yt-dlp?**

Not an official one. You can wrap yt-dlp in a small web service (FastAPI or Flask in Python), but in production that service also needs a job queue, storage, proxies for YouTube's bot checks, and regular yt-dlp updates. A managed API like Vid Kraken gives you the REST endpoint without running any of that.

**How do I use yt-dlp from Node.js?**

Run the yt-dlp command with child\_process.execFile, passing arguments as an array. Use -J for metadata as JSON, and --print after\_move:filepath to get the downloaded file's path. Don't parse yt-dlp's normal log output.

**How do I get the downloaded file path from the yt-dlp Python API?**

After info = ydl.extract\_info(url), the final path (after merging or converting) is info\['requested\_downloads']\[0]\['filepath'].

**Why does yt-dlp work on my laptop but fail on my server?**

YouTube treats cloud and datacenter IPs as likely bots, so the same command gets 'Sign in to confirm you're not a bot' or 403 errors on a server. The usual fixes are residential proxies and a PO Token provider, or a managed API that handles it for you.
