Skip to content
DocumentationAPI Reference

User Library

Access and manage user profiles, saved tracks, albums, and listening history

The User Library endpoints allow you to access user profile information, manage saved content, and retrieve listening history. These features enable personalized experiences based on user preferences and behavior.

ScopeAccess
user-read-privateUser profile, subscription level
user-read-emailUser email address
user-library-readSaved tracks, albums, shows, audiobooks
user-library-modifySave/remove tracks, albums, shows, audiobooks
user-top-readTop artists and tracks
user-read-recently-playedRecently played tracks
user-follow-readFollowed artists and users
user-follow-modifyFollow/unfollow artists and users

import Spotted from "spotted-ts";
const client = new Spotted({ bearerToken: userAccessToken });
const user = await client.me.retrieve();
console.log(`
Display Name: ${user.display_name}
Email: ${user.email}
Country: ${user.country}
Product: ${user.product} // "premium", "free", etc.
Followers: ${user.followers.total}
`);
const profile = await client.users.retrieve("spotify");

// Get all saved tracks using async iterator
const savedTracks = [];
for await (const item of client.me.tracks.list({ market: "US" })) {
savedTracks.push(item);
}
console.log(`You have ${savedTracks.length} saved tracks`);
// Each item contains: added_at, track
savedTracks.forEach((item) => {
console.log(`${item.track.name} - saved on ${item.added_at}`);
});
// Save multiple tracks (max 50 per request)
await client.me.tracks.save({
ids: ["4iV5W9uYEdYUVa79Axb7Rh", "1301WleyT98MSxVHPZCA6M"],
});
await client.me.tracks.remove({
ids: ["4iV5W9uYEdYUVa79Axb7Rh"],
});
const saved = await client.me.tracks.check({
ids: "trackId1,trackId2",
});
// [true, false] - first track is saved, second is not

// Get all saved albums using async iterator
for await (const item of client.me.albums.list({ market: "US" })) {
console.log(`${item.album.name} by ${item.album.artists[0].name}`);
}
// Save albums
await client.me.albums.save({ ids: ["albumId1", "albumId2"] });
// Remove albums
await client.me.albums.remove({ ids: ["albumId1"] });
// Check if albums are saved
const saved = await client.me.albums.check({ ids: "albumId1,albumId2" });
// Returns [true, false]

Audiobooks are available within the US, UK, Canada, Ireland, New Zealand, and Australia markets.

// Get all saved audiobooks using async iterator
for await (const item of client.me.audiobooks.list({ market: "US" })) {
console.log(`${item.name} by ${item.authors[0].name}`);
}
// Save audiobooks
await client.me.audiobooks.save({ ids: ["audiobookId1", "audiobookId2"] });
// Remove audiobooks
await client.me.audiobooks.remove({ ids: ["audiobookId1"] });
// Check if audiobooks are saved
const saved = await client.me.audiobooks.check({
ids: "audiobookId1,audiobookId2",
});
// Returns [true, false]

Get the user’s most listened to artists and tracks over different time periods.

Approximately the last 4 weeks of listening history

// Get top artists for different time periods
const recentTopArtists = [];
for await (const artist of client.me.top.listArtists({
time_range: "short_term",
limit: 50,
})) {
recentTopArtists.push(artist);
if (recentTopArtists.length >= 50) break;
}
console.log("Your recent top artists:");
recentTopArtists.forEach((artist, i) => {
console.log(
`${i + 1}. ${artist.name} (${artist.genres.slice(0, 2).join(", ")})`,
);
});
// Get top tracks for a time range
const topTracks = [];
for await (const track of client.me.top.listTracks({
time_range: "medium_term",
limit: 50,
})) {
topTracks.push(track);
if (topTracks.length >= 50) break;
}
// Compare top tracks across time periods
async function analyzeListeningTrends() {
const getTop20 = async (timeRange) => {
const tracks = [];
for await (const track of client.me.top.listTracks({
time_range: timeRange,
limit: 20,
})) {
tracks.push(track);
if (tracks.length >= 20) break;
}
return tracks;
};
const [shortTerm, mediumTerm, longTerm] = await Promise.all([
getTop20("short_term"),
getTop20("medium_term"),
getTop20("long_term"),
]);
// Find consistent favorites (in all three lists)
const shortTermIds = new Set(shortTerm.map((t) => t.id));
const mediumTermIds = new Set(mediumTerm.map((t) => t.id));
const consistentFavorites = longTerm.filter(
(track) => shortTermIds.has(track.id) && mediumTermIds.has(track.id),
);
return {
recentFavorites: shortTerm,
consistentFavorites,
allTimeFavorites: longTerm,
};
}

Track what the user has been listening to recently.

