spotify

package
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 26, 2026 License: MIT Imports: 12 Imported by: 0

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

View Source
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

View Source
var (
	// ErrUnauthorized is returned when the API rejects the token.
	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

func (a Album) ArtistNames() string

ArtistNames joins the credited artists, as Track.ArtistNames does.

func (Album) Year

func (a Album) Year() string

Year is the release year alone. `release_date` varies in precision — a year, a month or a full date — and only the year is worth a table column.

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 New

func New(token string) *Client

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

func (c *Client) CreatePlaylist(ctx context.Context, p NewPlaylist) (*Playlist, error)

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

func (c *Client) GetAlbum(ctx context.Context, id string) (*Album, error)

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

func (c *Client) GetPlaylist(ctx context.Context, id string) (*Playlist, error)

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

func (c *Client) ListPlaylists(ctx context.Context, limit int, all bool) ([]Playlist, error)

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

func (c *Client) Me(ctx context.Context) (*User, error)

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.

func (Owner) Name

func (o Owner) Name() string

Name is the owner's display name, falling back to the user id. Spotify omits the display name for accounts that never set one.

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

func (p Playlist) IsPublic() bool

IsPublic reports the visibility, treating an absent field as private. The API omits `public` for playlists the caller does not own.

func (Playlist) Total

func (p Playlist) Total() int

Total is the number of items in the playlist, reading the current field and falling back to the deprecated one.

func (Playlist) URL

func (p Playlist) URL() string

URL is the open.spotify.com link, for output that a human will click.

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

func (t Track) ArtistNames() string

ArtistNames joins the credited artists the way Spotify's own UI does.

func (Track) Duration

func (t Track) Duration() string

Duration renders the track length as m:ss, or the empty string when the API did not say.

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL