Metadata APIs
Metadata APIs let your plugin provide music catalog data to Spotube. Each interface handles a specific domain. Include "METADATA" in plugin.json abilities and implement whichever APIs your provider supports. See the example metadata implementations for complete working code.
Common types
All metadata APIs share these foundational types.
Pagination
sealed interface PaginationStrategy {
data class Offset(val offset: Int, val limit: Int) : PaginationStrategy
data class Cursor(val cursor: String?, val limit: Int) : PaginationStrategy
data class Page(val page: Int, val pageSize: Int) : PaginationStrategy
data class Continuation(val continuationToken: String?) : PaginationStrategy
}
data class PaginationResult<T>(
val items: List<T>,
val totalCount: Int,
val nextPagination: PaginationStrategy?,
)
Use Offset for the most common case. The others support cursor-based, page-based, and token-based APIs.
Thumbnail
data class Thumbnail(val url: String, val width: Int, val height: Int)
Search
MetadataSearchAPI handles all search queries. Declare which search types your provider supports, then implement the per-type methods.
interface MetadataSearchAPI : ZiplineService {
val supportedSearchTypes: List<MetadataSupportedSearchType>
suspend fun search(query: String): List<MetadataSearchResult>
suspend fun searchTracks(query: String, pagination: PaginationStrategy? = null):
PaginationResult<MetadataSearchResult.Track>
// + searchArtists, searchAlbums, searchPlaylists, searchUsers
}
Search results are a sealed class - MetadataSearchResult.Track, .Artist, .Album, .Playlist, .User. The per-type methods return typed PaginationResult wrappers. For example, searchTracks returns PaginationResult<MetadataSearchResult.Track> - not a raw list of MetadataTrack.
Tracks
interface MetadataTrackAPI : ZiplineService {
suspend fun getTrack(id: String): MetadataTrack
suspend fun savedTracks(pagination: PaginationStrategy?): PaginationResult<MetadataTrack>
suspend fun isSavedTracks(ids: List<String>): List<Boolean>
suspend fun saveTracks(ids: List<String>)
suspend fun removeSavedTracks(ids: List<String>)
suspend fun recommendationsBasedOnTracks(seedTrackIds: List<String>, limit: Int):
List<MetadataTrack>
}
Core CRUD for tracks. isSavedTracks returns a boolean list in the same order as the input IDs. recommendationsBasedOnTracks takes seed track IDs and a limit - return up to that many recommended tracks.
Albums
interface MetadataAlbumAPI : ZiplineService {
suspend fun getAlbum(id: String): MetadataAlbum.Detailed
suspend fun getTrackAlbum(track: MetadataTrack): MetadataAlbum.Detailed
suspend fun getAlbumTracks(id: String, pagination: PaginationStrategy?):
PaginationResult<MetadataTrack>
suspend fun savedAlbums(pagination: PaginationStrategy?):
PaginationResult<MetadataAlbum.Detailed>
suspend fun isSavedAlbums(ids: List<String>): List<Boolean>
suspend fun saveAlbums(ids: List<String>)
suspend fun removeSavedAlbums(ids: List<String>)
}
Album has two variants: Basic (id, title, description?, thumbnails, albumType, artists, externalUri?) and Detailed (adds releaseDate?, genres, trackCount). MetadataAlbumType is Single, Album, or Collection.
Artists
interface MetadataArtistAPI : ZiplineService {
suspend fun getArtist(id: String): MetadataArtist.Detailed
suspend fun artistOverview(id: String): MetadataArtistOverview
suspend fun getArtistTop10Tracks(id: String): List<MetadataTrack>
suspend fun relatedArtists(id: String, pagination: PaginationStrategy?):
PaginationResult<MetadataArtist.Basic>
suspend fun featuredPlaylists(id: String, pagination: PaginationStrategy?):
PaginationResult<MetadataPlaylist>
suspend fun getArtistAlbums(id: String, pagination: PaginationStrategy?):
PaginationResult<MetadataAlbum.Detailed>
// + saved, isSaved, save, removeSaved variants
}
Artist has two variants: Basic (id, name, thumbnails, externalUri?) and Detailed (adds genres?, biography?, followersCount?). artistOverview returns a comprehensive summary - artist details + top tracks + albums + related artists + featured playlists - in a single MetadataArtistOverview object.
Playlists
interface MetadataPlaylistAPI : ZiplineService {
suspend fun getPlaylist(id: String): MetadataPlaylist
suspend fun getPlaylistTracks(id: String, pagination: PaginationStrategy?):
PaginationResult<MetadataTrack>
suspend fun createPlaylist(name, description?, isPublic, isCollaborating,
imageBase64, trackIds): MetadataPlaylist
suspend fun updatePlaylist(id, name?, description?, isPublic?, isCollaborating?,
imageBase64?, trackIds?): MetadataPlaylist
suspend fun deletePlaylist(id: String)
suspend fun addTracksToPlaylist(playlistId: String, trackIds: List<String>)
suspend fun removeTracksFromPlaylist(playlistId: String, trackIds: List<String>)
// + saved, isSaved, save, removeSaved variants
}
createPlaylist and updatePlaylist both return the resulting MetadataPlaylist. On update, pass null for fields you don't want to change. imageBase64 is required on create - pass "" for no image.
Users
Minimal - fetch a user by ID.
interface MetadataUserAPI : ZiplineService {
suspend fun getUser(id: String): MetadataUser?
}
Browse
Provides the homepage/discover experience. featured() returns a flat list for the main browse screen. genres() lists available genres. list() returns browse sections for a genre (paginated). sublist() returns the items in an expanded section.
interface MetadataBrowseAPI : ZiplineService {
suspend fun featured(): List<MetadataBrowseItem>
suspend fun genres(): List<MetadataBrowseGenre>
suspend fun list(genreId: String, pagination: PaginationStrategy?):
PaginationResult<MetadataBrowseSection>
suspend fun sublist(genreId: String, sectionId: String, pagination: PaginationStrategy?):
PaginationResult<MetadataBrowseItem>
}
Browse items are sealed: MetadataBrowseItem.Track, .Album, .Artist, .Playlist, .User.
Binding
Register each API you implement in main():
zipline.bind<MetadataSearchAPI>(MetadataSearchAPI_SERVICE_NAME, RealMetadataSearchAPI())
zipline.bind<MetadataTrackAPI>(MetadataTrackAPI_SERVICE_NAME, RealMetadataTrackAPI())
zipline.bind<MetadataAlbumAPI>(MetadataAlbumAPI_SERVICE_NAME, RealMetadataAlbumAPI())
zipline.bind<MetadataArtistAPI>(MetadataArtistAPI_SERVICE_NAME, RealMetadataArtistAPI())
zipline.bind<MetadataPlaylistAPI>(MetadataPlaylistAPI_SERVICE_NAME, RealMetadataPlaylistAPI())
zipline.bind<MetadataBrowseAPI>(MetadataBrowseAPI_SERVICE_NAME, RealMetadataBrowseAPI())
zipline.bind<MetadataUserAPI>(MetadataUserAPI_SERVICE_NAME, RealMetadataUserAPI())
Data models
See Models for the full reference of MetadataTrack, MetadataUser, MetadataPlaylist, and all nested types.