February 2026 Web API Dev Mode Changes - Migration Guide

This guide is here to help developers with existing apps migrate to the updated Web API following the February 2026 announcement. It covers what's changing, who is affected, and provides step-by-step migration paths for common use cases.

Who is affected?

Extended Quota Mode apps: No migration required. Apps in extended quota mode are not affected by any of the changes described in this guide — all existing endpoints, fields, and behaviors remain unchanged. You can optionally adopt the new generic library endpoints (PUT/DELETE /me/library) but your existing integrations will remain fully functional.

Development Mode apps: This guide is for you. Read on for the full details of what's changing and how to update your app.

Timeline

DateWhat happens
February 11, 2026New Development Mode apps are created with new restrictions
March 9, 2026Existing Development Mode apps are migrated to new restrictions

Account and App Limit Changes

Premium Requirement

All Development Mode apps require the app owner to have an active Spotify Premium subscription. If the owner's Premium subscription lapses, the app will stop working. It will resume functioning once the owner resubscribes.

App Limits

RequirementNew apps
Client IDs per developer1
Users per app5

Existing apps are grandfathered: If you already have multiple Client IDs or more than 5 users, you will retain them. These limits only restrict what you can create or add going forward.


Endpoint Changes

Library Management

The entity type specific save, remove, follow, and unfollow endpoints have been replaced with generic library endpoints that work with Spotify URIs.

Saving items and following

Before:


_10
PUT /me/tracks
_10
PUT /me/albums
_10
PUT /me/episodes
_10
PUT /me/shows
_10
PUT /me/audiobooks
_10
PUT /me/following
_10
PUT /playlists/{id}/followers

After:


_10
PUT /me/library

The new PUT /me/library endpoint accepts Spotify URIs instead of IDs, allowing you to save or follow any supported content type in a single request. Appropriate scopes need to be passed depending on the entities being saved.

Removing items and unfollowing

Before:


_10
DELETE /me/tracks
_10
DELETE /me/albums
_10
DELETE /me/episodes
_10
DELETE /me/shows
_10
DELETE /me/audiobooks
_10
DELETE /me/following
_10
DELETE /playlists/{id}/followers

After:


_10
DELETE /me/library

Like the save endpoint, DELETE /me/library accepts Spotify URIs instead of IDs.

Checking if items are saved

The individual "contains" endpoints have been replaced with a single generic endpoint.

Before:


_10
GET /me/tracks/contains
_10
GET /me/albums/contains
_10
GET /me/episodes/contains
_10
GET /me/shows/contains
_10
GET /me/audiobooks/contains
_10
GET /me/following/contains
_10
GET /playlists/{id}/followers/contains

After:


_10
GET /me/library/contains

The new GET /me/library/contains endpoint accepts Spotify URIs instead of IDs.


Playlist Endpoint Renames

The playlist track management endpoints have been renamed from /tracks to /items:

Removed endpointReplacementComment
POST /playlists/{id}/tracksPOST /playlists/{id}/items
GET /playlists/{id}/tracksGET /playlists/{id}/itemsOnly available for playlists the user owns or collaborates on. Fields have been renamed in the response, see Playlist field changes for response changes.
DELETE /playlists/{id}/tracksDELETE /playlists/{id}/itemsParameter tracks renamed to items
PUT /playlists/{playlist_id}/tracksPUT /playlists/{id}/items

Batch/Bulk Fetch Endpoints (Removed)

The following batch endpoints are no longer available. Fetch items individually instead.

Removed endpointReplacement
GET /tracksGET /tracks/{id} (one request per track)
GET /albumsGET /albums/{id}
GET /artistsGET /artists/{id}
GET /episodesGET /episodes/{id}
GET /showsGET /shows/{id}
GET /audiobooksGET /audiobooks/{id}
GET /chaptersGET /chapters/{id}

Migration example:


