Skip to main content

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

CodeNameWhen it occursRetry?Safe to show users?
400Bad RequestMissing or invalid request parameters (e.g. calling /v0/tracks/search without a term)NoNo — show a generic "something went wrong" message
401UnauthorizedMissing, expired, or invalid tokenAfter re-authenticatingPrompt user to log in again
403ForbiddenValid token but the token type does not have access to this resource (e.g. using a User Token on a Partner-only endpoint)NoNo — this is a configuration issue
404Not FoundThe requested resource does not existNoYou can show "not found" if the resource is user-visible
429Too Many RequestsRate limit exceeded — per-second, daily app, or per-user limit reachedYes, with backoffNo — handle silently with a retry
500Internal Server ErrorAn unexpected error occurred on the API side. May be transient or triggered by a specific request — retry once, then report if it persistsOnce, then reportNo — retry silently
502Bad GatewayDDoS mitigation in progress — temporaryYes, with backoffNo — retry silently
503Service UnavailableTemporary service outageYes, with backoffNo — 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."