# bgutil-ytdlp-pot-provider: a proof-of-origin token source for yt-dlp

> The BgUtils POT provider supplies yt-dlp with proof-of-origin tokens generated through a Botguard interfacing library, in two shapes: an HTTP server on port 4416 with a prebuilt Docker image, or a script the plugin invokes per call. The README is careful about what a token does and does not do, and about how the server should be exposed.

**Brainicism/bgutil-ytdlp-pot-provider** — Proof-of-origin token provider plugin for yt-dlp

- Repository: https://github.com/Brainicism/bgutil-ytdlp-pot-provider
- Stars: 876 · Forks: 109
- Language: TypeScript
- License: GPL-3.0
- Published: 2026-10-20 · Updated: 2026-10-20 · Language: en
- Canonical page: https://hysenlabs.com/projects/brainicism-bgutil-ytdlp-pot-provider

## What a proof-of-origin token is, and what it is not

The README opens with a caution that is worth quoting in full, because it sets the expectation the rest of the project works within: providing a PO token does not guarantee bypassing 403 errors or bot checks, but it may help your traffic seem more legitimate.

That is the honest framing. A proof-of-origin token is a signal that the request came from a session with the genuine client code rather than a script, and YouTube's server checks for it on some requests. Supplying one makes the request look more like the browser traffic it is designed to be, and the README does not claim that this defeats every check.

The tokens are generated with LuanRT's Botguard interfacing library, and the project says plainly what it was built for: it was used to bypass the Sign in to confirm you're not a bot message when invoking yt-dlp from an IP address flagged by YouTube. The background is documented further in yt-dlp's own PO Token Guide, which is the reference to read for how the token fits into the downloader.

The licensing and distribution details are ordinary for a modern project: GPL-3.0, a Docker image on Docker Hub, a release on GitHub, a package on PyPI, and a CI badge in the README header. The releases are 2.0.0 on 2026-09-08, 2.0.1 on 2026-10-02 and 2.0.2 on 2026-10-07, with the last push on the same day. The repository is TypeScript, with the default branch on master, and the top-level layout is small: a plugin/ directory, a server/ directory, an install_plugin_dev.sh script, a .devcontainer/ directory and the usual project files.

One boundary is worth stating here, because the tool does not obscure it. A token changes how your request is classified, not what you are allowed to download. The legality of fetching a given video is a separate question that this project does not answer and cannot.

## Two halves: something that makes tokens, and something that hands them over

The provider comes in two parts, and the split is the same idea that yt-dlp itself uses for plugins.

The first part is the provider, and it has two options of its own. Option (a) is an HTTP server that generates the token and has interfaces for the plugin to retrieve data from, described as much faster and easy to set up, with a Docker image provided with Deno and Node.js support. Option (b) is a POT generation script with command line options for the plugin to invoke, which needs the script transpiled.

The second part is the provider plugin, which retrieves tokens from the provider and provides the token for yt-dlp using the PO Token Provider Framework.

So there are three deployable things: the token generator, in either of two shapes, and the Python plugin that yt-dlp loads. The plugin is what yt-dlp knows about, and it is deliberately thin, because its only job is to know where the provider is and how to ask it. Everything that requires a JavaScript runtime lives on the provider side, which is why the requirement list mentions Node.js or Deno at all, and why the Docker image exists.

The base requirements are explicit: yt-dlp 2025.05.22 or above, and either the Docker runtime for the image or Node.js 22 and above, or Deno 2.0.0 and above, along with git to clone the repository. The README says you only need to choose one of the two provider options, which is the right way to frame it, because they are alternatives rather than layers.

## Option (a): the HTTP server and its Docker image

The server is a JavaScript HTTP server on port 4416 by default, and when run natively it binds to localhost only, on 127.0.0.1 and::1.

Installing it from source is a clone, a transpile and, for the Deno flavour, an install step:

```shell
# Replace 2.0.2 with the latest version or the one that matches the plugin
git clone --single-branch --branch 2.0.2 https://github.com/Brainicism/bgutil-ytdlp-pot-provider.git
cd bgutil-ytdlp-pot-provider/server/
# If you are using Node:
npm ci
npx tsc
# Otherwise, if you want to use Deno:
deno install --allow-scripts=npm:canvas --frozen
```

