Metadata APIs

Reference for metadata plugin interfaces - Search, Track, Album, Artist, Playlist, User, and Browse.

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)

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.