@kody/spotify
README.md
166 lines · 9.3 KB · Markdown@kody/spotify
Official Spotify icon (green circle with three sound waves) from Spotify brand assets.
Intent
Provide reusable, account-agnostic Spotify helpers so Kody can read a connected profile, search the catalog, manage library and playlists, list devices, and control playback.
This listing is meant to be forked. After you fork, connect your Spotify developer app and run ./get-profile on your copy. Do not treat the live @kody/spotify integration as yours. There are no hard-coded personal playlists or account aliases.
When To Use
- Read the connected user's profile, recently played tracks, top items, or liked songs
- Search tracks, albums, artists, or playlists
- List playlists and devices, then start or transfer playback
- Preview playlist writes and playback changes with
dryRun: true
Do not use this package as a Spotify app that other people log into. Spotify Development Mode is for the app owner (and up to five registered users).
Required setup
Spotify has no built-in Kody platform app. You create a personal app in the Spotify developer dashboard and connect it with authorization code + PKCE. PKCE is a public-client flow, so no client secret is stored.
Costs and limits (since February 2026)
- Development Mode requires the app owner to hold Spotify Premium. If Premium lapses, the app stops working until it is restored.
- Developers can create up to 25 client IDs; all Development Mode apps share one API quota budget.
- Refresh tokens expire six months after the original authorization (refreshing an access token does not extend this). Reconnect about twice a year.
- Some Web API endpoints are unavailable in Development Mode. See the February 2026 migration guide.
- Extended Quota Mode is only for organizations with at least 250k monthly active users. Personal use stays in Development Mode.
Create the Spotify app
- Open the Spotify developer dashboard and click Create app.
- Set the redirect URI to exactly
https://kody.codes/connect/oauth. Spotify requires HTTPS and rejectslocalhost. - Select Web API, accept the developer terms, and copy the client ID. With PKCE you do not need the client secret.
- Only if someone other than the app owner will authorize: add them under User Management (max 5).
Connect to Kody
flow=pkce is Kody's default, so the link needs no flow parameter and no client secret. Open this URL while signed in to Kody, paste the client ID into the setup form, then authorize.
Guide starter (read-mostly):
https://kody.codes/connect/oauth?provider=spotify&authorizeUrl=https%3A%2F%2Faccounts.spotify.com%2Fauthorize&tokenUrl=https%3A%2F%2Faccounts.spotify.com%2Fapi%2Ftoken&scopes=user-read-recently-played%20user-read-playback-state%20user-library-read&allowedHosts=api.spotify.comDecoded: authorize https://accounts.spotify.com/authorize, token https://accounts.spotify.com/api/token, scopes user-read-recently-played user-read-playback-state user-library-read, host api.spotify.com.
Recommended for this package (adds profile/top/playlist reads plus playback and library/playlist writes):
https://kody.codes/connect/oauth?provider=spotify&authorizeUrl=https%3A%2F%2Faccounts.spotify.com%2Fauthorize&tokenUrl=https%3A%2F%2Faccounts.spotify.com%2Fapi%2Ftoken&scopes=user-read-recently-played%20user-read-playback-state%20user-read-currently-playing%20user-library-read%20playlist-read-private%20user-top-read%20user-modify-playback-state%20playlist-read-collaborative%20playlist-modify-public%20playlist-modify-private%20user-library-modify&allowedHosts=api.spotify.comChanging scopes means reconnecting. Reconnect later at https://kody.codes/connect/oauth?provider=spotify (or ?provider=spotify-work).
Access tokens last one hour. Use createAuthenticatedFetch — do not cache raw tokens. Refresh tokens hard-expire six months after the original authorization.
Multiple accounts
Every export accepts optional integration (Kody OAuth connection name). Default is spotify. Extra identities use spotify-* names — for example spotify-work or spotify-kids. Change provider in the connect URL, then pass that name on every call.
import getProfile from 'kody:@kody/spotify/get-profile'
export default async function main() {
return await getProfile({ integration: 'spotify-work' })
}Do not hard-code personal aliases such as personal, family, or kent. Those values throw.
Device targeting
Prefer human selectors so opaque Spotify Connect device ids never cross the agent tool boundary:
- Happy path — omit device selectors and let Spotify use the active Connect device.
- Named device — pass
deviceName(case-insensitive substring) and optionaldeviceType(Speaker,Computer, …). - Advanced —
deviceIdstill works, but agents should avoid copying ids fromget-devices.
Mutations and dryRun
Playlist writes (create-playlist, add-tracks-to-playlist, remove-tracks-from-playlist, follow-playlist, save-tracks) and playback helpers (play-context, play-pause, skip, seek, set-repeat, set-shuffle, set-volume, add-to-queue, transfer-playback) accept dryRun: true. A dry run validates input and returns a preview; Spotify is not written.
import createPlaylist from 'kody:@kody/spotify/create-playlist'
export default async function main() {
return await createPlaylist({ name: 'Focus', public: false, dryRun: true })
}Playback control acts on real devices. Use dryRun: true first when the user has not confirmed a live change.
Smoke test
After connect or reconnect, import a helper from this package (not packages.invoke with kodyId: 'spotify'):
import getProfile from 'kody:@kody/spotify/get-profile'
export default async function main() {
return await getProfile()
}Success includes id and product for the connected user. product should be premium for Development Mode.
Low-level equivalent (execute exploration only):
import { createAuthenticatedFetch } from 'kody:runtime'
export default async function main() {
const spotifyFetch = await createAuthenticatedFetch('spotify')
const response = await spotifyFetch('https://api.spotify.com/v1/me')
if (!response.ok) {
throw new Error('Spotify smoke test failed: ' + response.status + ' ' + (await response.text()))
}
const me = (await response.json()) as { display_name?: string; id: string; product?: string }
return { id: me.id, displayName: me.display_name, product: me.product }
}Exports
kody:@kody/spotify— package overview, PKCE connect URLs, and setup noteskody:@kody/spotify/accounts— default integration andspotify-*namingkody:@kody/spotify/get-profile— connected user profilekody:@kody/spotify/search— catalog searchkody:@kody/spotify/get-track— track detailskody:@kody/spotify/get-saved-tracks— liked/saved library trackskody:@kody/spotify/save-tracks— save tracks (dryRunsupported)kody:@kody/spotify/get-recently-played— listening historykody:@kody/spotify/get-top-items— top tracks or artistskody:@kody/spotify/get-recommendations— seed recommendations (may be unavailable in Development Mode)kody:@kody/spotify/list-playlists— the user's playlistskody:@kody/spotify/get-playlist— playlist summary and track previewkody:@kody/spotify/get-all-playlist-tracks— paginate every playlist trackkody:@kody/spotify/create-playlist— create a playlist (dryRunsupported)kody:@kody/spotify/add-tracks-to-playlist— add tracks (dryRunsupported)kody:@kody/spotify/remove-tracks-from-playlist— remove tracks (dryRunsupported)kody:@kody/spotify/follow-playlist— follow a playlist (dryRunsupported)kody:@kody/spotify/get-devices— Connect devices (usenamewithdeviceName)kody:@kody/spotify/playback-state— current playback snapshotkody:@kody/spotify/get-queue— current queuekody:@kody/spotify/play-context— start a context (dryRunsupported)kody:@kody/spotify/play-pause— toggle play/pause (dryRunsupported)kody:@kody/spotify/skip— next or previous (dryRunsupported)kody:@kody/spotify/seek— seek (dryRunsupported)kody:@kody/spotify/set-repeat/set-shuffle/set-volume(dryRunsupported)kody:@kody/spotify/add-to-queue— queue a URI (dryRunsupported)kody:@kody/spotify/transfer-playback— move playback (dryRunsupported)
Share this listing as https://kody.codes/@kody/spotify.
Troubleshooting
INVALID_CLIENT: Invalid redirect URI: the dashboard redirect URI must be exactlyhttps://kody.codes/connect/oauth.403 User not registered in the Developer Dashboard: add the user under User Management, or have them create their own app.403on a specific endpoint: that endpoint may be unavailable in Development Mode.- App failing across the board: check that the app owner's Premium subscription is active.
401after about an hour: access tokens expire hourly. UsecreateAuthenticatedFetch.invalid_grantabout six months after connecting: reconnect at/connect/oauth?provider=spotify.