Skip to content

Adapt to the api.spotify.com API change effective 2026-03-09 - #241

Open
torrresagus wants to merge 1 commit into
watsonbox:masterfrom
tiltmeter-dev:fix/spotify-2026-web-api
Open

torrresagus wants to merge 1 commit into
watsonbox:masterfrom
tiltmeter-dev:fix/spotify-2026-web-api

Conversation

@torrresagus

Copy link
Copy Markdown

What changed in the API

Notice spotify-2026-02-development-mode about api.spotify.com's API describes this change, published 2026-02-11 and effective 2026-03-09. It is not signed by api.spotify.com: it was read from a file the person running Tiltmeter trusts.

The notice's summary:

Spotify's February 2026 Web API changes for Development Mode apps (without extended quota; existing
apps since March 9): playlist /tracks endpoints move to /items and the playlist field tracks becomes
items; batch fetches, top tracks, browse and markets are gone; saves go through /me/library;
popularity, followers, label, email, country and product are no longer returned. Source:
https://developer.spotify.com/documentation/web-api/tutorials/february-2026-migration-guide

GET https://api.spotify.com/v1/playlists/{playlist_id}/tracks

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to GET https://api.spotify.com/v1/playlists/{playlist_id}/items
breaking field_removed $.items[*].track — (absent), renamed to $.items[*].item

POST https://api.spotify.com/v1/playlists/{playlist_id}/tracks

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to POST https://api.spotify.com/v1/playlists/{playlist_id}/items

PUT https://api.spotify.com/v1/playlists/{playlist_id}/tracks

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to PUT https://api.spotify.com/v1/playlists/{playlist_id}/items

DELETE https://api.spotify.com/v1/playlists/{playlist_id}/tracks

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to DELETE https://api.spotify.com/v1/playlists/{playlist_id}/items

POST https://api.spotify.com/v1/users/{user_id}/playlists

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to POST https://api.spotify.com/v1/me/playlists

GET https://api.spotify.com/v1/users/{user_id}/playlists

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to GET https://api.spotify.com/v1/me/playlists

GET https://api.spotify.com/v1/users/{user_id}

Severity Change Path Before After
breaking endpoint_removed $ — (gone)

GET https://api.spotify.com/v1/albums

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to GET https://api.spotify.com/v1/albums/{id}

GET https://api.spotify.com/v1/artists

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to GET https://api.spotify.com/v1/artists/{id}

PUT https://api.spotify.com/v1/me/tracks

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to PUT https://api.spotify.com/v1/me/library

PUT https://api.spotify.com/v1/me/albums

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to PUT https://api.spotify.com/v1/me/library

DELETE https://api.spotify.com/v1/me/tracks

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to DELETE https://api.spotify.com/v1/me/library

DELETE https://api.spotify.com/v1/me/albums

Severity Change Path Before After
breaking endpoint_removed $ — (gone), moved to DELETE https://api.spotify.com/v1/me/library

GET https://api.spotify.com/v1/playlists/{playlist_id}

Severity Change Path Before After
breaking field_removed $.tracks — (absent), renamed to $.items
breaking field_removed $.tracks.items[*].track — (absent), renamed to $.items.items[*].item

GET https://api.spotify.com/v1/me/playlists

Severity Change Path Before After
breaking field_removed $.items[*].tracks — (absent), renamed to $.items[*].items

GET https://api.spotify.com/v1/tracks/{id}

Severity Change Path Before After
breaking field_removed $.popularity — (absent)
breaking field_removed $.available_markets — (absent)
breaking field_removed $.linked_from — (absent)

GET https://api.spotify.com/v1/albums/{id}

Severity Change Path Before After
breaking field_removed $.label — (absent)
breaking field_removed $.popularity — (absent)
breaking field_removed $.available_markets — (absent)
breaking field_removed $.album_group — (absent)

GET https://api.spotify.com/v1/artists/{id}

Severity Change Path Before After
breaking field_removed $.followers — (absent)
breaking field_removed $.popularity — (absent)

