diff --git a/src/server/axum.rs b/src/server/axum.rs
index d402a7c..4371c72 100644
--- a/src/server/axum.rs
+++ b/src/server/axum.rs
@@ -7,7 +7,6 @@ use tower_http::{
 	normalize_path::{NormalizePath, NormalizePathLayer},
 	services::ServeDir,
 };
-use utoipa::OpenApi;
 use utoipa_axum::router::OpenApiRouter;
 use utoipa_scalar::{Scalar, Servable};
 
@@ -25,7 +24,7 @@ pub fn make_router(app: App) -> NormalizePath<Router> {
 		.fallback_service(ServeDir::new(&app.web_dir_path))
 		.layer(CompressionLayer::new());
 
-	let (open_api_router, open_api) = OpenApiRouter::with_openapi(doc::ApiDoc::openapi())
+	let (open_api_router, open_api) = OpenApiRouter::with_openapi(doc::open_api())
 		.nest("/api", api::router())
 		.split_for_parts();
 
diff --git a/src/server/axum/api.rs b/src/server/axum/api.rs
index 86abcae..42939ef 100644
--- a/src/server/axum/api.rs
+++ b/src/server/axum/api.rs
@@ -113,6 +113,10 @@ async fn get_initial_setup(
 	get,
 	path = "/settings",
 	tag = "Configuration",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200, body = dto::Settings),
 	),
@@ -141,6 +145,10 @@ async fn get_settings(
 	put,
 	path = "/settings",
 	tag = "Configuration",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	request_body = dto::NewSettings,
 )]
 async fn put_settings(
@@ -172,6 +180,10 @@ async fn put_settings(
 	get,
 	path = "/mount_dirs",
 	tag = "Configuration",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200, body = Vec<dto::MountDir>),
 	),
@@ -189,6 +201,10 @@ async fn get_mount_dirs(
 	put,
 	path = "/mount_dirs",
 	tag = "Configuration",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	request_body = Vec<dto::MountDir>,
 )]
 async fn put_mount_dirs(
@@ -236,6 +252,10 @@ async fn post_auth(
 	get,
 	path = "/users",
 	tag = "User Management",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200, body = Vec<dto::User>),
 	),
@@ -253,6 +273,10 @@ async fn get_users(
 	post,
 	path = "/user",
 	tag = "User Management",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	request_body = dto::NewUser,
 	responses(
 		(status = 200),
@@ -275,6 +299,10 @@ async fn post_user(
 	put,
 	path = "/user/{name}",
 	tag = "User Management",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	request_body = dto::UserUpdate,
 	responses(
 		(status = 200),
@@ -309,6 +337,10 @@ async fn put_user(
 	delete,
 	path = "/user/{name}",
 	tag = "User Management",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200),
 		(status = 404),
@@ -329,7 +361,15 @@ async fn delete_user(
 	Ok(())
 }
 
-#[utoipa::path(post, path = "/trigger_index", tag = "Configuration")]
+#[utoipa::path(
+	post,
+	path = "/trigger_index",
+	tag = "Configuration",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
+)]
 async fn post_trigger_index(
 	_admin_rights: AdminRights,
 	State(scanner): State<scanner::Scanner>,
@@ -342,6 +382,10 @@ async fn post_trigger_index(
 	get,
 	path = "/index_status",
 	tag = "Configuration",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200, body = dto::IndexStatus),
 	)
@@ -423,6 +467,10 @@ fn albums_to_response(albums: Vec<index::Album>, api_version: APIMajorVersion) -
 	get,
 	path = "/browse",
 	tag = "File Browser",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8)
 	),
@@ -446,6 +494,10 @@ async fn get_browse_root(
 	get,
 	path = "/browse/{*path}",
 	tag = "File Browser",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
 		("path", allow_reserved),
@@ -471,6 +523,10 @@ async fn get_browse(
 	get,
 	path = "/flatten",
 	tag = "File Browser",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
 	),
@@ -495,6 +551,10 @@ async fn get_flatten_root(
 	get,
 	path = "/flatten/{*path}",
 	tag = "File Browser",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
 		("path", allow_reserved),
@@ -521,6 +581,10 @@ async fn get_flatten(
 	get,
 	path = "/albums",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200, body = Vec<dto::AlbumHeader>),
 	)
@@ -544,6 +608,10 @@ async fn get_albums(
 	get,
 	path = "/artists",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200, body = Vec<dto::ArtistHeader>),
 	)