The Docker image is the quick route, and the port mapping in it is deliberate:

```shell
docker run --name bgutil-provider -d --init \
  -p 127.0.0.1:4416:4416 brainicism/bgutil-ytdlp-pot-provider
```

The image binds the server to all interfaces inside the container so that port forwarding works, which is why the host side of the mapping has to be restricted to the loopback interface explicitly.

The image comes in two flavours, Node.js and Deno. The:latest tag defaults to Node.js, and you can ask for a specific version and flavour with a tag like brainicism/bgutil-ytdlp-pot-provider:2.0.2-deno. The:node tag points to the latest Node.js image and:deno to the latest Deno image.

Running it natively means invoking the built entry point with the runtime of your choice, for Node:

```shell
node build/main.js [OPTIONS]
```

and for Deno, from the node_modules directory:

```shell
cd node_modules
deno run --allow-env --allow-net --allow-ffi=. --allow-read=. ../src/main.ts [OPTIONS]
```

The Deno invocation spells out its permissions. The server's own options are -p or --port for the listening port, and -H or --host for the address, which can be repeated or given as a comma-separated list to bind several addresses, and which defaults to localhost only.

## The two warnings that decide how you deploy it

The README devotes two callouts to operational detail, and they are the part of the documentation most worth internalising.

The first is about the port mapping. Omitting 127.0.0.1 from the port mapping publishes the server on all host interfaces by default, and the warning says this may allow untrusted local or external clients to access the unauthenticated server, generate tokens, consume system and network resources and potentially perform remote code execution. The server has no authentication, and the token generation path runs the Botguard library, so the exposure is not merely a free token service. Publishing to loopback is the default assumption the Docker run line encodes, and dropping the address prefix turns it into an open service.

The second warning is about networking. The Docker container's network is isolated from your local network by default, so if you are using a local proxy server it will not be reachable from within the container unless you pass --net=host as well. That is the usual container isolation behaviour, and the note matters because a user behind a proxy will otherwise see the provider fail in a way that looks like a token problem.

Together they describe a service designed to be local. The bind default is localhost only, the container publishes to the loopback interface, and the two escape hatches, exposing the port and joining the host network, are exactly the two changes that turn a local helper into something exposed. Neither is forbidden, and both are legitimate in a single-user machine, but each one is a decision rather than a default.

## Option (b): the script, and why the README does not recommend it

The script option avoids a running service entirely. The plugin spawns the script when it needs a token, and for this option you just make sure either node or deno is available in your PATH. Otherwise you use the yt-dlp option --js-runtimes RUNTIME:PATH to pass the path.

There is a subtlety there worth noticing, because it is the kind of thing that costs an afternoon. The --no-js-runtimes option does not prevent the plugin from using the JavaScript runtime. The argument is only used to retrieve the path to the runtime. So a user who disables yt-dlp's JavaScript support for their own reasons will still find the plugin starting a Node.js process, and the only way to point it at a runtime is the path option.

The README marks this method as not recommended for high concurrency usage, and gives two reasons. Every yt-dlp call incurs the overhead of spawning a new Node.js process to run the script, and the method handles cache concurrency poorly.

Both are process-level facts rather than tuning problems. Starting a Node.js process carries real overhead before any work happens, and the token cache is shared state that several simultaneous yt-dlp invocations will contend over. On a single download, once, the script is fine. On a server that fetches in parallel, the HTTP server exists precisely because the script cannot be made to work in that situation.

## Installing the plugin and telling yt-dlp where the provider is

The plugin installs the way yt-dlp plugins install. If yt-dlp came from pip or pipx:

```shell
python3 -m pip install -U bgutil-ytdlp-pot-provider
```

Otherwise you download bgutil-ytdlp-pot-provider.zip from the latest release and place the zip into one of the yt-dlp plugin folders, which the README links.

With option (a) and the default address, http://127.0.0.1:4416, yt-dlp is used like normal. If the provider is reachable somewhere else, you pass the address with the base_url extractor argument:

```shell
--extractor-args "youtubepot-bgutilhttp:base_url=http://127.0.0.1:8080"
```

The naming is worth reading closely, because it encodes the two provider options in the argument name itself: youtubepot-bgutilhttp is the HTTP server variant, and the script variant would use a different name. The README also notes that when you pass multiple extractor arguments to one provider or extractor, they are to be separated by semicolons as shown above.

