## 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](locales/), thanks to [our translators](#contribute)

**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](https://docs.invidious.io/api/)
- Does not use official YouTube APIs
- No Contributor License Agreement (CLA)


## Quick start

**Using Invidious:**

- [Select a public instance from the list](https://instances.invidious.io) and start watching videos right now!

**Hosting Invidious:**

- [Follow the installation instructions](https://docs.invidious.io/installation/)


## 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](https://github.com/SimonBrazell/privacy-redirect#get),
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 ).
1.  Create your feature branch (`git checkout -b my-new-feature`).
1.  Stage your files (`git add .`).
1.  Commit your changes (`git commit -m 'Add some feature'`).
1.  Push to the branch (`git push origin my-new-feature`).
1.  Create a new pull request ( https://github.com/iv-org/invidious/compare ).

### Translations

We use [Weblate](https://weblate.org) 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](./LICENSE).

>   16. 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:

```
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:

```
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:

```
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:

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

Provides an [EventSource](https://developer.mozilla.org/en-US/docs/Web/API/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](https://github.com/iv-org/invidious/issues/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:

```
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](./common_types.md) 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](#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_types.md#videoobject) common type

__PlaylistAttachment__
See [PlaylistObject](./common_types.md#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
}
```

##### GET `/api/v1/channels/:ucid/search`

> 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`](#get-apiv1channelsidcommunity) 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`](../api.md#get-apiv1commentsid) 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.

```

<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](http://httpd.apache.org/docs/current/programs/htpasswd.html) file and add required [username/login combos](http://aspirine.org/htpasswd_en.html) 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

```
<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](./url-parameters.md).

##### 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:

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

Parameters:

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

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

Parameters:

```
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](https://archive.org/details/youtubeannotations) 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:

```
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:

```
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).

##### GET `/api/v1/trending`

> 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:

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

##### GET `/api/v1/popular`

> 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:

```
q: String
```

##### GET `/api/v1/search`

> 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:

```
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:

```
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:
```
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:
```
url: URL
```

##### GET `/api/v1/clips`
> Schema:
```javascript
{
  startTime: Float64, // in seconds
  endTime: Float64, // in seconds
  clipTitle: String,
  video: VideoObject
}
```
Parameters:
```
id: string
```

---

## File: docs/applications.md

# Applications

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

### Applications
| Name | Description | Links |
| --- | --- | --- |
| [FreeTube](https://github.com/FreeTubeApp/FreeTube) | A desktop libre software YouTube app for privacy. | |
| [Yattee](https://github.com/yattee/yattee) | Alternative YouTube frontend for iPhone, iPad, Mac and Apple TV. | [App Store](https://apps.apple.com/app/yattee/id1595136629) |
| [CloudTube](https://sr.ht/~cadence/tube/) | A JavaScript-rich alternate online YouTube player. | |
| [HoloPlay](https://github.com/stephane-r/holoplay-pwa)| Progressive Web App for music connecting on Invidious API's with search, playlists and favorites. | |
| [WatchTube](https://github.com/WatchTubeTeam/) | Powerful YouTube client for Apple Watch. | [App Store](https://apps.apple.com/app/watchtube/id1599884909) |
| [TubiTui](https://codeberg.org/777/TubiTui) | A lightweight, libre, TUI-based YouTube client. For desktop. | |
| [Ytfzf](https://github.com/pystardust/ytfzf) | A posix script to find and watch youtube videos from the terminal. (Without API). | |
| [Playlet](https://github.com/iBicha/playlet) | A Roku TV YouTube app that uses Invidious as a backend. | [Roku Store](https://channelstore.roku.com/details/840aec36f51bfe6d96cf6db9055a372a/playlet) |
| [GTK+ Pipe Viewer](https://github.com/trizen/pipe-viewer) | YouTube client for Linux. | |
| [PlasmaTube](https://invent.kde.org/multimedia/plasmatube/) | YouTube client for Linux. | [KDE Applications](https://apps.kde.org/plasmatube/) |
| [yt2alt](https://github.com/iBicha/yt2alt) | A cli tool to import your profile from Youtube to an alternative platform. | |
| [Materialious](https://github.com/WardPearce/Materialious) | Modern material design for Invidious.  | |
| [IInvidious](https://www.colino.net/wordpress/en/iinvidious-an-apple-ii-invidious-client/) | An Invidious client for the Apple II.  | [Repository](https://github.com/colinleroy/a2tools) |

### Browser Extensions

| Name | Description | Links |
|---|---|---|
| [View on Invidious](https://omar.yt/722e5c15832840fe1ae8830b7c590254b9e0a45c/invidious-bookmarklet.html) | View page on Invidious (bookmarklet). | |
| [Redirector](https://github.com/einaregilsson/Redirector) | Extension for Firefox and Chromium that can manually be configured to process Invidious Redirects. For details, see the [setup page](./redirector.md) to use it with Invidious. | [Source](https://github.com/einaregilsson/Redirector) / [Firefox](https://addons.mozilla.org/addon/redirector) / [Chrome](https://chrome.google.com/webstore/detail/redirector/ocgpenflpmgnfapjedencafcfakcekcd) |
| [Invidious Copy URL](https://github.com/recette-lemon/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](https://github.com/SimonBrazell/privacy-redirect) | Redirects YouTube to Invidious, Twitter to Nitter, and Instagram to Bibliogram. | [Source](https://github.com/SimonBrazell/privacy-redirect) / [Firefox](https://addons.mozilla.org/addon/privacy-redirect) / [Chrome](https://chrome.google.com/webstore/detail/privacy-redirect/pmcmeagblkinmogikoikkdjiligflglb) |
| [SponsorBlock](https://github.com/ajayyy/SponsorBlock) | A crowd-sourced extension to skip sponsorships. Support Invidious instances if enabled in the options. | [Source](https://github.com/ajayyy/SponsorBlock) / [Firefox](https://addons.mozilla.org/addon/sponsorblock) / [Chrome](https://chrome.google.com/webstore/detail/mnjggcdmjocbbbhaepdhchncahnbgone) |
| [Inviduration](https://github.com/rsapkf/inviduration) | Firefox extension that shows total duration of playlists on Invidious. | [Source](https://github.com/rsapkf/inviduration) / [Firefox](https://addons.mozilla.org/addon/inviduration) |
| [Alter](https://github.com/w3bdev1/alter) | Firefox extension that redirects YouTube, Twitter and Reddit to Invidious, Nitter and Teddit respectively. | [Source](https://github.com/w3bdev1/alter) / [Firefox](https://addons.mozilla.org/addon/alter) |

### Userscripts for Invidious.

| Name | Description |
|---|---|
| [Invidious Redirect](https://greasyfork.org/en/scripts/370461-invidious-redirect) | Redirects YouTube URLs to a deprecated instance, but can be edited to redirect to a different instance. |
| [YouTube to Invidious](https://greasyfork.org/en/scripts/375264-youtube-to-invidious) | Scans current page for YouTube embeds and replace with invidio.us. (Can be edited to use any preferred instance) |
| [No-Youtube](https://github.com/mperez01/no-youtube) | Userscript to replace YouTube links to invidio.us. Can be edited to redirect to a preferred instance.
| [FYTE](https://greasyfork.org/en/scripts/9252-fyte-fast-youtube-embedded-player) | 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](https://github.com/dybdeskarphet/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](https://codeberg.org/mthsk/userscripts/src/branch/master/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](https://gitlab.com/userscripts3/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](https://docs.invidious.io/url-parameters/) for more information about URL Parameters. |

### Extensions that Integrate Invidious into other Apps

| Name | Description | Links |
|---|---|---|
| [iPhone Redirector Shortcut](https://www.icloud.com/shortcuts/6bbf26d989cf4d07a5fe1626efbc0950) | Automatically open YouTube videos in Invidious (iPhone shortcut). | |
| [FreshRSS Extension](https://github.com/tmiland/freshrss-invidious) | A FreshRSS extension to directly embed videos from Invidious channel feeds. | |
| [UntrackMe](https://f-droid.org/en/packages/app.fedilab.nitterizeme) | Android app to rewrite YouTube links to Invidious. Can optionally play videos in the app as well. | |
| [Kodi add-on](https://github.com/lekma/plugin.video.invidious) | Watch YouTube videos in the Kodi media center, using the Invidious API. Privacy-friendly alternative to the YouTube API add-on. | |
| [Koutube](https://github.com/iGerman00/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 <kbd>y</kbd> with a <kbd>k</kbd>. | [Source](https://github.com/iGerman00/koutube) / [Website](https://koutube.com) |

### Utilities

| Name | Description |
|---|---|
| [Invidious-Updater (And Installer)](https://github.com/tmiland/Invidious-Updater) | 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.

```
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](https://github.com/tmiland) for showing us a solution at [#299](https://github.com/iv-org/invidious/issues/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.
```
      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.
```
      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

      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:   

    - 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](https://github.com/iv-org/documentation/edit/master/docs/community-installation-guide.md). We rely on the community to help us.

After installation take a look at the [Post-install steps](installation.md#post-install-configuration).

## Podman (rootless container)

Guide contributor(s): [@sigulete](https://github.com/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](https://git.mcgee.red/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](https://github.com/iv-org/invidious/blob/master/config/config.example.yml).

`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

    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](https://docs.invidious.io/installation/#post-install-configuration). 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](https://github.com/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](https://forum.openwrt.org/t/use-home-arpa-as-default-tld-for-local-network/165056/11) 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):
```
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