A modern TypeScript client for the Spotify Web API.
@shadownine/foxify wraps Spotify's REST API in a grouped, autocomplete-friendly client:
import { createSpotifyClient } from "@shadownine/foxify";
const spotify = createSpotifyClient({
accessToken: process.env.SPOTIFY_ACCESS_TOKEN!,
});
const album = await spotify.albums.get("4aawyAB9vmqN3uQ7FjRGTy", {
market: "US",
});
console.log(album.name);The library is ESM-first, typed, fetch-based, and works in runtimes with Web Fetch APIs such as Bun, modern Node, browsers, and edge runtimes.
bun add @shadownine/foxifynpm install @shadownine/foxifyCreate a client with an access token:
import { createSpotifyClient } from "@shadownine/foxify";
const spotify = createSpotifyClient({
accessToken: "spotify-access-token",
});
const profile = await spotify.users.getCurrentProfile();
const playlists = await spotify.playlists.getCurrentUserPlaylists({ limit: 10 });Or provide a token getter if your app refreshes tokens:
const spotify = createSpotifyClient({
async getAccessToken() {
return await loadFreshSpotifyAccessToken();
},
});You can also inject fetch, change the base URL for tests, or enable simple 429 retry behavior:
const spotify = createSpotifyClient({
accessToken: "token",
fetch: globalThis.fetch,
baseUrl: "https://api.spotify.com/v1",
autoRetry: { retries: 1 },
});Endpoints are grouped by Spotify resource:
spotify.albums.get(id);
spotify.artists.getTopTracks(id, { market: "US" });
spotify.playlists.addItems(playlistId, ["spotify:track:..."]);
spotify.player.pause({ device_id: "device-id" });
spotify.search.items("Daft Punk", ["artist", "track"]);
spotify.users.follow("artist", ["artist-id"]);Available groups:
spotify.albums
spotify.artists
spotify.audiobooks
spotify.categories
spotify.chapters
spotify.episodes
spotify.genres
spotify.library
spotify.markets
spotify.player
spotify.playlists
spotify.search
spotify.shows
spotify.tracks
spotify.usersThere is also a raw request escape hatch:
const result = await spotify.request("GET", "/me/player", {
query: { market: "US" },
});@shadownine/foxify includes helpers for common Spotify OAuth flows.
import {
createAuthorizeUrl,
createPkceChallenge,
exchangeAuthorizationCode,
} from "@shadownine/foxify";
const pkce = await createPkceChallenge();
const authorizationUrl = createAuthorizeUrl({
clientId: "spotify-client-id",
redirectUri: "https://your-app.test/callback",
scopes: ["user-read-email", "playlist-read-private"],
state: "csrf-state",
codeChallenge: pkce.codeChallenge,
});
// Redirect the user to authorizationUrl, then exchange the callback code:
const tokens = await exchangeAuthorizationCode({
clientId: "spotify-client-id",
code: "callback-code",
codeVerifier: pkce.codeVerifier,
redirectUri: "https://your-app.test/callback",
});import { refreshAccessToken } from "@shadownine/foxify";
const tokens = await refreshAccessToken({
clientId: "spotify-client-id",
clientSecret: "spotify-client-secret",
refreshToken: "refresh-token",
});import { clientCredentialsToken } from "@shadownine/foxify";
const tokens = await clientCredentialsToken({
clientId: "spotify-client-id",
clientSecret: "spotify-client-secret",
});Runnable examples live in examples/. After installing dependencies,
set the Spotify environment variables needed by the example you want, then run:
bun run example:client-credentials-search
bun run example:oauth-login
bun run example:user-profile-playlistsconst results = await spotify.search.items("lofi", ["playlist", "track"], {
market: "US",
limit: 10,
});await spotify.tracks.save(["spotify-track-id"]);
const saved = await spotify.tracks.checkSaved(["spotify-track-id"]);const playlist = await spotify.playlists.create({
name: "Weekend Flight Deck",
description: "Fresh tracks for late-night building.",
public: false,
});
await spotify.playlists.addItems(playlist.id, [
"spotify:track:...",
]);await spotify.player.startResumePlayback({
uris: ["spotify:track:..."],
});
await spotify.player.setVolume(65);
await spotify.player.pause();Spotify Web API errors throw SpotifyApiError:
import { SpotifyApiError } from "@shadownine/foxify";
try {
await spotify.tracks.get("missing-track");
} catch (error) {
if (error instanceof SpotifyApiError) {
console.error(error.status);
console.error(error.message);
console.error(error.retryAfter);
}
}OAuth helper failures throw SpotifyOAuthError:
import { SpotifyOAuthError } from "@shadownine/foxify";
try {
await refreshAccessToken({ clientId, refreshToken });
} catch (error) {
if (error instanceof SpotifyOAuthError) {
console.error(error.message);
}
}bun install
bun run typecheck
bun run test
bun run buildBuild output is generated with tsdown into dist/.
The plain foxify npm name is already taken, so this package is configured as @shadownine/foxify.
Before publishing, make sure the npm scope is yours. If you use a different npm username or org, update package.json and the import examples in this README.
Dry-run the package contents:
bun run pack:dryPublish from a version tag:
npm version patch
git push --follow-tagsPushing the tag starts the workflow and publishes the package to npm.
Manual fallback:
npm publish --access public- Spotify access tokens are required for Web API requests.
- Query arrays are serialized as Spotify-style comma lists.
- Empty
204responses resolve toundefined. - Deprecated playlist item endpoints are still available with
Deprecatedsuffixes for compatibility.