Core concepts .md

Errors

Every error the v3 API returns uses the same JSON envelope, so your client can handle failures with one code path instead of one per endpoint.

{
  "error": "not_found",
  "detail": "movie with tmdb_id 999999999 not found"
}

error is always present and is a machine-readable code you can switch on. detail adds a human-readable explanation when FlickList has one to give. Some error codes, like a plain unauthorized, omit detail entirely rather than sending it as null.

Every surface uses this same envelope, whether the failure came from Sync, Scrobble, Lists, or the catalog endpoints. A scope failure looks the same shape as a validation failure:

{
  "error": "forbidden",
  "detail": "..."
}
Status Meaning What to do
400 The request failed validation. Fix the request body or params per detail. Retrying unmodified fails again.
401 The credential is missing, invalid, expired, or revoked. Sign in again through the device-code flow, or check the API key you're sending.
403 The credential is valid but lacks the scope this endpoint needs. Use a credential with the read or write scope required. See Authentication.
404 Nothing matches the identifier you sent. Double check the ID. The item may not exist in FlickList's catalog at all.
429 You've exceeded the per-credential rate limit. Read the Retry-After header (seconds) and wait before retrying. See Rate limits & fair use.

Adding something that already exists (an item already on your watchlist, a rating you already set) is not an error: those writes are idempotent and succeed, with the response telling you what was already there. See the individual Your data pages for each endpoint's exact response.

Branch on the HTTP status code first, error second. The status code tells you the category; the error string tells you which specific case it was, when you need that level of detail:

const res = await fetch(url, { headers });
if (!res.ok) {
  const body = await res.json();
  if (res.status === 429) {
    // wait Number(res.headers.get('retry-after')) seconds, then retry
  } else {
    console.error(body.error, body.detail);
  }
}