Retries and Timeouts
The Spotify SDK includes built-in retry logic with exponential backoff and jitter to handle transient errors, rate limits, and network issues automatically.
Overview
The SDK automatically retries requests that fail due to:
Connection errors - Network connectivity issues
Timeout exceptions - Requests that exceed the timeout limit
Rate limit responses (429) - Respects the Retry-After header from Spotify
Server errors (5xx) - Temporary Spotify server issues
Client errors (4xx) like NotFoundError, BadRequestError, and AuthenticationError are not retried automatically, as they typically indicate an issue with the request that won’t be resolved by retrying.
Default Retry Configuration
Both SpotifyClient and AsyncSpotifyClient use these default retry settings:
Setting Default Value Description max_retries3Maximum number of retry attempts INITIAL_BACKOFF0.5 secondsStarting backoff delay MAX_BACKOFF8.0 secondsMaximum backoff delay BACKOFF_MULTIPLIER2Multiplier for exponential backoff
These values are defined in the AsyncBaseClient class (src/spotify_sdk/_async/_base_client.py:28-32):
class AsyncBaseClient :
# Retry configuration
MAX_RETRIES = 3
INITIAL_BACKOFF = 0.5
MAX_BACKOFF = 8.0
BACKOFF_MULTIPLIER = 2
Configuring Max Retries
You can customize the maximum number of retries when initializing the client:
Sync Client
Async Client
Disable Retries
from spotify_sdk import SpotifyClient
client = SpotifyClient(
access_token = "your-access-token" ,
max_retries = 5 , # Increase to 5 retry attempts
)
Exponential Backoff with Jitter
The SDK uses exponential backoff with jitter to avoid overwhelming the API and prevent thundering herd problems.
How It Works
The backoff time doubles with each retry attempt:
Attempt 1: 0.5 seconds
Attempt 2: 1.0 seconds
Attempt 3: 2.0 seconds
Attempt 4: 4.0 seconds
Attempt 5: 8.0 seconds (capped at MAX_BACKOFF)
A random multiplier between 0.5 and 1.0 is applied to prevent synchronized retries from multiple clients.
The client waits for the calculated backoff time before retrying the request.
The backoff calculation is implemented in _calculate_backoff() (src/spotify_sdk/_async/_base_client.py:221-228):
def _calculate_backoff ( self , attempt : int ) -> float :
"""Calculate backoff time with jitter."""
backoff = min (
self . INITIAL_BACKOFF * ( self . BACKOFF_MULTIPLIER ** attempt),
self . MAX_BACKOFF ,
)
# Add jitter (0.5 to 1.0 multiplier)
return backoff * ( 0.5 + random.random() * 0.5 )
Example Backoff Times
Here’s what actual backoff times might look like with jitter:
Attempt Base Delay With Jitter (range) 1 0.5s 0.25s - 0.5s 2 1.0s 0.5s - 1.0s 3 2.0s 1.0s - 2.0s 4 4.0s 2.0s - 4.0s 5 8.0s 4.0s - 8.0s
Jitter helps distribute retry attempts over time, reducing the likelihood of multiple clients retrying simultaneously and causing another spike in traffic.
Rate Limit Handling
When Spotify returns a 429 (rate limit) response, the SDK respects the Retry-After header provided by the API.
Rate Limit Retry Logic
The retry logic for rate limits (src/spotify_sdk/_async/_base_client.py:146-154):
except RateLimitError as e:
last_exception = e
if attempt < retries:
sleep_time = e.retry_after or self ._calculate_backoff(
attempt
)
await anyio.sleep(sleep_time)
continue
raise
The SDK will:
Use the Retry-After value from the response header if available
Fall back to exponential backoff if no Retry-After header is present
Retry the request after waiting
Raise RateLimitError if all retries are exhausted
Handling Rate Limits in Your Code
from spotify_sdk import SpotifyClient, RateLimitError
client = SpotifyClient( access_token = "your-access-token" )
try :
album = client.albums.get( "5K79FLRUCSysQnVESLcTdb" )
except RateLimitError as e:
print ( f "Rate limited! Retry after { e.retry_after } seconds" )
print ( f "Status: { e.status_code } " )
print ( f "Message: { e.message } " )
Timeout Configuration
The SDK allows you to configure request timeouts at the client level and per-request.
Client-Level Timeout
Set a default timeout for all requests:
from spotify_sdk import SpotifyClient
client = SpotifyClient(
access_token = "your-access-token" ,
timeout = 60.0 , # 60 second timeout for all requests
)
Per-Request Timeout
The underlying request() method supports per-request timeout overrides (src/spotify_sdk/_async/_base_client.py:90-110):
async def request (
self ,
method : str ,
path : str ,
params : dict[ str , Any] | None = None ,
json : dict[ str , Any] | None = None ,
headers : dict[ str , str ] | None = None ,
content : str | bytes | None = None ,
timeout : float | None = None , # Override timeout per request
max_retries : int | None = None ,
) -> Any:
Timeout exceptions are automatically retried up to max_retries times. If you’re experiencing consistent timeouts, consider increasing the timeout value rather than just increasing retries.
Retry Flow Diagram
Here’s how the retry logic works:
Request Attempt
|
v
[Make API Call]
|
+---> Success? --> Return Response
|
+---> Connection/Timeout Error?
| |
| v
| [Calculate Backoff]
| |
| v
| [Sleep with Jitter]
| |
| v
| Attempts < max_retries? --> Retry Request
| |
| v
| Raise SpotifyError
|
+---> Rate Limit (429)?
| |
| v
| [Use Retry-After or Backoff]
| |
| v
| [Sleep]
| |
| v
| Attempts < max_retries? --> Retry Request
| |
| v
| Raise RateLimitError
|
+---> Server Error (5xx)?
| |
| v
| [Calculate Backoff]
| |
| v
| [Sleep with Jitter]
| |
| v
| Attempts < max_retries? --> Retry Request
| |
| v
| Raise ServerError
|
+---> Client Error (4xx)? --> Raise Specific Error
Which Errors Trigger Retries
Retried Automatically
These errors are automatically retried:
Connection Errors
Timeout Exceptions
Rate Limits (429)
Server Errors (5xx)
import httpx
# These are caught and retried
try :
response = await client.request( "GET" , "/albums/123" )
except httpx.ConnectError:
# Automatically retried
pass
Not Retried
These errors are not retried automatically:
from spotify_sdk import (
AuthenticationError, # 401 - Invalid token
BadRequestError, # 400 - Invalid request
ForbiddenError, # 403 - Insufficient permissions
NotFoundError, # 404 - Resource not found
)
# These are raised immediately without retries
try :
album = client.albums.get( "invalid_id" )
except NotFoundError:
# Not retried - the ID is invalid
pass
Implementation Details
The retry logic is implemented in the request() method of AsyncBaseClient (src/spotify_sdk/_async/_base_client.py:122-163):
for attempt in range (retries + 1 ):
try :
access_token = await self ._get_access_token()
request_headers = self ._default_headers(access_token)
if headers:
request_headers.update(headers)
response = await self ._http_client.request(
method = method,
url = path,
params = self ._clean_params(params),
json = json,
content = content,
timeout = timeout,
headers = request_headers,
)
return self ._handle_response(response)
except (httpx.ConnectError, httpx.TimeoutException) as e:
last_exception = e
if attempt < retries:
await anyio.sleep( self ._calculate_backoff(attempt))
continue
raise SpotifyError( f "Connection error: { e } " ) from e
except RateLimitError as e:
last_exception = e
if attempt < retries:
sleep_time = e.retry_after or self ._calculate_backoff(
attempt
)
await anyio.sleep(sleep_time)
continue
raise
except ServerError as e:
last_exception = e
if attempt < retries:
await anyio.sleep( self ._calculate_backoff(attempt))
continue
raise
Best Practices
Set appropriate max_retries for your use case
For production applications, the default max_retries=3 is usually sufficient. For batch processing or less time-sensitive operations, you might increase this: # Production API - keep default
client = SpotifyClient( access_token = "token" , max_retries = 3 )
# Batch processing - can tolerate longer retry periods
client = SpotifyClient( access_token = "token" , max_retries = 5 )
# Critical path - fail fast
client = SpotifyClient( access_token = "token" , max_retries = 1 )
Increase timeout for slow connections
If you’re on a slow network or making requests that return large responses, increase the timeout: client = SpotifyClient(
access_token = "token" ,
timeout = 60.0 , # 60 seconds for slow connections
)
Monitor and log retry behavior
While retries happen automatically, you may want to log when they occur for monitoring: import logging
from spotify_sdk import SpotifyClient, RateLimitError, ServerError
logger = logging.getLogger( __name__ )
client = SpotifyClient( access_token = "token" , max_retries = 3 )
try :
album = client.albums.get( "5K79FLRUCSysQnVESLcTdb" )
except RateLimitError as e:
logger.warning( f "Rate limited after retries: { e.retry_after } s" )
raise
except ServerError as e:
logger.error( f "Server error after retries: { e.message } " )
raise
Handle exhausted retries gracefully
When all retries are exhausted, the SDK raises an exception. Handle this in your application: from spotify_sdk import SpotifyClient, ServerError
import time
client = SpotifyClient( access_token = "token" , max_retries = 3 )
max_attempts = 3
for attempt in range (max_attempts):
try :
album = client.albums.get( "5K79FLRUCSysQnVESLcTdb" )
break
except ServerError:
if attempt < max_attempts - 1 :
time.sleep( 10 ) # Wait before trying again
continue
raise # All attempts failed