{"record":{"id":"1ba089f6f5b21e1e","repo":"jackwener/OpenCLI","slug":"api-error-1ba089","errorCode":"API_ERROR","errorMessage":"${err?.error?.message || `Spotify API error ${res.status}`}","messagePattern":"\\$\\{err\\?\\.error\\?\\.message \\|\\| `Spotify API error \\$\\{res\\.status\\}`\\}","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/spotify/spotify.js","lineNumber":90,"sourceCode":"        throw new CliError('AUTH_CORRUPTED', 'Token file is corrupted. Run: opencli spotify auth');\n    }\n    if (Date.now() > tokens.expires_at - 60_000)\n        return refreshAccessToken(tokens.refresh_token);\n    return tokens.access_token;\n}\n// ── Spotify API helper ────────────────────────────────────────────────────────\nasync function api(method, path, body) {\n    const token = await getToken();\n    const res = await fetch(`https://api.spotify.com/v1${path}`, {\n        method,\n        headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },\n        body: body ? JSON.stringify(body) : undefined,\n    });\n    if (res.status === 204 || res.status === 202)\n        return null;\n    if (!res.ok) {\n        const err = await res.json().catch(() => ({}));\n        throw new CliError('API_ERROR', err?.error?.message || `Spotify API error ${res.status}`);\n    }\n    return res.json();\n}\nasync function findTrackUri(query) {\n    const data = await api('GET', `/search?q=${encodeURIComponent(query)}&type=track&limit=1`);\n    const track = getFirstSpotifyTrack(data);\n    if (!track)\n        throw new CliError('EMPTY_RESULT', `No track found for: ${query}`);\n    return track;\n}\nfunction openBrowser(url) {\n    const cmd = process.platform === 'win32' ? `start \"\" \"${url}\"` : process.platform === 'darwin' ? `open \"${url}\"` : `xdg-open \"${url}\"`;\n    exec(cmd);\n}\n// ── Commands ──────────────────────────────────────────────────────────────────\ncli({\n    site: 'spotify',\n    name: 'auth',","sourceCodeStart":72,"sourceCodeEnd":108,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/spotify/spotify.js#L72-L108","documentation":"api() is the low-level Spotify REST helper; for any non-OK response (other than 204/202) it parses the JSON body and throws a CliError with code API_ERROR carrying err.error.message from Spotify's error envelope, falling back to a generic message with the HTTP status. This surfaces upstream Spotify Web API failures (4xx/5xx) uniformly to callers.","triggerScenarios":"Any authenticated Spotify Web API request returning 400 (bad query/params), 401 (expired/invalid access token that wasn't refreshed), 403 (insufficient scope, e.g. playback control without premium), 404 (no active device), or 429 (rate limited) — the thrown message mirrors Spotify's error.message.","commonSituations":"Playing a track with a free account (403 PREMIUM_REQUIRED); no active Spotify device open (404); malformed search query (400); hitting rate limits after polling loops (429); expired token slipping through the 60s refresh window.","solutions":["Read the embedded Spotify error.message to identify the specific cause (403 premium, 404 no device, 429 rate limit).","For 401, re-authenticate with opencli spotify auth to refresh credentials.","For 404, open a Spotify player/device first so playback endpoints have an active device.","For 429, back off and retry later; reduce polling frequency.","For 403, verify the account is Premium and the app has the required scopes."],"exampleFix":"// before: fire and forget\nawait api('PUT', `/me/player/play`, { uris: [uri] }); // 404 if no device\n// after: catch and surface\ncatch (e) { if (e.code === 'API_ERROR' && /404|device/i.test(e.message)) await transferToActiveDevice(); }","handlingStrategy":"try-catch","validationCode":"// Pre-checks that avoid common upstream 4xx/403/404:\n// Premium required for playback; ensure a device is active before play/pause/volume calls.\nconst devices = await api('GET', '/me/player/devices');\nif (!devices?.devices?.some(d => d.is_active)) await api('PUT', '/me/player', { device_ids: [devices.devices[0].id] });","typeGuard":null,"tryCatchPattern":"try {\n  return await api('PUT', `/me/player/volume?volume_percent=${level}`);\n} catch (e) {\n  if (e.code === 'API_ERROR') {\n    if (e.message.includes('401')) await reAuth();\n    else if (/404|device/i.test(e.message)) console.error('Open a Spotify player first');\n    else if (e.message.includes('429')) await backoff();\n    else if (/403|premium/i.test(e.message)) console.error('Premium account required');\n  }\n  throw e;\n}","preventionTips":["Handle 429 with exponential backoff; never tight-loop the API.","Ensure an active Spotify device exists before playback commands.","Confirm the account is Premium and scopes cover playback control.","Catch e.code === 'API_ERROR' and branch on the embedded Spotify message/status."],"tags":["spotify","api","http-error","network"],"backgroundTag":"upstream-api-error","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}