Error handling
Error response structure
All error responses from the Epidemic Sound API return a JSON object with a single message field:
{
"message": "Invalid credentials"
}
The message field is intended for developer debugging only. Do not surface raw API error messages directly to your end users — use the HTTP status code to determine what to show in your UI instead.
HTTP status codes
| Code | Name | When it occurs | Retry? | Safe to show users? |
|---|---|---|---|---|
| 400 | Bad Request | Missing or invalid request parameters (e.g. calling /v0/tracks/search without a term) | No | No — show a generic "something went wrong" message |
| 401 | Unauthorized | Missing, expired, or invalid token | After re-authenticating | Prompt user to log in again |
| 403 | Forbidden | Valid token but the token type does not have access to this resource (e.g. using a User Token on a Partner-only endpoint) | No | No — this is a configuration issue |
| 404 | Not Found | The requested resource does not exist | No | You can show "not found" if the resource is user-visible |
| 429 | Too Many Requests | Rate limit exceeded — per-second, daily app, or per-user limit reached | Yes, with backoff | No — handle silently with a retry |
| 500 | Internal Server Error | An unexpected error occurred on the API side. May be transient or triggered by a specific request — retry once, then report if it persists | Once, then report | No — retry silently |
| 502 | Bad Gateway | DDoS mitigation in progress — temporary | Yes, with backoff | No — retry silently |
| 503 | Service Unavailable | Temporary service outage | Yes, with backoff | No — retry silently |
For details on rate limit headers, retry strategies, and DDoS protection, see Troubleshooting.
Error handling example
async function apiFetch(url, options, maxRetries = 3) {
let attempt = 0
while (attempt < maxRetries) {
const response = await fetch(url, options)
if (response.ok) {
return response.json()
}
const status = response.status
if (status === 401) {
// Re-authenticate, then retry once
await refreshToken()
attempt++
continue
}
if (status === 429 || status === 502 || status === 503) {
// Exponential backoff: 1s, 2s, 4s, ...
const waitMs = Math.pow(2, attempt) * 1000
await new Promise((resolve) => setTimeout(resolve, waitMs))
attempt++
continue
}
if (status === 500) {
// Retry once — a 500 can be transient, but retrying repeatedly won't
// help if it's triggered by the request itself (e.g. accessing a
// resource outside your plan). Report it if it persists.
if (attempt === 0) {
attempt++
continue
}
const body = await response.json()
throw new Error(
`API error 500: ${body.message} (report this if it persists)`
)
}
// Non-retryable error
const body = await response.json()
throw new Error(`API error ${status}: ${body.message}`)
}
throw new Error('Max retries exceeded')
}
What to show users
Use the HTTP status code to drive UI decisions, not the message field. For example:
- 401: "Your session has expired. Please log in again."
- 404: "This track is no longer available."
- 429 / 502 / 503: Retry silently in the background; only show a message if all retries fail.
- 500: Retry once. If it keeps failing, report the error to Epidemic Sound.
- Everything else: "Something went wrong. Please try again later."