invidious Documentation

Invidious is an alternative front-end to YouTube

22,594 stars Crystal 1 file ยท ~14,020 tokens #agplv3#hacktoberfest#invidious#libre#video#watch#youtube#youtube-video
RAW Doc

Docker & Self-Hosted Deployment Verified OCI

Deploy invidious on any server, homelab, or cloud container platform.

1-Click Terminal Command
bash (docker run)
docker run -d --name invidious -p 3000:3000 --restart always docker.io/library/postgres:14
Configured Services & Ports
โš™๏ธ invidious
image: build locally
๐Ÿ”Œ Ports: 127.0.0.1:3000:3000
โš™๏ธ invidious-db
image: docker.io/library/postgres:14
docker-compose.yml
yaml (docker-compose.yml)
# Warning: This docker-compose file is made for development purposes.
# Using it will build an image from the locally cloned repository.
#
# If you want to use Invidious in production, see the docker-compose.yml file provided
# in the installation documentation: https://docs.invidious.io/installation/

version: "3"
services:

  invidious:
    build:
      context: .
      dockerfile: docker/Dockerfile
    restart: unless-stopped
    ports:
      - "127.0.0.1:3000:3000"
    depends_on:
      invidious-db:
        condition: service_healthy
        restart: true
    environment:
      # Please read the following file for a comprehensive list of all available
      # configuration options and their associated syntax:
      # https://github.com/iv-org/invidious/blob/master/config/config.example.yml
      INVIDIOUS_CONFIG: |
        db:
          dbname: invidious
          user: kemal
          password: kemal
          host: invidious-db
          port: 5432
        check_tables: true
        # external_port:
        # domain:
        # https_only: false
        # statistics_enabled: false
        hmac_key: "CHANGE_ME!!"
    healthcheck:
      test: wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1
      interval: 30s
      timeout: 5s
      retries: 2

  invidious-db:
    image: docker.io/library/postgres:14
    restart: unless-stopped
    volumes:
      - postgresdata:/var/lib/postgresql/data
      - ./config/sql:/config/sql
      - ./docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh
    environment:
      POSTGRES_DB: invidious
      POSTGRES_USER: kemal
      POSTGRES_PASSWORD: kemal
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]

volumes:
  postgresdata:

1. Project Overview & Quickstart (iv-org/invidious)

File: README.md

Screenshots

Player Preferences Subscriptions

Features

User features

  • Lightweight
  • No ads
  • No tracking
  • No JavaScript required
  • Light/Dark themes
  • Customizable homepage
  • Subscriptions independent from Google
  • Notifications for all subscribed channels
  • Audio-only mode (with background play on mobile)
  • Support for Reddit comments
  • Available in many languages, thanks to our translators

Data import/export

  • Import subscriptions from YouTube, NewPipe and FreeTube
  • Import watch history from YouTube and NewPipe
  • Export subscriptions to NewPipe and FreeTube
  • Import/Export Invidious user data

Technical features

  • Embedded video support
  • Developer API
  • Does not use official YouTube APIs
  • No Contributor License Agreement (CLA)

Quick start

Using Invidious:

Hosting Invidious:

Documentation

The full documentation can be accessed online at https://docs.invidious.io/

The documentation's source code is available in this repository:
https://github.com/iv-org/documentation

Extensions

We highly recommend the use of Privacy Redirect,
a browser extension that automatically redirects YouTube URLs to any Invidious instance and replaces
embedded YouTube videos on other websites with Invidious.

The documentation contains a list of browser extensions that we recommended to use along with Invidious.

You can read more here: https://docs.invidious.io/applications/

Contribute