// Get recently played tracks
const recent = await client.me.player.listRecentlyPlayedTracks({ limit: 50 });
recent.items.forEach((item) => {
const playedAt = new Date(item.played_at);
console.log(`${item.track.name} - played at ${playedAt.toLocaleString()}`);
});
// Get tracks played after a specific timestamp (Unix ms)
const oneHourAgo = Date.now() - 60 * 60 * 1000;
const lastHour = await client.me.player.listRecentlyPlayedTracks({
after: oneHourAgo,
});
async function analyzeListeningHistory() {
const recent = await client.me.player.listRecentlyPlayedTracks({ limit: 50 });
// Count plays per artist
const artistCounts = {};
const hourlyDistribution = new Array(24).fill(0);
recent.items.forEach((item) => {
// Count by artist
const artistName = item.track.artists[0].name;
artistCounts[artistName] = (artistCounts[artistName] || 0) + 1;
// Count by hour
const hour = new Date(item.played_at).getHours();
hourlyDistribution[hour]++;
});
// Sort artists by play count
const topArtists = Object.entries(artistCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 5);
// Find peak listening hour
const peakHour = hourlyDistribution.indexOf(Math.max(...hourlyDistribution));
return {
totalPlays: recent.items.length,
topArtists,
peakListeningHour: peakHour,
hourlyDistribution,
};
}

// Get all followed artists using async iterator
const followedArtists = [];
for await (const artist of client.me.following.list({ type: "artist" })) {
followedArtists.push(artist);
}
console.log(`Following ${followedArtists.length} artists`);
// Follow artists
await client.me.following.follow({
type: "artist",
ids: "artistId1,artistId2",
});
// Unfollow artists
await client.me.following.unfollow({
type: "artist",
ids: "artistId1",
});
// Check if following specific artists
const isFollowing = await client.me.following.check({
type: "artist",
ids: "artistId1,artistId2",
});
// Returns [true, false]

Here’s a comprehensive example for managing user library operations using the SDK:

import Spotted from "spotted-ts";
const client = new Spotted({ bearerToken: userAccessToken });
// Get library statistics
async function getLibraryStats() {
// Get counts from first page of each resource
const [tracksPage, albumsPage, audiobooksPage] = await Promise.all([
client.me.tracks.list({ limit: 1 }),
client.me.albums.list({ limit: 1 }),
client.me.audiobooks.list({ limit: 1 }),
]);
// Get top artists for genre analysis
const topArtists = [];
for await (const artist of client.me.top.listArtists({
time_range: "long_term",
limit: 50,
})) {
topArtists.push(artist);
if (topArtists.length >= 50) break;
}
// Calculate genre distribution
const genreCounts = {};
topArtists.forEach((artist) => {
artist.genres.forEach((genre) => {
genreCounts[genre] = (genreCounts[genre] || 0) + 1;
});
});
const topGenres = Object.entries(genreCounts)
.sort((a, b) => b[1] - a[1])
.slice(0, 10)
.map(([genre]) => genre);
return {
savedTracksCount: tracksPage.total,
savedAlbumsCount: albumsPage.total,
savedAudiobooksCount: audiobooksPage.total,
topGenres,
topArtist: topArtists[0]?.name,
};
}
// Export library to JSON
async function exportLibrary() {
const tracks = [];
for await (const item of client.me.tracks.list()) {
tracks.push({
id: item.track.id,
name: item.track.name,
artist: item.track.artists[0].name,
album: item.track.album.name,
addedAt: item.added_at,
});
}
const albums = [];
for await (const item of client.me.albums.list()) {
albums.push({
id: item.album.id,
name: item.album.name,
artist: item.album.artists[0].name,
addedAt: item.added_at,
});
}
const audiobooks = [];
for await (const item of client.me.audiobooks.list()) {
audiobooks.push({
id: item.id,
name: item.name,
author: item.authors[0].name,
addedAt: item.added_at,
});
}
return { exportedAt: new Date().toISOString(), tracks, albums, audiobooks };
}
// Usage
const stats = await getLibraryStats();
console.log(`
Library Stats:
- ${stats.savedTracksCount} saved tracks
- ${stats.savedAlbumsCount} saved albums
- ${stats.savedAudiobooksCount} saved audiobooks
- Top artist: ${stats.topArtist}
- Top genres: ${stats.topGenres.slice(0, 3).join(", ")}
`);
// Export entire library
const backup = await exportLibrary();
console.log(
`Exported ${backup.tracks.length} tracks, ${backup.albums.length} albums, and ${backup.audiobooks.length} audiobooks`,
);

Example of syncing saved tracks between accounts using the SDK:

async function syncLibraries(sourceClient, targetClient, options = {}) {
const { dryRun = false } = options;
const results = { added: 0, skipped: 0 };
// Get all source tracks
const sourceTracks = [];
for await (const item of sourceClient.me.tracks.list()) {
sourceTracks.push(item.track.id);
}
// Check and sync in batches of 50
for (let i = 0; i < sourceTracks.length; i += 50) {
const batch = sourceTracks.slice(i, i + 50);
const savedStatus = await targetClient.me.tracks.check({
ids: batch.join(","),
});
const toSave = batch.filter((_, idx) => !savedStatus[idx]);
if (toSave.length > 0 && !dryRun) {
await targetClient.me.tracks.save({ ids: toSave });
}
results.added += toSave.length;
results.skipped += batch.length - toSave.length;
}
return results;
}