@@ -567,6 +635,10 @@ async fn get_artists(
 	get,
 	path = "/artists/{artist}",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("artist",)),
 	responses(
 		(status = 200, body = dto::Artist),
@@ -584,6 +656,10 @@ async fn get_artist(
 	get,
 	path = "/artists/{artists}/albums/{album}",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("artists",),
 		("album",),
@@ -608,6 +684,10 @@ async fn get_album(
 	post, // post because of https://github.com/whatwg/fetch/issues/551
 	path = "/songs",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	request_body = dto::GetSongsBulkInput,
 	responses(
 		(status = 200, body = dto::GetSongsBulkOutput),
@@ -639,6 +719,10 @@ async fn get_songs(
 	get,
 	path = "/peaks/{*path}",
 	tag = "Media",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("path", allow_reserved)),
 	responses(
 		(status = 200, body = [u8]),
@@ -659,6 +743,10 @@ async fn get_peaks(
 	get,
 	path = "/albums/random",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
 		dto::GetRandomAlbumsParameters,
@@ -689,6 +777,10 @@ async fn get_random_albums(
 	get,
 	path = "/albums/recent",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
 		dto::GetRecentAlbumsParameters
@@ -716,6 +808,10 @@ async fn get_recent_albums(
 	get,
 	path = "/genres",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200, body = Vec<dto::GenreHeader>),
 	)
@@ -738,6 +834,10 @@ async fn get_genres(
 	get,
 	path = "/genres/{genre}",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("genre",)),
 	responses(
 		(status = 200, body = Vec<dto::Genre>),
@@ -755,6 +855,10 @@ async fn get_genre(
 	get,
 	path = "/genres/{genre}/albums",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("genre",)),
 	responses(
 		(status = 200, body = Vec<dto::AlbumHeader>),
@@ -779,6 +883,10 @@ async fn get_genre_albums(
 	get,
 	path = "/genres/{genre}/artists",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("genre",)),
 	responses(
 		(status = 200, body = Vec<dto::ArtistHeader>),
@@ -803,6 +911,10 @@ async fn get_genre_artists(
 	get,
 	path = "/genres/{genre}/songs",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("genre",)),
 	responses(
 		(status = 200, body = dto::SongList),
@@ -829,6 +941,10 @@ async fn get_genre_songs(
 	get,
 	path = "/search/{*query}",
 	tag = "Collection",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
 		("query", allow_reserved),
@@ -874,6 +990,10 @@ async fn get_search(
 	get,
 	path = "/playlists",
 	tag = "Playlists",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	responses(
 		(status = 200, body = Vec<dto::PlaylistHeader>),
 	)
@@ -892,6 +1012,10 @@ async fn get_playlists(
 	put,
 	path = "/playlist/{name}",
 	tag = "Playlists",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("name",)),
 	request_body = dto::SavePlaylistInput,
 )]
@@ -918,6 +1042,10 @@ async fn put_playlist(
 	get,
 	path = "/playlist/{name}",
 	tag = "Playlists",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("Accept-Version" = Option<i32>, Header, minimum = 7, maximum = 8),
 		("name",),
@@ -955,6 +1083,10 @@ async fn get_playlist(
 	delete,
 	path = "/playlist/{name}",
 	tag = "Playlists",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("name",)),
 )]
 async fn delete_playlist(
@@ -972,6 +1104,10 @@ async fn delete_playlist(
 	get,
 	path = "/audio/{*path}",
 	tag = "Media",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(("path", allow_reserved)),
 	responses(
 		(status = 206, body = [u8]),
@@ -1002,6 +1138,10 @@ async fn get_audio(
 	get,
 	path = "/thumbnail/{*path}",
 	tag = "Media",
+	security(
+		("auth_token" = []),
+		("auth_query_param" = []),
+	),
 	params(
 		("path", allow_reserved),
 		dto::ThumbnailOptions
diff --git a/src/server/doc.rs b/src/server/doc.rs
index 3af22a9..add86b0 100644
--- a/src/server/doc.rs
+++ b/src/server/doc.rs
@@ -1,4 +1,41 @@
-use utoipa::OpenApi;
+use utoipa::openapi::{
+	security::{ApiKey, ApiKeyValue, HttpAuthScheme, HttpBuilder, SecurityScheme},
+	ComponentsBuilder, ContactBuilder, InfoBuilder, License, OpenApi, OpenApiBuilder,
+};
 
-#[derive(OpenApi)]
-pub struct ApiDoc;
+pub fn open_api() -> OpenApi {
+	let auth_token_description = "Authentication token acquired from the `/auth` endpoint";
+
+	OpenApiBuilder::new()
+		.info(
+			InfoBuilder::new()
+				.title(env!("CARGO_PKG_NAME"))
+				.version(env!("CARGO_PKG_VERSION"))
+				.license(Some(License::new("MIT")))
+				.contact(Some(
+					ContactBuilder::new().name(Some("Antoine Gersant")).build(),
+				))
+				.build(),
+		)
+		.components(Some(
+			ComponentsBuilder::new()
+				.security_scheme(
+					"auth_header",
+					SecurityScheme::Http(
+						HttpBuilder::new()
+							.scheme(HttpAuthScheme::Bearer)
+							.description(Some(auth_token_description))
+							.build(),
+					),
+				)
+				.security_scheme(
+					"auth_query_param",
+					SecurityScheme::ApiKey(ApiKey::Query(ApiKeyValue::with_description(
+						"auth_token",
+						auth_token_description,
+					))),
+				)
+				.build(),
+		))
+		.build()
+}