Code

  1. Fork it ( https://github.com/iv-org/invidious/fork ).
  2. Create your feature branch (git checkout -b my-new-feature).
  3. Stage your files (git add .).
  4. Commit your changes (git commit -m 'Add some feature').
  5. Push to the branch (git push origin my-new-feature).
  6. Create a new pull request ( https://github.com/iv-org/invidious/compare ).

Translations

We use Weblate to manage Invidious translations.

You can suggest new translations and/or correction here: https://hosted.weblate.org/engage/invidious/.

Creating an account is not required, but recommended, especially if you want to contribute regularly.
Weblate also allows you to log-in with major SSO providers like GitHub, GitLab, BitBucket, Google, ...

Projects using Invidious

A list of projects and extensions for or utilizing Invidious can be found in the documentation: https://docs.invidious.io/applications/

Liability

We take no responsibility for the use of our tool, or external instances
provided by third parties. We strongly recommend you abide by the valid
official regulations in your country. Furthermore, we refuse liability
for any inappropriate use of Invidious, such as illegal downloading.
This tool is provided to you in the spirit of free, open software.

You may view the LICENSE in which this software is provided to you here.

  1. Limitation of Liability.

IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.


File: kubernetes/README.md

The Helm chart has moved to a dedicated GitHub repository: https://github.com/iv-org/invidious-helm-chart/tree/master/invidious

2. Official Technical Reference & Guides (iv-org/documentation)

File: README.md

The Invidious documentation

Running the documentation locally for development purposes

Run those commands in the repository's folder.

Local `mkdocs-material` installation

bash
# You might want to create a virtualenv first
pip install mkdocs-material
mkdocs-material serve

With docker

bash
docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material:latest

File: docs/api/authenticated-endpoints.md

API - Authenticated endpoints

All endpoints under namespace /api/v1/auth require authentication.

Authentication can be in one of two forms:

  • A Cookie: <SID> header (for logged in users)
  • An Authorization: Bearer <TOKEN> (recommended)

A new token can be generated from /authorize_token with the given parameters:

text
scopes: Comma-separated list of scopes
callback_url: URL to redirect to with generated token
expire: Int64 how long a given token should be valid (in seconds)

Each scope has the following format:

text
METHOD1;METHOD2...:ENDPOINT(*)?

Where METHOD can be one of GET, POST, PUT, DELETE, PATCH.

An ENDPOINT can be any of the documented endpoints below.

Examples:

  • POST:playlists*: authorizes POST methods to any endpoint under /api/v1/auth/playlists (/api/v1/auth/playlists, /api/v1/playlists/:id/videos, etc.)

  • :playlists/*: authorizes any method to endpoints under /api/v1/auth/playlists/ (/api/v1/auth/playlists/:id, /api/v1/playlists/:id/videos, etc.)

  • GET:playlists/IVPAAAAAAA: authorizes GET only to playlist IVPAAAAAAA.

  • :preferences: authorizes any method to /api/v1/auth/preferences

  • GET;POST:preferences: authorizes GET or POST to /api/v1/auth/preferences

When a callback_url is specified, after a user has authorized a token with the desired scopes, a GET request will be made to the callback_url with the token URL-escaped and appended as token=TOKEN.

GET `/api/v1/auth/feed`

Get subscription feed for the authenticated user.

Parameters:

text
max_results: Int32
page: Int32

Schema:

javascript
{
    "notifications": [
        {
            "type": "shortVideo",
            "title": String,
            "videoId": String,
            "videoThumbnails": [
                {
                    "quality": String,
                    "url": String,
                    "width": Int64,
                    "height": Int64
                }
            ],
            "lengthSeconds": Int64,
            "author": String,
            "authorId": String,
            "authorUrl": String,
            "published": Int64,
            "publishedText": String,
            "viewCount": Int64
        }
    ],
    "videos": [
        {
            "type": "shortVideo",
            "title": String,
            "videoId": String,
            "videoThumbnails": [
                {
                    "quality": String,
                    "url": String,
                    "width": Int64,
                    "height": Int64
                }
            ],
            "lengthSeconds": Int64,
            "author": String,
            "authorId": String,
            "authorUrl": String,
            "published": Int64,
            "publishedText": String,
            "viewCount": Int64
        }
    ]
}

GET `/api/v1/auth/notifications`

Parameters:

text
topics: Array(String) (comma separated: e.g. "UCID1,UCID2) limit of 1000 topics
since: Int64, timestamp

Provides an EventSource for receiving changes from each topic in topics. Currently the only supported topic-type is ucid, which will return an updated video object whenever the given channel uploads a video.

Important to note is that an event will also be sent when a channel changes an already uploaded video, for example changing description or title.

Each event is a JSON object with the same schema as /api/v1/videos.

A debug topic can also provided which will return a (psuedo-)randomly selected video every minute.

since will return all videos uploaded since TIMESTAMP, with a limit of the 15 most recent videos from each topic.

More details in #469.

POST `/api/v1/auth/notifications`

Same as above GET endpoint, however topics is moved into post body as Content-Type: application/x-www-form-urlencoded.

GET `/api/v1/auth/playlists`

Get list of playlists for the given user.

Schema:

javascript
[
    {
        "type": "invidiousPlaylist",
        "title": String,
        "playlistId": String,
        "author": String,
        "authorId": null,
        "authorUrl": null,
        "authorThumbnails": [],
        "description": String,
        "descriptionHtml": String,
        "videoCount": Int32,
        "viewCount": 0,
        "updated": Int64,
        "isListed": Boolean,
        "videos": [
            {
                "title": String,
                "videoId": String,
                "author": String,
                "authorId": String,
                "authorUrl": String,
                "videoThumbnails": [
                    {
                        "quality": String,
                        "url": String,
                        "width": Int32,
                        "height": Int32
                    }
                ],
                "index": Int32,
                "indexId": String,
                "lengthSeconds": Int32
            }
        ]
    }
]

POST `/api/v1/auth/playlists`

Content-Type: application/json

Create new playlist.

Example request body:

javascript
{
    "title": String,
    "privacy": "private"
}

privacy can be any of: public, unlisted, private

If successful, returns 201, a link to the created resource as a Location header, and the following response:

javascript
{
    "title": String,
    "playlistId": String
}

GET `/api/v1/auth/playlists/:id`

Returns same result as unauthenticated /api/v1/playlists/:id.

Important to note is that if the requested playlist is marked as private, it will return an error if the request is not authenticated as the playlist's author.

PATCH `/api/v1/auth/playlists/:id`

Content-Type: application/json

Modify a playlist's description, title, description, or privacy.

Example request body:

javascript
{
    "title": String,
    "description": String,
    "privacy": "private"
}

privacy can be any of: public, unlisted, private

Will return 204 on success.

DELETE `/api/v1/auth/playlists/:id`

Delete a given playlist :id.

Will return 204 on success.

POST `/api/v1/auth/playlists/:id/videos`

Content-Type: application/json

Add a video to the given playlist :id.

Example request body:

javascript
{
    "videoId": String
}

Returns a 201 on success with the following schema:

javascript
{
    "title": String,
    "videoId": String,
    "author": String,
    "authorId": String,
    "authorUrl": String,
    "videoThumbnails": [
          {
            "quality": String,
            "url": String,
            "width": Int32,
            "height": Int32
          }
    ]
}

DELETE `/api/v1/auth/playlists/:id/videos/:index`

Delete a video from the given playlist :id with indexId :index.

Will return 204 on success.

GET `/api/v1/auth/preferences`

Get preferences for authenticated user.

Schema:

javascript
{
    "annotations": false,
    "annotations_subscribed": false,
    "autoplay": false,
    "captions": [
        "",
        "",
        ""
    ],
    "comments": [
        "youtube",
        ""
    ],
    "continue": false,
    "continue_autoplay": true,
    "dark_mode": "light",
    "latest_only": false,
    "listen": false,
    "local": false,
    "locale": "en-US",
    "max_results": 40,
    "notifications_only": false,
    "player_style": "invidious",
    "quality": "hd720",
    "default_home": "Popular",
    "feed_menu": [
        "Trending",
        "Playlists"
    ],
    "related_videos": true,
    "sort": "published",
    "speed": 1.0,
    "thin_mode": false,
    "unseen_only": false,
    "video_loop": false,
    "volume": 100
}

POST `/api/v1/auth/preferences`

Content-Type: application/json

Patch user preferences.

Example body:

javascript
{
    "speed": 2.0,
    "volume": 10
}

GET `/api/v1/auth/subscriptions`

Get user's subscriptions.

Schema:

javascript
[
    {
        "author": String,
        "authorId": String
    }
]

POST `/api/v1/auth/subscriptions/:ucid`

Content-Type: application/json

Add a given ucid to a user's subscriptions.

Will return 204 on success.

DELETE `/api/v1/auth/subscriptions/:ucid`

Removes a given ucid from a user's subscriptions.

Will return 204 on success.

GET `/api/v1/auth/tokens`

Get a list of tokens for the authenticated user.

Schema:

javascript
[
    {
        "session": String,
        "issued": Int64
    }
]

POST `/api/v1/auth/tokens/register`

Content-Type: application/json

Create a new token for the authenticated user.

Example request body:

javascript
{
    "scopes": Array(String), // Each scope has same format as each scope in `/authorize_token`
    "callbackUrl": String?,
    "expire": Int64
}

Returns a 200 on success with the newly created token as the response body.

Example response:

javascript
{
    "session":"v1:YUwKEL1XwHQzp7-AAAAAAAAAAAAAAAAAA=",
    "scopes":["GET:notifications"],
    "signature":"jNYdAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAXGb__2Gv-w="
}

POST `/api/v1/auth/tokens/unregister`

Content-Type: application/json

Revoke a token for the authenticated user.

Example request:

javascript
{
    "session": "v1:YUwKEL1XwHQzp7-AAAAAAAAAAAAAAAAAA="
}

Returns 204 on success.

GET `/api/v1/auth/history`

Get the history of videos played by the user.

Parameters:

text
max_results: Int32
page: Int32

Schema:

javascript
[
   // video IDs
]

POST `/api/v1/auth/history/:id`

Set a video as watched.

Returns 204 on success.

DELETE `/api/v1/auth/history/:id`

Delete a video from the user watched history.

Returns 204 on success.


File: docs/api/channels_endpoint.md

API - Channels endpoint

Please refer to the Common object types page for more
details on the various JSON objects used below.

GET `/api/v1/channels/:id`

Response:

javascript
{
	"author": String,
	"authorId": String,
	"authorUrl": String,
	"authorVerified": Boolean,
	"authorBanners": [
		// One or more ImageObject
	],
	"authorThumbnails": [
		// One or more ImageObject
	],

	"subCount": Number, // Integer
	"totalViews": Number, // Integer
	"joined": Number, // Unix timestamp

	"autoGenerated": Boolean,
	"isFamilyFriendly": Boolean,

	"description": String,
	"descriptionHtml": String,
	"allowedRegions": String[],

	"tabs": String[],
	"pronouns": String?

	"latestVideos": [
		// One or more VideoObject
	],
	"relatedChannels": [
		// One or more ChannelObject
	]
}

GET `/api/v1/channels/:id/channels`

URL parameters:

  • continuation: A continuation token to get the next chunk of items. The token is provided each time this API is requested.

Response:

javascript
{
	"relatedChannels": [
		// One or more ChannelObject
	],
	"continuation": String
}

GET `/api/v1/channels/:id/latest`

This is the same as requesting /api/v1/channels/:id/videos without URL parameters.

GET `/api/v1/channels/:id/playlists`

URL parameters:

  • continuation: A continuation token to get the next chunk of items. The token is provided each time this API is requested.
  • sort_by: Sort order filter. Accepted values: oldest, newest, last. Defaults to last.

Response:

javascript
{
	"playlists": [
		// One or more PlaylistOject
	],
	"continuation": continuation
}

GET `/api/v1/channels/:id/podcasts`

URL parameters:

  • continuation: A continuation token to get the next chunk of items. The token is provided each time this API is requested.

Response:

javascript
{
	"playlists": [
		// One or more PlaylistOject
	],
	"continuation": continuation
}

GET `/api/v1/channels/:id/releases`

URL parameters:

  • continuation: A continuation token to get the next chunk of items. The token is provided each time this API is requested.

Response:

javascript
{
	"playlists": [
		// One or more PlaylistOject
	],
	"continuation": continuation
}

GET `/api/v1/channels/:id/shorts`

URL parameters:

  • continuation: A continuation token to get the next chunk of items. The token is provided each time this API is requested.
  • sort_by: Sort order filter. Accepted values: newest, popular or oldest

Response:

See: GET /api/v1/channels/:id/videos

GET `/api/v1/channels/:id/streams`

URL parameters:

  • continuation: A continuation token to get the next chunk of items. The token is provided each time this API is requested.
  • sort_by: Sort order filter. Accepted values: newest, popular or oldest

Response:

See: GET /api/v1/channels/:id/videos

GET `/api/v1/channels/:id/videos`

URL parameters:

  • continuation: A continuation token to get the next chunk of items. The token is provided each time this API is requested.
  • sort_by: Sort order filter. Accepted values: newest, popular or oldest (Broken as of 10/2022). Default to newest.

Response:

javascript
{
	"videos": [
		// One or more VideoObject
	],
	"continuation": String
}

GET `/api/v1/channels/:id/community`

Please refer to the Community Post Attachment types below for more details on the attachment JSON objects used below.

Url parameters

  • continuation: A continuation token to get the next chunk of items.

Response:

javascript
{
	"authorId": String,
	"comments": {
		"attachment": ImageAttachment | MultiImageAttachment | VideoAttachment | PollAttachment | PlaylistAttachment
		"author": String,
		"authorIsChannelOwner": Boolean
		"authorId": String,
		"authorThumbnails": ImageObject[],
		"authorUrl": String,
		"commentId": String,
		"content": String,
		"contentHtml": String,
		"isEdited": Boolean,
		"likeCount": Number,
		"published": Number,
		"publishedText": String,
		"replyCount": Number
	}[]
}

Community Post Attachment Types

VideoAttachment
See VideoObject common type

PlaylistAttachment
See PlaylistObject common type

ImageAttachment

javascript
{
    "type": "image",
    "imageThumbnails": ImageObject[]
}

MultiImageAttachment

javascript
{
    "type": "multiImage",
    "images": ImageObject[][]
}

PollAttachment

javascript
{
	"type": "poll",
	"totalVotes": Number,
	"choices": {
		"text": String,
		"image?": ImageObject[]
	}[]

}

Unknown

This usually means that parsing support for the attachment type has not yet been implemented.

javascript
{
    "type": "unknown",
    "error": String
}

Url parameters

  • q: The query to search
  • page: The page number of the search (Int32)

Response:

javascript
[
	VideoObject,
	PlaylistObject
];

GET `/api/v1/post/:id`

Url parameters

  • ucid: It's recommended to provide the channel's id for fetching the post.

Response:
Same as /api/v1/channels/:id/community but only returns one post in the comments array

GET `/api/v1/post/:id/comments`

Url parameters

  • ucid: You need to provide the channel's id for fetching the post's comments.
  • sort_by: "top", "new" (default: top)

Response:
Same as /api/v1/channels/:id/comments but has a postId instead of a videoId


File: docs/api/common_types.md

API - Common object types

ImageObject

javascript
{
	"url": String,
	"width": Number, // Integer
	"height": Number // Integer
}

ThumbnailObject

javascript
{
	"quality": String,
	"url": String,
	"width": Number, // Integer
	"height": Number // Integer
}

VideoObject

javascript
{
	"type": "video", // Constant

	"title": String,
	"videoId": String,

	"author": String,
	"authorId": String,
	"authorUrl": String,
	"authorVerified": Boolean,

	"videoThumbnails": [
		// One or more ThumbnailObject
	],

	"description": String,
	"descriptionHtml": String,

	"viewCount": Number, // Integer
	"viewCountText": String,
	"lengthSeconds": Number, // Integer

	"published": Number, // Unix timestamp
	"publishedText": String,

	// Only available on premiered videos
	"premiereTimestamp": Number, // Unix timestamp

	"liveNow": Boolean,
	"premium": Boolean,
	"isUpcoming": Boolean,

	"isNew" Boolean,
	"is4k": Boolean,
	"is8k": Boolean,
	"isVr180": Boolean,
	"isVr360": Boolean,
	"is3d": Boolean,
	"hasCaptions": Boolean
}

ChannelObject

javascript
{
	"type": "channel", // Constant

	"author": String,
	"authorId": String,
	"authorUrl": String,
	"authorVerified": Boolean,
	"authorThumbnails": [
		// One or more ThumbnailObject
	],

	"autoGenerated": Boolean,
	"subCount": Number, // Integer
	"videoCount": Number, // Integer

	"description": String,
	"descriptionHtml": String,
}

PlaylistObject

javascript
{
	"type": "playlist", // Constant

	"title": String,
	"playlistId": String,
	"playlistThumbnail": String,

	"author": String,
	"authorId": String,
	"authorUrl": String,
	"authorVerified": Boolean,

	"videoCount": Number, // Integer
	"videos": [
		{
			"title": String,
			"videoId": String,
			"lengthSeconds": Number, // Integer
			"videoThumbnails": [
				// One or more ThumbnailObject
			]
		}
	]
}

File: docs/apache2.md

Apache2 reverse proxy setup

  • A very basic config, secured with Let's Encrypt. Any log is disabled by default. Do not forget to replace ServerName with your domain.
text
<VirtualHost *:443>
        ServerName invidious.domain.tld
        ServerAdmin admin@localhost

        ProxyPreserveHost On
        ProxyRequests off
        ProxyPass / http://127.0.0.1:3000/ nocanon
        ProxyPassReverse / http://127.0.0.1:3000/

        AllowEncodedSlashes on

#        ErrorLog /var/log/apache2/invidious.domain.tld/error.log
        CustomLog /dev/null combined

        RewriteEngine on
        SSLCertificateFile /etc/letsencrypt/live/invidious.domain.tld/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/invidious.domain.tld/privkey.pem
        SSLCertificateChainFile /etc/letsencrypt/live/invidious.domain.tld/chain.pem

</VirtualHost>
  • Another config example without HTTPS, but with Apache Basic Auth HTTP login.
    The user will connect to Apache on port 3333 and will be asked to log in. If authentification is successful, Apache will redirect the user to Invidious' page.
    To make the VirtualHost config below actually work, you should as well:
    • Create a .htpasswd file and add required username/login combos to it, if not already existing.
    • Open port 3333 (or any other free port) adding Listen 3333 to Apache ports.conf (Debian /etc/apache2/ports.conf)
    • If you run Invidious with default parameters, you may need to replace default host binding (0.0.0.0) with localhost (127.0.0.1) instead. That way, Invidious won't be publicly available on port 3000 anymore, but only accessible via the reverse proxy on port 3333. So if you run Invidious via a systemd service, you would edit the service file (e.g. /etc/systemd/system/invidious.service) and modify the ExecStart line to include the -b switch as follows ExecStart=/home/invidious/invidious/invidious -b 127.0.0.1 -o invidious.log and then reload the daemon with systemctl daemon-reload so that changes are taken into account.
    • A convenient way to open such protected Invidious page without having to log in manually everytime is to access use a URL with the following format: http://username:password@domain:3333
text
<VirtualHost *:3333>

    ServerName invidious.domain.tld #add your own domain name (or localhost if you have none)
    ServerAdmin admin@localhost

    <Location />
        Deny from all # Forbid access to all by default...
        #Allow from 127.0.0.1 #...Except from specific IPs (which will not need to authenticate)...
        AuthUserFile /etc/apache2/.htpasswd #path to .htpasswd file
        AuthName "Restricted Area" # name displayed in the promptbox
        AuthType Basic # http://httpd.apache.org/docs/current/howto/auth.html
        Satisfy Any
        Require valid-user # ...and except from authenticated users included in the .htpasswd file
    </Location>

    ProxyPass / http://127.0.0.1:3000/ nocanon
    ProxyPassReverse / http://127.0.0.1:3000/
    ProxyPreserveHost On
    ProxyRequests Off
    AllowEncodedSlashes On

    #ErrorLog ${APACHE_LOG_DIR}/error.log
    CustomLog /dev/null combined

</VirtualHost>

File: docs/api.md

API

Language

All endpoints that return a JSON body support &hl=LANGUAGE for translating fields into the desired language. A list of languages are provided in List of URL parameters.

GET `/api/v1/stats`

Schema:

javascript
{
  "version": String,
  "software": {
    "name": "invidious",
    "version": String,
    "branch": String
  },
  "openRegistrations": Bool,
  "usage": {
    "users": {
      "total": Int32,
      "activeHalfyear": Int32,
      "activeMonth": Int32
    }
  },
  "metadata": {
    "updatedAt": Int64,
    "lastChannelRefreshedAt": Int64
  },
  "playback": {
    "totalRequests": Int32?
    "successfulRequests": Int32?
    "ratio": Float32?
  }
}

GET `/api/v1/videos/:id`

Schema:

text
/* Detailed source-code truncated for AI context efficiency. */

Parameters:

text
region: ISO 3166 country code (default: "US")

GET `/api/v1/annotations/:id`

Parameters:

text
source: "archive", "youtube" (default: "archive")

Returns annotation XML from YouTube's /annotations_invideo endpoint. Alternatively it provides access to legacy annotation data using this collection on archive.org.

GET `/api/v1/comments/:id`

Schema:

javascript
{
  "commentCount": Int32?,
  "videoId": String,
  "comments": [
    {
      "author": String,
      "authorThumbnail": String,
      "authorId": String,
      "authorUrl": String,

      "isEdited": Boolean,
      "isPinned": Boolean,
      "isSponsor": Boolean?,
      "sponsorIconUrl": String?,

      "content": String,
      "contentHtml": String,
      "published": Int64,
      "publishedText": String,
      "likeCount": Int32,
      "commentId": String,
      "authorIsChannelOwner": Bool,
      "creatorHeart": {
        "creatorThumbnail": String,
        "creatorName": String
      }?,
      "replies": {
        "replyCount": Int32,
        "continuation": String
      }?
    }
  ],
  "continuation": String?
}

Parameters:

text
sort_by: "top", "new" (default: top)
source: "youtube", "reddit" (default: youtube)
continuation: String

GET `/api/v1/captions/:id`

Schema:

javascript
{
  "captions": [
    {
      "label": String,
      "languageCode": String,
      "url": String
    }
  ]
}

Parameters:

text
label: String
lang:  String
tlang: String
region: ISO 3166 country code (default: "US")

A request with label will return the selected captions in WebVTT format.
Captions can also be selected with an ISO lang, e.g. &lang=en, tlang will auto-translate from English into the requested language (if English captions are available).

Schema:

javascript
[
  {
    "title": String,
    "videoId": String,
    "videoThumbnails": [
      {
        "quality": String,
        "url": String,
        "width": Int32,
        "height": Int32
      }
    ],

    "lengthSeconds": Int32,
    "viewCount": Int64,

    "author": String,
    "authorId": String,
    "authorUrl": String,

    "published": Int64,
    "publishedText": String,
    "description": String,
    "descriptionHtml": String,

    "liveNow": Bool,
    "paid": Bool,
    "premium": Bool
  }
]

Parameters:

text
type: "music", "gaming", "movies", "default"
region: ISO 3166 country code (default: "US")

Schema:

javascript
[
    {
        "type": "shortVideo",
        "title": String,
        "videoId": String,
        "videoThumbnails": [
            {
            "quality": String,
            "url": String,
            "width": Int32,
            "height": Int32
            }
        ],

        "lengthSeconds": Int32,
        "viewCount": Int64,

        "author": String,
        "authorId": String,
        "authorUrl": String,

        "published": Int64,
        "publishedText": String
    }
]

GET `/api/v1/search/suggestions`

Schema:

javascript
{
    "query": String,
    "suggestions": Array(String)
}

Parameters:

text
q: String

Schema:

javascript
[
  {
    type: "video",
    title: String,
    videoId: String,
    author: String,
    authorId: String,
    authorUrl: String,
    videoThumbnails: [
      {
        quality: String,
        url: String,
        width: Int32,
        height: Int32
      }
    ],
    descriptionHtml: String,
    viewCount: Int64,
    published: Int64,
    publishedText: String,
    lengthSeconds: Int32,
    liveNow: Bool,
    paid: Bool,
    premium: Bool
  },
  {
    type: "playlist",
    title: String,
    playlistId: String,
    playlistThumbnail: String,
    author: String,
    authorId: String,
    authorUrl: String,
    authorVerified: Boolean,

    videoCount: Int32,
    videos: [
      {
        title: String,
        videoId: String,
        lengthSeconds: Int32,
        videoThumbnails: [
          {
            quality: String,
            url: String,
            width: Int32,
            height: Int32
          }
        ]
      }
    ]
  },
  {
    type: "channel",
    author: String,
    authorId: String,
    authorUrl: String,

    authorThumbnails: [
      {
        url: String,
        width: Int32,
        height: Int32
      }
    ],
    autoGenerated: Bool
    subCount: Int32,
    videoCount: Int32,
    descriptionHtml: String
  },
  {
    type: "hashtag"
    title: String,
    url: String,
    channelCount: Int32,
    videoCount: Int32
  }
];

Parameters:

text
q: String
page: Int32
sort: "relevance", "views"
date: "hour", "today", "week", "month", "year"
duration: "short", "long", "medium"
type: "video", "playlist", "channel", "movie", "show", "all", (default: all)
features: "hd", "subtitles", "creative_commons", "3d", "live", "purchased", "4k", "360", "location", "hdr", "vr180" (comma separated: e.g. "&features=hd,subtitles,3d,live")
region: ISO 3166 country code (default: "US")

GET `/api/v1/playlists/:plid`

Schema:

javascript
{
    "title": String,
    "playlistId": String,

    "author": String,
    "authorId": String,
    "authorThumbnails": [
        {
            "url": String,
            "width": String,
            "height": String
        }
    ],
    "description": String,
    "descriptionHtml": String,

    "videoCount": Int32,
    "viewCount": Int64,
    "viewCountText": String,
    "updated": Int64,

    "videos": [
        {
          "title": String,
          "videoId": String,
          "author": String,
          "authorId": String,
          "authorUrl": String,

          "videoThumbnails": [
              {
                  "quality": String,
                  "url": String,
                  "width": Int32,
                  "height": Int32
              }
          ],
          "index": Int32,
          "lengthSeconds": Int32
        }
    ]
}

Parameters:

text
page: Int32

GET `/api/v1/mixes/:rdid`

Schema:

javascript
{
  title: String,
  mixId: String,
  videos: [
    {
      title: String,
      videoId: String,
      author: String,
      authorId: String,
      authorUrl: String,
      videoThumbnails: [
        {
          quality: String,
          url: String,
          width: Int32,
          height: Int32
        }
      ],
      index: Int32,
      lengthSeconds: Int32
    }
  ]
}

GET `/api/v1/hashtag/:tag`

Schema:

javascript
{
  results: VideoObject[],
}

Parameters:

text
page: Int32

GET `/api/v1/resolveurl`

Schema:

javascript
{
  browseId?: String,
  ucid?: String,
  videoId?: String,
  playlistId?: String,
  startTimeSeconds?: String,
  postId?: String,
  params?: String,
  pageType: string
}

Parameters:

text
url: URL

GET `/api/v1/clips`

Schema:

javascript
{
  startTime: Float64, // in seconds
  endTime: Float64, // in seconds
  clipTitle: String,
  video: VideoObject
}

Parameters:

text
id: string

File: docs/applications.md

Applications

Lists of third-party projects that use or support Invidious.

Applications

Name Description Links
FreeTube A desktop libre software YouTube app for privacy.
Yattee Alternative YouTube frontend for iPhone, iPad, Mac and Apple TV. App Store
CloudTube A JavaScript-rich alternate online YouTube player.
HoloPlay Progressive Web App for music connecting on Invidious API's with search, playlists and favorites.
WatchTube Powerful YouTube client for Apple Watch. App Store
TubiTui A lightweight, libre, TUI-based YouTube client. For desktop.
Ytfzf A posix script to find and watch youtube videos from the terminal. (Without API).
Playlet A Roku TV YouTube app that uses Invidious as a backend. Roku Store
GTK+ Pipe Viewer YouTube client for Linux.
PlasmaTube YouTube client for Linux. KDE Applications
yt2alt A cli tool to import your profile from Youtube to an alternative platform.
Materialious Modern material design for Invidious.
IInvidious An Invidious client for the Apple II. Repository

Browser Extensions

Name Description Links
View on Invidious View page on Invidious (bookmarklet).
Redirector Extension for Firefox and Chromium that can manually be configured to process Invidious Redirects. For details, see the setup page to use it with Invidious. Source / Firefox / Chrome
Invidious Copy URL Adds context menu options on Invidious to copy shortened YouTube URL at current time or not (Requires using developer mode in Chrome or a developer version of Firefox).
Privacy Redirect Redirects YouTube to Invidious, Twitter to Nitter, and Instagram to Bibliogram. Source / Firefox / Chrome
SponsorBlock A crowd-sourced extension to skip sponsorships. Support Invidious instances if enabled in the options. Source / Firefox / Chrome
Inviduration Firefox extension that shows total duration of playlists on Invidious. Source / Firefox
Alter Firefox extension that redirects YouTube, Twitter and Reddit to Invidious, Nitter and Teddit respectively. Source / Firefox

Userscripts for Invidious.

Name Description
Invidious Redirect Redirects YouTube URLs to a deprecated instance, but can be edited to redirect to a different instance.
YouTube to Invidious Scans current page for YouTube embeds and replace with invidio.us. (Can be edited to use any preferred instance)
No-Youtube Userscript to replace YouTube links to invidio.us. Can be edited to redirect to a preferred instance.
FYTE Replace all YouTube embeds on a page with the video's thumbnail. Click on the thumbnail to play the video. Invidious is supported in the Options panel (redirects to invidio.us by default, can be edited).
Privacy Redirector Userscript to automatically redirect to different private frontends, including Invidious. Can be configured to redirect to specific instances of the user's choice.
Simple Sponsor Skipper Userscript based on SponsorBlock that is pre-configured to work with multiple Invidious instances, but can be easily altered to use any instance desired by the user.
Invidious Preferences Userscript Userscript for enforcing preferences across multiple instances, through URL Parameters, and without using browser cookies. Works in tandem with Privacy Redirector and Simple Sponsor Skipper. See here for more information about URL Parameters.

Extensions that Integrate Invidious into other Apps

Name Description Links
iPhone Redirector Shortcut Automatically open YouTube videos in Invidious (iPhone shortcut).
FreshRSS Extension A FreshRSS extension to directly embed videos from Invidious channel feeds.
UntrackMe Android app to rewrite YouTube links to Invidious. Can optionally play videos in the app as well.
Kodi add-on Watch YouTube videos in the Kodi media center, using the Invidious API. Privacy-friendly alternative to the YouTube API add-on.
Koutube Watch YouTube videos on Discord without loading the YouTube embed. Quickly replace any YouTube links using the sed syntax (s/y/k) or by swapping the y with a k. Source / Website

Utilities

Name Description
Invidious-Updater (And Installer) Automatic install and update script for Invidious.

File: docs/caddy.md

Caddy reverse proxy setup

This is a very basic config, assuming that you're using Caddy to manage SSL certificates for you.
Any log is disabled by default. Do not forget to replace server_name with your domain.

text
https://<server_name> {
  reverse_proxy localhost:3000

  log {
      output discard
  }
}

File: docs/captcha-bug.md

CAPTCHA bug on Debian and Ubuntu

Warning: If you use ImageMagick on Ubuntu for other services like Mastodon this guide may break them, please use [this guide](https://linuxconfig.org/how-to-install-imagemagick-7-on-ubuntu-18-04-linux) instead.

There is some issue on Debian 9 and Ubuntu 18.04 and later. It appears that the clock (the CAPTCHA) has no hands but you can see them outside the clock. You need to compile ImageMagick yourself with librsvg to solve this issue.
Thanks Tmiland for showing us a solution at #299

For lazy people, a little hack is to disable CAPTCHA or use text one.

You can check if your version of ImageMagick is affected with convert -list format.
It should show the following if your installed version is okay.

text
SVG  SVG       rw+   Scalable Vector Graphics (RSVG 2.40.13)
     SVGZ  SVG       rw+   Compressed Scalable Vector Graphics (RSVG 2.40.13)

If this is not the case, your version is not compiled with librsvg, then you get the following.

text
SVG  SVG       rw+   Scalable Vector Graphics (XML 2.9.4)
     SVGZ  SVG       rw+   Compressed Scalable Vector Graphics (XML 2.9.4)

Follow the steps to fix this issue:

bash
$ sudo apt purge imagemagick
$ cd /tmp
# check for new releases: https://github.com/ImageMagick/ImageMagick6/releases
$ wget https://github.com/ImageMagick/ImageMagick6/archive/6.9.11-19.tar.gz
$ tar -xvf 6.9.10-24.tar.gz
$ cd ImageMagick6-6.9.10-24
$ ./configure --with-rsvg
$ make
$ sudo make install

Set the correct path: $ sudo ln -s /usr/local/bin/convert /usr/bin/convert
If you get an error here that this file already exists, please execute $ sudo apt autoremove

Now convert -list format reports

text
SVG  rw+   Scalable Vector Graphics (RSVG 2.40.16)
 SVGZ  rw+   Compressed Scalable Vector Graphics (RSVG 2.40.16)

Restart Invidious, just to be sure:

bash
$ sudo systemctl restart invidious.service

File: docs/community-installation-guide.md

Community Installation Guide

??? warning "Before following the community installation guide"
All the platforms listed in the community installation guide are not supported officially by the Invidious developers.
This means:

text
- The Invidious developers can't help you to solve issues with your platform. Ask the community on [Matrix](https://matrix.to/#/#invidious:matrix.org) or [IRC](https://web.libera.chat/?channel=#invidious) before creating GitHub issues. But if you do fix an issue please create a PR for updating the community installation guide.
- The guide for your platform may be outdated because things have changed since the creation of the guide.

If your platform is not listed but you would like to contribute to this guide for adding it, please do here. We rely on the community to help us.

After installation take a look at the Post-install steps.

Podman (rootless container)

Guide contributor(s): @sigulete

Podman is usually pre-installed in Fedora, CentOS, RHEL and derivatives. But if this is not the case, the instruction below will install all necessary packages.

RHEL based and RHEL-like systems

bash
sudo dnf install podman

Download the configuration files from Invidious' repository

Note: Currently the repository has to be cloned, this is because the init-invidious-db.sh file and the config/sql directory have to be mounted to the postgres container (See the volumes section in the postgres' container). This "problem" will be solved in the future.

`` Absolute path in your home directory where Invidious will be downloaded (e.i. /home/johnsmith/.inv)

bash
cd 
git clone https://github.com/iv-org/invidious.git

Create Pod - videos

bash
podman pod create --name videos -p 3000:3000

Create Container - postgres

bash
podman create --rm \
--pod videos \
--name postgres \
--label "io.containers.autoupdate=registry" \
--health-cmd='pg_isready -U $POSTGRES_USER -d $POSTGRES_DB' \
-v postgresdata:/var/lib/postgresql/data \
-v /invidious/config/sql:/config/sql:z \
-v /invidious/docker/init-invidious-db.sh:/docker-entrypoint-initdb.d/init-invidious-db.sh:z \
-e POSTGRES_DB=invidious \
-e POSTGRES_USER=kemal \
-e POSTGRES_PASSWORD=kemal \
docker.io/library/postgres:14

Create Container - invidious

Copy /invidious/config/config.example.yml to /config.yml and update parameters as required.

bash
podman create --rm \
--pod videos \
--name invidious \
--label "io.containers.autoupdate=registry" \
--health-cmd="wget -nv --tries=1 --spider http://127.0.0.1:3000/api/v1/stats || exit 1" \
--health-interval=30s \
--health-timeout=5s \
--health-retries=2 \
-v /config.yml:/invidious/config/config.yml:z,U \
quay.io/invidious/invidious:latest

Create systemd services to manage the Pod

Podman can generate systemd services to handle the life cycle of pods and containers.
The instructions below will create 3 service units, and they will be placed in the correct location ready to be used.

bash
cd ~
cp $(podman generate systemd --new --files --name videos) .config/systemd/user

Start Pod

Despite the existance of 3 services, only the one related to the Pod must be used. The life cycle for the 2 containers implementing postgres and invidious will be handled by the pod.

bash
systemctl --user daemon-reload
systemctl --user enable --now pod-videos.service

And similarly, the instruction below will re-start the service:

bash
systemctl --user restart pod-videos.service

If this service runs on a server, it will stop as soon as you logout, because it is running in user space.
To ensure it is persistent and remains active after logging out, you will need to enable user lingering.

bash
loginctl enable-linger

Updating to the latest release

bash
podman auto-update
podman image prune -f

Podman via systemd

Guide contributor(s): @redbeardymcgee

This method employs rootless containers through podman whose lifecycles are managed by systemd and is suitable for systems which come with Podman version 5.x or higher. Ensure that SELinux is in enforcing mode for maximum security. Do not run any of the following commands or scripts as root.

Define containers

Add the quadlet definitions for Invidious, the database, and the companion containers to $HOME/.config/containers/systemd/invidious.

ini
# $HOME/.config/containers/systemd/invidious/invidious.container
[Unit]
Description=Invidious
Requires=invidious-db.service
After=invidious-db.service
Requires=invidious-companion.service
After=invidious-companion.service

[Service]
Restart=on-failure
TimeoutStartSec=900

[Install]
WantedBy=multi-user.target

[Container]
Image=quay.io/invidious/invidious:latest
ContainerName=invidious
AutoUpdate=registry

Network=invidious.network
HostName=invidious

Volume=./config.yml:/invidious/config/config.yml:Z
ini
# $HOME/.config/containers/systemd/invidious/invidious-db
[Unit]
Description=Invidious postgres

[Service]
Restart=on-failure
TimeoutStartSec=900

[Install]
WantedBy=multi-user.target

[Container]
Image=docker.io/library/postgres:14
ContainerName=invidious-db
AutoUpdate=registry

Network=invidious.network
HostName=invidious-db

Volume=invidious-db:/var/lib/postgresql/data:Z

Environment=POSTGRES_DB=invidious
Environment=POSTGRES_USER=kemal
Environment=POSTGRES_PASSWORD=kemal

# NOTE: Alternatively, set password as a podman secret
# `printf 'my-postgres-password' | podman secret create --replace invidious-db-pw -`
# Secret=invidious-db-pw,type=env,target=POSTGRES_PASSWORD
ini
# $HOME/.config/containers/systemd/invidious/invidious-companion
[Unit]
Description=Invidious companion

[Service]
Restart=on-failure
TimeoutStartSec=900

[Install]
WantedBy=multi-user.target

[Container]
Image=quay.io/invidious/invidious-companion:latest
ContainerName=invidious-companion
AutoUpdate=registry

Network=invidious.network
HostName=invidious-companion

Volume=invidious-companion-cache:/var/tmp/youtubei.js:rw,Z

# WARNING: The container will fail to start without this env var
# NOTE: The podman secret is preferred, but you may set the env var directly like this
# Environment=SERVER_SECRET_KEY=my-secret-key

# If you set the env var above, comment this out
# `pwgen 16 1 | podman secret create --replace invidious-db-pw -`
Secret=invidious-companion-secret-key,type=env,target=SERVER_SECRET_KEY

Define the storage volumes

The database requires a data volume to persist the database. The companion uses a cache volume.

ini
# $HOME/.config/containers/systemd/invidious/invidious-db.volume
[Volume]
VolumeName=invidious-db
ini
# $HOME/.config/containers/systemd/invidious/invidious-companion.volume
[Volume]
VolumeName=invidious-companion-cache

Modify `config.yml` for your evironment

Copy the example config from HERE.

curl -o "$HOME"/.config/containers/systemd/invidious/config.yml https://raw.githubusercontent.com/iv-org/invidious/refs/heads/master/config/config.example.yml

Edit the configuration according to your environment. The example is very well commented. Notable fields include invidious_companion and invidious_companion_key to ensure that the companion container is connectable. If you changed the $POSTGRES_PASSWORD, then it should be configured to match in the db field. The field hmac_key is mandatory.

!!! warning

text
The Invidious container may fail to start or operate as expected if the `config.yml` is not correctly configured.

Confirm the container services are generated

Systemd units are generated on-the-fly during daemon-reload command, but before that let's check syntax with quadlet generator. Note, you need Podman version 5.0 or higher, older versions will not work:

QUADLET_UNIT_DIRS="$HOME/.config/containers/systemd/invidious" /lib/systemd/user-generators/podman-user-generator -user -dryrun

Reload systemd daemon. Keep in mind you need to do this command every time you change a unit file.

systemctl --user daemon-reload

Prepare the database

The database container requires an initial migration. This should be handled by the field check_tables in config.yml if set to true. The following steps will manually initialize the database in case there is an issue.

bash
# Start the database container
systemctl --user start invidious-db

# Enter the container, install curl, initialize the database, uninstall curl
podman exec invidious-db \
  sh -c '
        apt-get update
        apt-get install --assume-yes --no-install-recommends curl

        for initdb in channels videos channel_videos users session_ids nonces annotations playlists playlist_videos
        do
          curl -s https://raw.githubusercontent.com/iv-org/invidious/refs/heads/master/config/sql/$initdb.sql | psql postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@invidious-db/$POSTGRES_DB
        done
        apt-get --assume-yes purge curl
        '

Create a timer to restart Invidious regularly

Invidious recommends restarting frequently in the post-install configuration documentation. A systemd timer is an effective method of achieving this. Add the file to $HOME/.config/systemd/user/invidious.timer and activate it with systemctl --user enable --now invidious.timer.

ini
# $HOME/.config/systemd/user/invidious.timer
[Unit]
Description=Restart Invidious every hour

[Install]
WantedBy=timers.target

[Timer]
OnUnitActiveSec=60minutes

Start the application

And the whole application can be now started:

systemctl --user start invidious

Keep in mind that generated units cannot be enabled using systemctl --user enable. The Invidious containers will be started automatically.

Proxmox VMs and LXCs

Guide contributor(s): @SimplyProgrammer

This method provides a simple and straightforward guide with scripts to setup Invidious inside a Proxmox Linux VM or LXC container.
It utilizes podman to create Invidious containers, Caddy 2 for self-signed https reverse proxy and Linux service for automatic startup.

Prerequirements

Debian 12+ VM or an unprivileged Debian 12+ LXC container with:

  • Name "invidious" (or something similar)
  • 4GB+ of RAM
  • 2+ x86_64 cores, 4 is ideal (ARM is also possible but compose.yml has to be modified accordingly).
  • 20+GB of storage
  • Static IP or static DHCP lease on your router.

Networking

Further, you will need a DNS domain, paid or local one to point to this IP of your LXC/VM with Invidious.
It would be great if your router would allow/do this by default, and automatically assign something like invidious.lan or even better, invidious.home.arpa to your LXC/VM. For example, OpenWrt does this by default. For other routers, extra config might be needed.
If you have your own domain, reversed-proxy etc., setup might differ for you...

Installation

Run this script (with sudo privileges or as root):

text
mkdir -p ~/invidious-podman && cd "$_" && git clone "https://github.com/SimplyProgrammer/Invidious-podman-LXC-VM.git" . && chmod 755 setup-invidious.sh && ./setup-invidious.sh

When it asks for the Invidious git repo, for 90% of you just press enter (unless you have your own forked one).

The most important part, when it asks for the DNS domain, specify aforementioned invidious.lan, invidious.home.arpa or whatever you have. But make sure the domain is reachable and working in advance (nslookup etc.).

If everything was done properly, now you should have a working LXC/VM with Invidious service and should be able to open it at https://<your-invidious-domain>/.
It should automatically start with your VM/LXC, no extra work needed.

More info and the whole solution is available for review at https://github.com/SimplyProgrammer/Invidious-podman-LXC-VM.

MacOS

Looking for contributors to this operating system. Please submit a new doc for MacOS at https://github.com/iv-org/documentation/blob/master/docs/community-installation-guide.md

BSD (FreeBSD - OpenBSD)

People have successfully ran Invidious on FreeBSD/OpenBSD as discussed in https://github.com/iv-org/invidious/issues/2388

We are looking for contributors to write a proper guide for installing Invidious on FreeBSD/OpenBSD. If you can, please submit a new doc for FreeBSD/OpenBSD at https://github.com/iv-org/documentation/blob/master/docs/community-installation-guide.md