Documentation
¶
Overview ¶
Package spotify is a thin client for the Spotify Web API.
Schemas and paths follow the February 2026 revision, which renamed the playlist track endpoints and reshaped the responses: https://developer.spotify.com/documentation/web-api/references/changes/february-2026
Three of those changes shape this package:
- /playlists/{id}/tracks became /playlists/{id}/items, for GET, POST, PUT and DELETE alike, and the DELETE body's `tracks` array became `items`.
- The playlist object's `tracks` field became `items` at every nesting level, so what was tracks.tracks.track is now items.items.item.
- Batch lookups (GET /tracks, /albums, /artists) were removed outright, so a set of ids costs one request each. Nothing here does that in a loop.
The track object also lost `popularity`, which is why ranking search results lives in internal/match rather than being a sort on a field from the API.
Index ¶
- Constants
- Variables
- type Album
- type Artist
- type Client
- func (c *Client) AddItems(ctx context.Context, playlistID string, uris []string, position int) (string, error)
- func (c *Client) CreatePlaylist(ctx context.Context, p NewPlaylist) (*Playlist, error)
- func (c *Client) GetAlbum(ctx context.Context, id string) (*Album, error)
- func (c *Client) GetPlaylist(ctx context.Context, id string) (*Playlist, error)
- func (c *Client) ListAlbumTracks(ctx context.Context, id string, limit int, all bool) ([]Track, error)
- func (c *Client) ListPlaylistItems(ctx context.Context, id string, limit int, all bool) ([]PlaylistItem, error)
- func (c *Client) ListPlaylists(ctx context.Context, limit int, all bool) ([]Playlist, error)
- func (c *Client) Me(ctx context.Context) (*User, error)
- func (c *Client) RemoveItems(ctx context.Context, playlistID string, uris []string, snapshotID string) (string, error)
- func (c *Client) SearchAlbums(ctx context.Context, q string, limit, offset int) ([]Album, int, error)
- func (c *Client) SearchTracks(ctx context.Context, q string, limit, offset int) ([]Track, int, error)
- type NewPlaylist
- type Owner
- type Playlist
- type PlaylistItem
- type Track
- type User
Constants ¶
const ( // MaxItemsPerRequest caps both adding and removing playlist items. MaxItemsPerRequest = 100 // MaxSearchLimit is the search cap. February 2026 cut it from 50 to 10. MaxSearchLimit = 10 // MaxPageLimit is the cap on paged list endpoints. MaxPageLimit = 50 )
Limits the API imposes. They are exported because commands have to chunk and validate against them before making a call.
Variables ¶
var ( ErrUnauthorized = errors.New("spotify: token rejected") // ErrForbidden is returned when the token is valid but the call is not // allowed — a missing scope, or an account outside a Development Mode app's // list of authorised users. ErrForbidden = errors.New("spotify: forbidden") // ErrNotFound is returned when the API has no such object. ErrNotFound = errors.New("spotify: not found") // ErrRateLimited is returned when retries ran out on a 429. ErrRateLimited = errors.New("spotify: rate limited") )
Functions ¶
This section is empty.
Types ¶
type Album ¶
type Album struct {
ID string `json:"id"`
Name string `json:"name"`
URI string `json:"uri,omitempty"`
ReleaseDate string `json:"release_date,omitempty"`
TotalTracks int `json:"total_tracks,omitempty"`
AlbumType string `json:"album_type,omitempty"`
Artists []Artist `json:"artists,omitempty"`
Tracks *page[Track] `json:"tracks,omitempty"`
Items *page[Track] `json:"items,omitempty"`
}
Album is an album object. `available_markets`, `label` and `popularity` are gone as of February 2026, and `genres` now always comes back empty.
Unlike the playlist object, the album's track collection is still called `tracks` — the February 2026 rename applied to playlists only. `Items` is decoded as well so a later rename does not empty the list silently.
func (Album) ArtistNames ¶
ArtistNames joins the credited artists, as Track.ArtistNames does.
type Artist ¶
type Artist struct {
ID string `json:"id"`
Name string `json:"name"`
URI string `json:"uri,omitempty"`
}
Artist is the simplified artist attached to a track.
type Client ¶
type Client struct {
// contains filtered or unexported fields
}
func (*Client) AddItems ¶
func (c *Client) AddItems(ctx context.Context, playlistID string, uris []string, position int) (string, error)
AddItems appends items to a playlist, or inserts them at position when it is non-negative. It chunks at MaxItemsPerRequest and returns the final snapshot.
Chunks go in order, and a later chunk failing leaves the earlier ones in place: the API has no transaction across requests. The error says how many landed so the caller can report it rather than implying nothing happened.
func (*Client) CreatePlaylist ¶
CreatePlaylist creates a playlist for the current user.
POST /users/{user_id}/playlists was removed in February 2026; this is the replacement, and it can only create playlists for the authenticated user.
func (*Client) GetAlbum ¶
GetAlbum fetches one album's metadata.
Single-album lookup survived February 2026; it was the batch GET /albums that was removed, which is why nothing here fetches ids in a loop.
func (*Client) GetPlaylist ¶
GetPlaylist fetches a playlist's metadata, so a command can show what it is about to change and confirm the id is real.
func (*Client) ListAlbumTracks ¶
func (c *Client) ListAlbumTracks(ctx context.Context, id string, limit int, all bool) ([]Track, error)
ListAlbumTracks returns an album's tracks, in album order.
This is the reliable way to fill a playlist from a record: it is the actual track list, so none of the name-matching in internal/match is involved, and it pages at 50 rather than being capped at the 10 that search now allows.
The objects are simplified tracks, so they carry no album of their own — the caller already knows which album it asked for.
func (*Client) ListPlaylistItems ¶
func (c *Client) ListPlaylistItems(ctx context.Context, id string, limit int, all bool) ([]PlaylistItem, error)
ListPlaylistItems returns a playlist's items from GET /playlists/{id}/items — the path that replaced /tracks.
func (*Client) ListPlaylists ¶
ListPlaylists returns the current user's playlists. It pages to the end when all is set, otherwise it returns the first limit entries.
func (*Client) Me ¶
Me returns the current user's profile. Also the cheapest way to prove a token works, which is what `auth status` uses it for.
func (*Client) RemoveItems ¶
func (c *Client) RemoveItems(ctx context.Context, playlistID string, uris []string, snapshotID string) (string, error)
RemoveItems removes every occurrence of the given URIs from a playlist.
The request body's array is `items` as of February 2026, where it was `tracks`. snapshotID is optional; passing it makes the removal apply to a known version of the playlist rather than to whatever it looks like now.
func (*Client) SearchAlbums ¶
func (c *Client) SearchAlbums(ctx context.Context, q string, limit, offset int) ([]Album, int, error)
SearchAlbums returns albums matching q. The same February 2026 cap applies as for tracks, so limit is clamped to MaxSearchLimit.
func (*Client) SearchTracks ¶
func (c *Client) SearchTracks(ctx context.Context, q string, limit, offset int) ([]Track, int, error)
SearchTracks returns tracks matching q. limit is clamped to MaxSearchLimit — the February 2026 ceiling of 10, down from 50.
It returns the total the API reports alongside the page, so a caller can tell "these are all the matches" from "these are the first ten of hundreds".
type NewPlaylist ¶
type NewPlaylist struct {
Name string `json:"name"`
Description string `json:"description,omitempty"`
Public bool `json:"public"`
}
NewPlaylist is the body of a create request.
type Owner ¶
type Owner struct {
ID string `json:"id"`
DisplayName string `json:"display_name,omitempty"`
URI string `json:"uri,omitempty"`
}
Owner is the simplified user object on a playlist.
A user carries `display_name`, not `name` — decoding it as an Artist leaves the owner blank on every row, which is exactly what it did until a real playlist listing showed an empty column.
type Playlist ¶
type Playlist struct {
ID string `json:"id"`
Name string `json:"name"`
Description string `json:"description,omitempty"`
Public *bool `json:"public,omitempty"`
Owner Owner `json:"owner,omitempty"`
URI string `json:"uri,omitempty"`
SnapshotID string `json:"snapshot_id,omitempty"`
ExternalURL map[string]string `json:"external_urls,omitempty"`
Items *page[PlaylistItem] `json:"items,omitempty"`
Tracks *page[PlaylistItem] `json:"tracks,omitempty"`
}
Playlist is a playlist object. The item collection is `items` since February 2026; `tracks` remains as a deprecated alias, which is why Total reads whichever of the two the API actually sent.
func (Playlist) IsPublic ¶
IsPublic reports the visibility, treating an absent field as private. The API omits `public` for playlists the caller does not own.
type PlaylistItem ¶
type PlaylistItem struct {
AddedAt string `json:"added_at,omitempty"`
IsLocal bool `json:"is_local,omitempty"`
Item *Track `json:"item,omitempty"`
LegacyT *Track `json:"track,omitempty"`
}
PlaylistItem is one entry in a playlist. February 2026 renamed the nested object from `track` to `item`; both are decoded so a response from either shape yields a track rather than a silently empty row.
func (PlaylistItem) Track ¶
func (i PlaylistItem) Track() Track
Track returns the item's track, whichever field carried it.
type Track ¶
type Track struct {
ID string `json:"id"`
Name string `json:"name"`
URI string `json:"uri"`
DurationMS int `json:"duration_ms,omitempty"`
Explicit bool `json:"explicit,omitempty"`
TrackNumber int `json:"track_number,omitempty"`
DiscNumber int `json:"disc_number,omitempty"`
Artists []Artist `json:"artists,omitempty"`
Album Album `json:"album,omitempty"`
IsLocal bool `json:"is_local,omitempty"`
}
Track is a track object, minus the fields February 2026 removed (`popularity`, `available_markets`, `external_ids`, `linked_from`).
func (Track) ArtistNames ¶
ArtistNames joins the credited artists the way Spotify's own UI does.
type User ¶
type User struct {
ID string `json:"id"`
DisplayName string `json:"display_name,omitempty"`
URI string `json:"uri,omitempty"`
ExternalURL map[string]string `json:"external_urls,omitempty"`
}
User is the current user's profile. February 2026 removed `country`, `email`, `product`, `followers` and `explicit_content`, so there is no longer any way to tell from the API whether the account is Premium.