_11
// Before: Batch fetch
_11
const response = await fetch('/v1/tracks?ids=id1,id2,id3');
_11
const { tracks } = await response.json();
_11
_11
// After: Individual fetches
_11
const trackIds = ['id1', 'id2', 'id3'];
_11
const tracks = await Promise.all(
_11
trackIds.map(id =>
_11
fetch(`/v1/tracks/${id}`).then(r => r.json())
_11
)
_11
);

Browse and Artist Endpoints (Removed)

Removed endpointDescription
GET /browse/new-releasesNew album releases
GET /browse/categoriesBrowse categories list
GET /browse/categories/{id}Single category details
GET /artists/{id}/top-tracksArtist's top tracks by country

Other User Data (Removed)

Removed endpointReplacement
GET /users/{id}No replacement - use GET /me for current user
GET /users/{id}/playlistsNo replacement - use GET /me/playlists for current user
POST /users/{user_id}/playlistsPOST /me/playlists
GET /marketsNo replacement

Field Changes by Content Type

The following field changes apply across all endpoints that return these content types.

Removed fields

The following fields have been removed from response objects. Update your code to handle their absence gracefully.

Track

available_markets, external_ids, linked_from, popularity

Album

album_group, available_markets, external_ids, label, popularity

Artist

followers, popularity

User (from GET /me)

country, email, explicit_content, followers, product

Show

available_markets, publisher

Audiobook / Chapter

available_markets, publisher (audiobook only)

Renamed fields

Playlist

The tracks field has been renamed:

BeforeAfter
tracksitems
tracks.tracksitems.items
tracks.tracks.trackitems.items.item

Important: Playlist contents (items) are only returned for playlists the user owns or collaborates on. For other playlists, only metadata is returned and the items field will be absent from the response.


Migration Checklist

Use this checklist to ensure your app is ready:

  • Account: Ensure the app owner has Spotify Premium
  • Library endpoints: Replace content-specific save/remove/follow/unfollow calls with PUT/DELETE /me/library
  • Contains checks: Replace content-specific "contains" calls with GET /me/library/contains
  • Batch fetches: Replace batch endpoints with individual fetch calls
  • Browse and artist endpoints: Remove features using browse categories, new releases, or artist top tracks
  • Other users: Remove features that fetch other users' profiles or playlists
  • Playlist endpoints: Update /playlists/{id}/tracks calls to /playlists/{id}/items
  • Playlist handling: Update code for the tracksitems field rename
  • Removed fields: Handle missing fields gracefully (check for undefined/null)
  • Global recommendation: Limit API calls to avoid hitting rate limits

Common Migration Patterns

Pattern 1: Updating library save calls


_14
// Before
_14
await spotify.put('/me/tracks', { ids: ['trackId1', 'trackId2'] });
_14
await spotify.put('/me/albums', { ids: ['albumId1'] });
_14
await spotify.put('/me/following', { ids: ['artistId1'], type: 'artist' });
_14
_14
// After — all entity types in a single call
_14
await spotify.put('/me/library', {
_14
uris: [
_14
'spotify:track:trackId1',
_14
'spotify:track:trackId2',
_14
'spotify:album:albumId1',
_14
'spotify:artist:artistId1'
_14
]
_14
});

Pattern 2: Handling missing popularity field


_10
// Before
_10
const sortedTracks = tracks.sort((a, b) => b.popularity - a.popularity);
_10
_10
// After - popularity is no longer available
_10
// Consider alternative sorting criteria or remove this feature
_10
const sortedTracks = tracks.sort((a, b) =>
_10
a.name.localeCompare(b.name)
_10
);

Pattern 3: Handling playlist field rename


_12
// Before
_12
const trackCount = playlist.tracks.total;
_12
const firstTrack = playlist.tracks.items[0].track;
_12
_12
// After
_12
const trackCount = playlist.items?.total ?? 0;
_12
const firstTrack = playlist.items?.items?.[0]?.item;
_12
_12
// Note: items may be undefined for playlists you don't own
_12
if (!playlist.items) {
_12
console.log('Track details not available for this playlist');
_12
}


Need Help?