GET https://api.spotify.com/v1/me

Severity Change Path Before After
breaking field_removed $.country — (absent)
breaking field_removed $.followers — (absent)
breaking field_removed $.email — (absent)
breaking field_removed $.explicit_content — (absent)
breaking field_removed $.product — (absent)

GET https://api.spotify.com/v1/me/tracks

Severity Change Path Before After
breaking field_removed $.items[*].track.popularity — (absent)
breaking field_removed $.items[*].track.available_markets — (absent)

GET https://api.spotify.com/v1/shows/{id}

Severity Change Path Before After
breaking field_removed $.available_markets — (absent)
breaking field_removed $.publisher — (absent)

The notice's notes:

GET /v1/playlists/{playlist_id}/tracks: Playlist contents are only returned for playlists the user
owns or collaborates on.
DELETE /v1/playlists/{playlist_id}/tracks: The tracks member of the request body is renamed to
items.
POST /v1/users/{user_id}/playlists: Playlists are created for the current user.
GET /v1/users/{user_id}/playlists: Only the current user's playlists remain readable: other users'
playlists have no replacement.
GET /v1/users/{user_id}: GET /me returns the current user's profile; other users' profiles have no
replacement.
GET /v1/albums: Batch fetches are gone: request each id on its own.
GET /v1/artists: Batch fetches are gone: request each id on its own.
PUT /v1/me/tracks: Send Spotify URIs in a comma-separated uris query parameter (spotify:track:ID,
spotify:album:ID), at most 40, instead of ids.
PUT /v1/me/albums: Send Spotify URIs in a comma-separated uris query parameter (spotify:track:ID,
spotify:album:ID), at most 40, instead of ids.
DELETE /v1/me/tracks: Send Spotify URIs in a comma-separated uris query parameter (spotify:track:ID,
spotify:album:ID), at most 40, instead of ids.
DELETE /v1/me/albums: Send Spotify URIs in a comma-separated uris query parameter (spotify:track:ID,
spotify:album:ID), at most 40, instead of ids.

What this pull request changes

Playlist items are now read from item and the playlist's items href and total, and the artist and album lookups request one id at a time because the batch endpoints are gone. popularity, label and followers are now optional and exported as empty when missing. Mocks and tests use the new URLs and shapes. I haven't run the tests or the type checker.

  • src/components/PlaylistExporter.test.ts
  • src/components/PlaylistTable.test.tsx
  • src/components/SavedAlbumsExporter.ts
  • src/components/data/TracksAlbumData.ts
  • src/components/data/TracksArtistsData.ts
  • src/components/data/TracksBaseData.ts
  • src/components/data/TracksData.test.ts
  • src/mocks/handlers.ts
  • src/spotify.ts

Tests

sh -c 'pnpm install --frozen-lockfile --ignore-scripts --reporter=silent && CI=true npx vitest run' passed on these edits. It ran with network access and without secrets.

Left for a human

  • Per-id artist and album fetches fail the whole export if one id errors (e.g. 404); the old batch calls returned nulls instead. I left this as is.
  • Spotify now returns playlist contents only for playlists the user owns or collaborates on, so exports of other playlists may fail or come back empty.
  • The popularity and label columns will be empty because Spotify no longer returns those fields.
  • The scrub.test.ts fixtures still use the old batch artists URL. It only tests the scrubbing helper, so I left it alone.

This pull request was generated automatically by Tiltmeter from the unsigned notice spotify-2026-02-development-mode about api.spotify.com's API. An AI agent (Claude Code with claude-sonnet-5-5) wrote these edits; review them like any other change.

Playlist items are now read from `item` and the playlist's `items` href and total, and the artist and album lookups request one id at a time because the batch endpoints are gone. `popularity`, `label` and `followers` are now optional and exported as empty when missing. Mocks and tests use the new URLs and shapes. I haven't run the tests or the type checker.

Notice spotify-2026-02-development-mode about api.spotify.com's API describes the change. It was read from a file and is not signed by the provider.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant