Accessing the Spotify API requires configuring OAuth2. It is not overly complex, although Spotify adds a few extra steps compared to providers like GitHub or Google. These differences cost me hours of debugging due to browser caching and redirect quirks, so Iβm documenting the full workflow to save you time.
In this guide we will
- create a Spotify developer application
- configure redirect and callback URLs
- obtain the client ID and client secret
- implement refresh token generation
- use the access token to make authenticated API calls
The examples use Next.js for convenience, though the concepts translate cleanly to Node.js or any backend framework. You only need minimal familiarity with Next.js to follow along.
Spotify Developer Account
You will need a Spotify developer account to create an OAuth2 application. Navigate to the developer dashboard and click βCreate appβ, then fill in the fields:
- Name: any label for your integration
- Description: a short explanation
- Website: optional for development
- Redirect URIs: the callback endpoint in your project
If you don't have an API setup yet, I'll be showing how to implement the API routes here.
Most implementations use something like:
1/api/spotify/callback
Important: Spotify rejects localhost
This is the first part which is new for most. Setting the url to http://localhost:3000 will not work.
Instead register the loopback IP from:
1http://127.0.0.1:3000/api/spotify/callback
Both map to your machine, but Spotify validates them differently. localhost is a hostname that depends on DNS resolution. 127.0.0.1 is an explicit IP address and always resolves to the local interface, which is why Spotify accepts it. Make sure your OAuth redirect in your code matches exactly what you register in the developer dashboard.
Last question is: Which API/SDKs are you planning to use? Fill in your use case (most likely web) and click "Save". After having pressed save you'll see your client ID, and secret if you press "View client secret". Copy these, and add to your .envor .env.local file like so:
1 2SPOTIFY_CLIENT_ID=your-client-id SPOTIFY_CLIENT_SECRET=your-client-secret
It doesn't matter whether you use quotes around your environment variable values. All of these work:
SPOTIFY_CLIENT_ID=valueSPOTIFY_CLIENT_ID="value"SPOTIFY_CLIENT_ID='value'
Generate Your .env Block
Use the interactive form below to build your environment variables. Paste your credentials from the Spotify Dashboard and copy the generated block directly into your project.
Environment Generator
Paste your credentials to generate your .env block
Tip: Create an app in the Spotify Dashboard, then copy the Client ID and Secret below.
Don't have one? Use the Token Generator
# Spotify Integration
SPOTIFY_CLIENT_ID="your_client_id"
SPOTIFY_CLIENT_SECRET="your_client_secret"
SPOTIFY_REDIRECT_URI="http://127.0.0.1:3000/api/spotify/callback"
SPOTIFY_REFRESH_TOKEN="your_refresh_token"If your API calls for different variable names than these two, obviously change those. Next we will configure the authorization code flow, exchanging the temporary code for both an access token and a refresh token.
API Routes
Now you'll have to implement the API route which you registered in the developer dashboard. Like I mentioned this implementation is following Next.js but you should just register an api in your desired framework.
Create the api route
1 2touch src/app/api/spotify/callback/route.ts ## or app/api/spotify/callback/route.ts if no src dir
And insert your improved GET request handler:
- Environment validation at startup fails fast if credentials are missing
- Helper function reduces URL construction duplication
- Better error handling with fallbacks for JSON parsing
- Consistent redirect URI usage from environment variables
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64import { NextResponse } from 'next/server'; import { NextRequest } from 'next/server'; export const dynamic = 'force-dynamic'; const SPOTIFY_ACCOUNTS_BASE = 'https://accounts.spotify.com'; export async function GET(request: NextRequest) { try { const { searchParams } = new URL(request.url); const code = searchParams.get('code'); const error = searchParams.get('error'); if (error) { return NextResponse.redirect(new URL('/?error=' + error, request.url)); } if (!code) { return NextResponse.redirect(new URL('/?error=no_code', request.url)); } const clientId = process.env.SPOTIFY_CLIENT_ID; const clientSecret = process.env.SPOTIFY_CLIENT_SECRET; const redirectUri = process.env.SPOTIFY_REDIRECT_URI; if (!clientId || !clientSecret) { return NextResponse.redirect(new URL('/?error=missing_credentials', request.url)); } const authString = `${clientId}:${clientSecret}`; const base64Auth = Buffer.from(authString).toString('base64'); const tokenResponse = await fetch(`${SPOTIFY_ACCOUNTS_BASE}/api/token`, { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${base64Auth}` }, body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: redirectUri || 'http://127.0.0.1:3000/api/spotify/callback' }) }); if (!tokenResponse.ok) { const errorData = await tokenResponse.json(); return NextResponse.redirect(new URL(`/?error=token_exchange_failed&details=${errorData.error_description || errorData.error}`, request.url)); } const tokenData = await tokenResponse.json(); const redirectUrl = new URL('/dev/spotify', request.url); redirectUrl.searchParams.set('success', 'true'); redirectUrl.searchParams.set('refresh_token', tokenData.refresh_token); redirectUrl.searchParams.set('access_token', tokenData.access_token); return NextResponse.redirect(redirectUrl); } catch (error) { console.error('Error in Spotify callback:', error); return NextResponse.redirect(new URL('/?error=unknown_error', request.url)); } }
Interactive Setup Tool
I've built a custom Spotify Token Generator tool specifically for this workflow. It validates your credentials and generates the .env block for you automatically. Use it to skip the manual work!
Spotify Developer Account
Making API Calls
Once you have your tokens, you can make authenticated requests to the Spotify API. Here's how to use the access token and refresh it when needed:
Using the Access Token
1 2 3 4 5 6 7 8 9 10 11 12 13async function fetchSpotifyData(accessToken: string) { const response = await fetch('https://api.spotify.com/v1/me/player/currently-playing', { headers: { 'Authorization': `Bearer ${accessToken}` } }); if (!response.ok) { throw new Error(`Spotify API error: ${response.status}`); } return await response.json(); }
Refreshing the Access Token
Access tokens expire after 1 hour, so you'll need to refresh them using the refresh token:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62export async function POST(request: NextRequest) { try { const { refresh_token } = await request.json() if (!refresh_token) { return NextResponse.json( { error: 'Refresh token required' }, { status: 400 } ) } const clientId = process.env.SPOTIFY_CLIENT_ID const clientSecret = process.env.SPOTIFY_CLIENT_SECRET if (!clientId || !clientSecret) { return NextResponse.json( { error: 'Missing credentials' }, { status: 500 } ) } const authString = `${clientId}:${clientSecret}` const base64Auth = Buffer.from(authString).toString('base64') const response = await fetch('https://accounts.spotify.com/api/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Basic ${base64Auth}` }, body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token }) }) if (!response.ok) { const errorData = await response.json() return NextResponse.json( { error: 'Token refresh failed', details: errorData.error_description || errorData.error }, { status: 400 } ) } const data = await response.json() return NextResponse.json({ access_token: data.access_token, expires_in: data.expires_in, refresh_token: data.refresh_token || refresh_token // Spotify may return a new refresh token }) } catch (error) { console.error('Error refreshing token:', error) return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ) } }
Common Pitfalls
1. Browser Caching Issues
Spotify's OAuth flow can get cached by browsers, especially during development. Use these strategies to avoid cache-related issues:
- Always use incognito/private browsing for testing
- Add
show_dialog=trueto force the consent screen - Clear browser cookies and localStorage if needed
2. Mismatched Redirect URIs
This is the most common issue. The redirect URI in your code must exactly match what's registered in the Spotify Developer Dashboard:
- β
http://127.0.0.1:3000/api/spotify/callback - β
http://localhost:3000/api/spotify/callback
3. Scope Issues
Make sure you request all necessary scopes upfront. Spotify doesn't allow incremental scope requests.
4. Token Storage
Never store tokens in client-side code or commit them to version control:
- β Store refresh tokens securely on the server
- β Use environment variables for client credentials
- β Don't store tokens in localStorage or cookies
Production Considerations
For production deployments:
- Use HTTPS: All redirect URIs must use HTTPS in production
- Secure Storage: Use a database or secure key management for refresh tokens
- Rate Limiting: Implement rate limiting for your API endpoints
- Error Handling: Provide user-friendly error messages
- Token Rotation: Implement refresh token rotation for better security
Complete Example
Here's a complete example of a Spotify API service:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55class SpotifyAPIService { private accessToken: string | null = null private tokenExpiry: number = 0 async getCurrentlyPlaying( refreshToken: string ): Promise<Record<string, unknown>> { const accessToken = await this.getAccessToken(refreshToken) const response = await fetch( 'https://api.spotify.com/v1/me/player/currently-playing', { headers: { Authorization: `Bearer ${accessToken}` } } ) if (response.status === 204) { return null // No track currently playing } if (!response.ok) { throw new Error( `Failed to fetch currently playing: ${response.status}` ) } return await response.json() } private async getAccessToken(refreshToken: string): Promise<string | null> { // Return cached token if still valid if (this.accessToken && Date.now() < this.tokenExpiry) { return this.accessToken } // Refresh the token const response = await fetch('/api/spotify/refresh', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ refresh_token: refreshToken }) }) if (!response.ok) { throw new Error('Failed to refresh access token') } const data = await response.json() this.accessToken = data.access_token this.tokenExpiry = Date.now() + data.expires_in * 1000 return this.accessToken } }
Conclusion
Setting up Spotify OAuth2 requires attention to detail, especially with redirect URIs and token management. The key takeaways are:
- Always use
127.0.0.1instead oflocalhostfor development - Store refresh tokens securely and handle token expiration
- Implement proper error handling and user feedback
- Test thoroughly in different browsers and environments
With this setup, you can now integrate Spotify's rich API into your applications, from music players to analytics dashboards.
Try It Live
Once you have your access token from the Token Generator, use the API explorer below to test real Spotify API calls directly from this page:
API Explorer
Test Spotify endpoints with your access token
Get one from the Token Generator β expires in 1 hour
Ready to build more? Check out my other guides on API integration patterns and Next.js authentication.