diff --git a/src/WikiMusic/Servant/ApiSpec.hs b/src/WikiMusic/Servant/ApiSpec.hs
--- a/src/WikiMusic/Servant/ApiSpec.hs
+++ b/src/WikiMusic/Servant/ApiSpec.hs
@@ -3,14 +3,23 @@
 {-# LANGUAGE NoFieldSelectors #-}
 
 module WikiMusic.Servant.ApiSpec
-  ( WikiMusicPrivateAPI,
-    WikiMusicPublicAPI,
+  ( PrivateAPI,
+    PublicAPI,
     WikiMusicAPIServer,
-    WikiMusicAPIDocsServer,
-    WikiMusicPrivateArtistsAPI,
-    WikiMusicPrivateSongsAPI,
-    WikiMusicPrivateAuthAPI,
-    WikiMusicPrivateGenresAPI,
+    APIDocsServer,
+    ArtistsAPI,
+    SongsAPI,
+    AuthAPI,
+    GenresAPI,
+    WithBaseEntityRoutes,
+    WithComments,
+    WithPaginationParams,
+    WithOpinions,
+    WithArtworks,
+    WithArtworkOrders,
+    LoginRespondWithAuth,
+    WithSearch,
+    WithIdentifier,
   )
 where
 
@@ -27,137 +36,141 @@
 import WikiMusic.Model.Other
 
 type WikiMusicAPIServer =
-  WikiMusicPrivateAPI
+  PrivateAPI
     :<|> SwaggerAPI
-    :<|> WikiMusicPublicAPI
+    :<|> PublicAPI
 
-type WikiMusicAPIDocsServer = WikiMusicPublicAPI :<|> WikiMusicPrivateAPI
+type APIDocsServer = PublicAPI :<|> PrivateAPI
 
+type SwaggerAPI = "swagger.json" :> Get '[JSON] Data.OpenApi.OpenApi
+
 type WithAuth = Header "x-wikimusic-auth" Text
 
-type WikiMusicPrivateArtistsAPI =
+type PrivateAPI =
+  ArtistsAPI
+    :<|> GenresAPI
+    :<|> SongsAPI
+    :<|> AuthAPI
+
+type PublicAPI =
+  "login"
+    :> ReqBody '[JSON] LoginRequest
+    :> LoginRespondWithAuth
+    :<|> "reset-password"
+      :> ( "email"
+             :> Capture "email" Text
+             :> Post '[JSON] MakeResetPasswordLinkResponse
+             :<|> "do"
+               :> ReqBody '[JSON] DoPasswordResetRequest
+               :> Verb 'POST 204 '[JSON] ()
+         )
+    :<|> "system-information"
+      :> Get '[JSON] SystemInformationResponse
+
+type ArtistsAPI =
   "artists"
-    :> WithAuth
-    :> QueryParam "limit" Int
-    :> QueryParam "offset" Int
-    :> QueryParam "sort-order" Text
-    :> QueryParam "include" Text
-    :> Get '[JSON] GetArtistsQueryResponse
-    :<|> "artists"
-      :> WithAuth
-      :> "search"
-      :> Capture "searchInput" Text
-      :> QueryParam "limit" Int
-      :> QueryParam "offset" Int
-      :> QueryParam "sort-order" Text
-      :> QueryParam "include" Text
-      :> Get '[JSON] GetArtistsQueryResponse
-    :<|> "artists" :> "identifier" :> WithAuth :> Capture "identifier" UUID :> QueryParam "sort-order" Text :> QueryParam "include" Text :> Get '[JSON] GetArtistsQueryResponse
-    :<|> "artists" :> WithAuth :> ReqBody '[JSON] InsertArtistsRequest :> Post '[JSON] InsertArtistsCommandResponse
-    :<|> "artists" :> "comments" :> WithAuth :> ReqBody '[JSON] InsertArtistCommentsRequest :> Post '[JSON] InsertArtistCommentsCommandResponse
-    :<|> "artists" :> "opinions" :> WithAuth :> ReqBody '[JSON] UpsertArtistOpinionsRequest :> Post '[JSON] UpsertArtistOpinionsCommandResponse
-    :<|> "artists" :> "artworks" :> WithAuth :> ReqBody '[JSON] InsertArtistArtworksRequest :> Post '[JSON] InsertArtistArtworksCommandResponse
-    :<|> "artists" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "artists" :> "comments" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "artists" :> "opinions" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "artists" :> "artworks" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "artists" :> "artworks" :> "order" :> WithAuth :> ReqBody '[JSON] ArtistArtworkOrderUpdateRequest :> Patch '[JSON] ()
-    :<|> "artists" :> "edit" :> WithAuth :> ReqBody '[JSON] ArtistDeltaRequest :> Patch '[JSON] ()
+    :> ( WithBaseEntityRoutes
+           :<|> WithAuth
+             :> WithPaginationParams
+             :> Get '[JSON] GetArtistsQueryResponse
+           :<|> "search"
+             :> WithAuth
+             :> WithSearch
+             :> WithPaginationParams
+             :> Get '[JSON] GetArtistsQueryResponse
+           :<|> WithDetailsFromIdentifier GetArtistsQueryResponse
+           :<|> WithAuth :> ReqBody '[JSON] InsertArtistsRequest :> Post '[JSON] InsertArtistsCommandResponse
+           :<|> WithComments InsertArtistCommentsRequest InsertArtistCommentsCommandResponse
+           :<|> WithOpinions UpsertArtistOpinionsRequest UpsertArtistOpinionsCommandResponse
+           :<|> WithArtworks InsertArtistArtworksRequest InsertArtistArtworksCommandResponse
+           :<|> WithArtworkOrders ArtistArtworkOrderUpdateRequest
+           :<|> "edit" :> WithAuth :> ReqBody '[JSON] ArtistDeltaRequest :> Patch '[JSON] ()
+       )
 
-type WikiMusicPrivateSongsAPI =
+type SongsAPI =
   "songs"
-    :> WithAuth
-    :> QueryParam "limit" Int
-    :> QueryParam "offset" Int
-    :> QueryParam "sort-order" Text
-    :> QueryParam "include" Text
-    :> Get '[JSON] GetSongsQueryResponse
-    :<|> "songs"
-      :> "search"
-      :> WithAuth
-      :> Capture "searchInput" Text
-      :> QueryParam "limit" Int
-      :> QueryParam "offset" Int
-      :> QueryParam "sort-order" Text
-      :> QueryParam "include" Text
-      :> Get '[JSON] GetSongsQueryResponse
-    :<|> "songs" :> "identifier" :> WithAuth :> Capture "identifier" UUID :> QueryParam "sort-order" Text :> QueryParam "include" Text :> Get '[JSON] GetSongsQueryResponse
-    :<|> "songs" :> WithAuth :> ReqBody '[JSON] InsertSongsRequest :> Post '[JSON] InsertSongsCommandResponse
-    :<|> "songs" :> "comments" :> WithAuth :> ReqBody '[JSON] InsertSongCommentsRequest :> Post '[JSON] InsertSongCommentsCommandResponse
-    :<|> "songs" :> "opinions" :> WithAuth :> ReqBody '[JSON] UpsertSongOpinionsRequest :> Post '[JSON] UpsertSongOpinionsCommandResponse
-    :<|> "songs" :> "artworks" :> WithAuth :> ReqBody '[JSON] InsertSongArtworksRequest :> Post '[JSON] InsertSongArtworksCommandResponse
-    :<|> "songs" :> "artists" :> WithAuth :> ReqBody '[JSON] InsertArtistsOfSongsRequest :> Post '[JSON] InsertArtistsOfSongCommandResponse
-    :<|> "songs" :> "artists" :> WithAuth :> ReqBody '[JSON] InsertArtistsOfSongsRequest :> Delete '[JSON] ()
-    :<|> "songs" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "songs" :> "comments" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "songs" :> "opinions" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "songs" :> "artworks" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "songs" :> "artworks" :> "order" :> WithAuth :> ReqBody '[JSON] SongArtworkOrderUpdateRequest :> Patch '[JSON] ()
-    :<|> "songs" :> "edit" :> WithAuth :> ReqBody '[JSON] SongDeltaRequest :> Patch '[JSON] ()
-    :<|> "songs" :> "contents" :> WithAuth :> ReqBody '[JSON] InsertSongContentsRequest :> Post '[JSON] InsertSongContentsCommandResponse
-    :<|> "songs" :> "contents" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "songs" :> "contents" :> WithAuth :> ReqBody '[JSON] SongContentDeltaRequest :> Patch '[JSON] ()
+    :> ( WithBaseEntityRoutes
+           :<|> WithAuth
+             :> WithPaginationParams
+             :> Get '[JSON] GetSongsQueryResponse
+           :<|> "search"
+             :> WithAuth
+             :> WithSearch
+             :> WithPaginationParams
+             :> Get '[JSON] GetSongsQueryResponse
+           :<|> "identifier" :> WithDetailsFromIdentifier GetSongsQueryResponse
+           :<|> WithAuth :> ReqBody '[JSON] InsertSongsRequest :> Post '[JSON] InsertSongsCommandResponse
+           :<|> WithComments InsertSongCommentsRequest InsertSongCommentsCommandResponse
+           :<|> WithOpinions UpsertSongOpinionsRequest UpsertSongOpinionsCommandResponse
+           :<|> WithArtworks InsertSongArtworksRequest InsertSongArtworksCommandResponse
+           :<|> "artists" :> WithAuth :> ReqBody '[JSON] InsertArtistsOfSongsRequest :> Post '[JSON] InsertArtistsOfSongCommandResponse
+           :<|> "artists" :> WithAuth :> ReqBody '[JSON] InsertArtistsOfSongsRequest :> Delete '[JSON] ()
+           :<|> WithArtworkOrders SongArtworkOrderUpdateRequest
+           :<|> "edit" :> WithAuth :> ReqBody '[JSON] SongDeltaRequest :> Patch '[JSON] ()
+           :<|> "contents" :> WithAuth :> ReqBody '[JSON] InsertSongContentsRequest :> Post '[JSON] InsertSongContentsCommandResponse
+           :<|> "contents" :> WithAuth :> WithIdentifier :> Delete '[JSON] ()
+           :<|> "contents" :> WithAuth :> ReqBody '[JSON] SongContentDeltaRequest :> Patch '[JSON] ()
+       )
 
-type WikiMusicPrivateGenresAPI =
+type GenresAPI =
   "genres"
-    :> WithAuth
-    :> QueryParam "limit" Int
+    :> ( WithBaseEntityRoutes
+           :<|> WithAuth
+             :> WithPaginationParams
+             :> Get '[JSON] GetGenresQueryResponse
+           :<|> "search"
+             :> WithAuth
+             :> WithSearch
+             :> WithPaginationParams
+             :> Get '[JSON] GetGenresQueryResponse
+           :<|> WithDetailsFromIdentifier GetGenresQueryResponse
+           :<|> WithAuth :> ReqBody '[JSON] InsertGenresRequest :> Post '[JSON] InsertGenresCommandResponse
+           :<|> WithComments InsertGenreCommentsRequest InsertGenresCommandResponse
+           :<|> WithOpinions UpsertGenreOpinionsRequest UpsertGenreOpinionsCommandResponse
+           :<|> WithArtworks InsertGenreArtworksRequest InsertGenreArtworksCommandResponse
+           :<|> WithArtworkOrders GenreArtworkOrderUpdateRequest
+           :<|> "edit" :> WithAuth :> ReqBody '[JSON] GenreDeltaRequest :> Patch '[JSON] ()
+       )
+
+type AuthAPI =
+  "me" :> WithAuth :> Get '[JSON] GetMeQueryResponse
+    :<|> "users"
+      :> ( "invite" :> WithAuth :> ReqBody '[JSON] InviteUsersRequest :> Post '[JSON] MakeResetPasswordLinkResponse
+             :<|> "delete" :> WithAuth :> ReqBody '[JSON] DeleteUsersRequest :> Post '[JSON] ()
+         )
+
+type WithPaginationParams =
+  QueryParam "limit" Int
     :> QueryParam "offset" Int
     :> QueryParam "sort-order" Text
     :> QueryParam "include" Text
-    :> Get '[JSON] GetGenresQueryResponse
-    :<|> "genres"
-      :> "search"
-      :> WithAuth
-      :> Capture "searchInput" Text
-      :> QueryParam "limit" Int
-      :> QueryParam "offset" Int
-      :> QueryParam "sort-order" Text
-      :> QueryParam "include" Text
-      :> Get '[JSON] GetGenresQueryResponse
-    :<|> "genres" :> "identifier" :> WithAuth :> Capture "identifier" UUID :> QueryParam "sort-order" Text :> QueryParam "include" Text :> Get '[JSON] GetGenresQueryResponse
-    :<|> "genres" :> WithAuth :> ReqBody '[JSON] InsertGenresRequest :> Post '[JSON] InsertGenresCommandResponse
-    :<|> "genres" :> "comments" :> WithAuth :> ReqBody '[JSON] InsertGenreCommentsRequest :> Post '[JSON] InsertGenreCommentsCommandResponse
-    :<|> "genres" :> "opinions" :> WithAuth :> ReqBody '[JSON] UpsertGenreOpinionsRequest :> Post '[JSON] UpsertGenreOpinionsCommandResponse
-    :<|> "genres" :> "artworks" :> WithAuth :> ReqBody '[JSON] InsertGenreArtworksRequest :> Post '[JSON] InsertGenreArtworksCommandResponse
-    :<|> "genres" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "genres" :> "comments" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "genres" :> "opinions" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "genres" :> "artworks" :> WithAuth :> Capture "identifier" UUID :> Delete '[JSON] ()
-    :<|> "genres" :> "artworks" :> "order" :> WithAuth :> ReqBody '[JSON] GenreArtworkOrderUpdateRequest :> Patch '[JSON] ()
-    :<|> "genres" :> "edit" :> WithAuth :> ReqBody '[JSON] GenreDeltaRequest :> Patch '[JSON] ()
 
-type WikiMusicPrivateAuthAPI =
-  "me" :> WithAuth :> Get '[JSON] GetMeQueryResponse
-    :<|> "users" :> "invite" :> WithAuth :> ReqBody '[JSON] InviteUsersRequest :> Post '[JSON] MakeResetPasswordLinkResponse
-    :<|> "users" :> "delete" :> WithAuth :> ReqBody '[JSON] DeleteUsersRequest :> Post '[JSON] ()
+type LoginRespondWithAuth =
+  Verb
+    'POST
+    204
+    '[JSON]
+    ( Headers
+        '[WithAuth]
+        NoContent
+    )
 
-type WikiMusicPrivateAPI =
-  WikiMusicPrivateArtistsAPI
-    :<|> WikiMusicPrivateGenresAPI
-    :<|> WikiMusicPrivateSongsAPI
-    :<|> WikiMusicPrivateAuthAPI
+type WithIdentifier = Capture "identifier" UUID
 
-type WikiMusicPublicAPI =
-  "login"
-    :> ReqBody '[JSON] LoginRequest
-    :> Verb
-         'POST
-         204
-         '[JSON]
-         ( Headers
-             '[WithAuth]
-             NoContent
-         )
-    :<|> "reset-password"
-      :> "email"
-      :> Capture "email" Text
-      :> Post '[JSON] MakeResetPasswordLinkResponse
-    :<|> "reset-password"
-      :> "do"
-      :> ReqBody '[JSON] DoPasswordResetRequest
-      :> Verb 'POST 204 '[JSON] ()
-    :<|> "system-information"
-      :> Get '[JSON] SystemInformationResponse
+type WithSearch = Capture "searchInput" Text
 
-type SwaggerAPI = "swagger.json" :> Get '[JSON] Data.OpenApi.OpenApi
+type WithBaseEntityRoutes =
+  WithAuth :> WithIdentifier :> Delete '[JSON] ()
+    :<|> "comments" :> WithAuth :> WithIdentifier :> Delete '[JSON] ()
+    :<|> "opinions" :> WithAuth :> WithIdentifier :> Delete '[JSON] ()
+    :<|> "artworks" :> WithAuth :> WithIdentifier :> Delete '[JSON] ()
+
+type WithComments a b = "comments" :> WithAuth :> ReqBody '[JSON] a :> Post '[JSON] b
+
+type WithOpinions a b = "opinions" :> WithAuth :> ReqBody '[JSON] a :> Post '[JSON] b
+
+type WithArtworks a b = "artworks" :> WithAuth :> ReqBody '[JSON] a :> Post '[JSON] b
+
+type WithArtworkOrders a = "artworks" :> "order" :> WithAuth :> ReqBody '[JSON] a :> Patch '[JSON] ()
+
+type WithDetailsFromIdentifier a = "identifier" :> WithAuth :> WithIdentifier :> Get '[JSON] a
diff --git a/wikimusic-api-spec.cabal b/wikimusic-api-spec.cabal
--- a/wikimusic-api-spec.cabal
+++ b/wikimusic-api-spec.cabal
@@ -1,7 +1,7 @@
 cabal-version: 1.12
 
 name:           wikimusic-api-spec
-version:        1.1.0.0
+version:        1.2.0.0
 description:    Please see the README at <https://gitlab.com/jjba-projects/wikimusic-api-spec>
 homepage:       https://gitlab.com/jjba-projects/wikimusic-api-spec
 bug-reports:    https://gitlab.com/jjba-projects/wikimusic-api-spec/-/issues