So the whole contract is: the plugin registers itself with yt-dlp, the plugin knows how to reach the provider, and the provider generates tokens. There is no shared state to configure beyond the base URL, and no token format to understand, because the plugin handles the exchange.

## Versions, packaging and the boundary of the project

The version floor is the thing to check first. yt-dlp 2025.05.22 or above is required, because the provider framework and the plugin loading mechanism it depends on arrived in that release, and a plugin that silently fails to load on an older yt-dlp is a confusing failure. The JavaScript side wants Node.js 22 or above, or Deno 2.0.0 or above, and the Docker image exists so that neither has to be installed on the host at all.

The packaging is consistent across four channels: GitHub releases, a PyPI package, a Docker image on Docker Hub, and a source clone. The README's clone command pins a branch, with a comment to replace 2.0.2 with the latest version or the one that matches the plugin, which is a sensible convention, because the plugin and the provider are released together and a mismatch is possible.

The repository layout matches the two-part design. The server/ directory holds the HTTP server and the generation script, the plugin/ directory holds the Python plugin, install_plugin_dev.sh is there for working on the plugin locally, and the .devcontainer/ directory provides a development environment for the server. There is a CODEOWNERS file, a CONTRIBUTING.md and a LICENSE.

What the project does not do is decide anything for you. It does not guarantee that a token prevents a 403, it does not give you the right to download a particular video, and it does not hide the server from your network if you choose to expose it. Those three positions are all stated plainly in the README, which is a good sign for a tool that sits in the path between a downloader and a video host.

## Conclusion

Adopt this provider if you run yt-dlp against YouTube from an address that trips the bot check and you have the right to download what you are fetching, because it fills in the token that yt-dlp's own provider framework expects and it does so without patching yt-dlp. Do not treat it as a licence to download anything, since a token only makes the request look like a browser session and the responsibility for the content stays with you, and do not run the server on a shared interface. Before you deploy it, read the port binding note, because the server is unauthenticated and the README warns that publishing it on all host interfaces lets untrusted clients generate tokens, consume your resources and potentially achieve remote code execution, and decide between the HTTP server and the script on the basis of concurrency, because the script spawns a Node.js process for every yt-dlp call.

## FAQ

### What does the BgUtils POT provider do for yt-dlp?

It generates proof-of-origin tokens using LuanRT's Botguard interfacing library and supplies them to yt-dlp through the PO Token Provider Framework. The README says a token does not guarantee bypassing 403 errors or bot checks, but it may help your traffic seem more legitimate.

### How do I run the provider as an HTTP server?

Use the Docker image with docker run --name bgutil-provider -d --init -p 127.0.0.1:4416:4416 brainicism/bgutil-ytdlp-pot-provider, or run the built server natively with node build/main.js. The server listens on port 4416 by default and binds to localhost only unless you pass --host.

### Which provider option should I use?

The HTTP server, if you make more than an occasional call, because the README says the script method is not recommended for high concurrency usage: every yt-dlp call spawns a new Node.js process and cache concurrency is handled poorly. The server comes with a prebuilt Docker image and is described as simpler and faster.

### How do I install the plugin and point yt-dlp at the provider?

Install it with python3 -m pip install -U bgutil-ytdlp-pot-provider, or place the zip from the latest release into a yt-dlp plugin folder. With the default address http://127.0.0.1:4416 no extra arguments are needed, otherwise pass --extractor-args "youtubepot-bgutilhttp:base_url=http://127.0.0.1:8080".

## Sources

- [Brainicism/bgutil-ytdlp-pot-provider on GitHub](https://github.com/Brainicism/bgutil-ytdlp-pot-provider)
- [Issues](https://github.com/Brainicism/bgutil-ytdlp-pot-provider/issues)
- [License: GPL-3.0](https://github.com/Brainicism/bgutil-ytdlp-pot-provider/blob/master/LICENSE)
- [README](https://github.com/Brainicism/bgutil-ytdlp-pot-provider/blob/master/README.md)
- [Releases](https://github.com/Brainicism/bgutil-ytdlp-pot-provider/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/brainicism-bgutil-ytdlp-pot-provider
