jackwener/OpenCLI · error · CliError
API_ERROR
API_ERROR
Error message
${err?.error?.message || `Spotify API error ${res.status}`} What it means
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.
Source
Thrown at clis/spotify/spotify.js:90
throw new CliError('AUTH_CORRUPTED', 'Token file is corrupted. Run: opencli spotify auth');
}
if (Date.now() > tokens.expires_at - 60_000)
return refreshAccessToken(tokens.refresh_token);
return tokens.access_token;
}
// ── Spotify API helper ────────────────────────────────────────────────────────
async function api(method, path, body) {
const token = await getToken();
const res = await fetch(`https://api.spotify.com/v1${path}`, {
method,
headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' },
body: body ? JSON.stringify(body) : undefined,
});
if (res.status === 204 || res.status === 202)
return null;
if (!res.ok) {
const err = await res.json().catch(() => ({}));
throw new CliError('API_ERROR', err?.error?.message || `Spotify API error ${res.status}`);
}
return res.json();
}
async function findTrackUri(query) {
const data = await api('GET', `/search?q=${encodeURIComponent(query)}&type=track&limit=1`);
const track = getFirstSpotifyTrack(data);
if (!track)
throw new CliError('EMPTY_RESULT', `No track found for: ${query}`);
return track;
}
function openBrowser(url) {
const cmd = process.platform === 'win32' ? `start "" "${url}"` : process.platform === 'darwin' ? `open "${url}"` : `xdg-open "${url}"`;
exec(cmd);
}
// ── Commands ──────────────────────────────────────────────────────────────────
cli({
site: 'spotify',
name: 'auth',View on GitHub (pinned to 49907e53dc)
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.
Example fix
// before: fire and forget
await api('PUT', `/me/player/play`, { uris: [uri] }); // 404 if no device
// after: catch and surface
catch (e) { if (e.code === 'API_ERROR' && /404|device/i.test(e.message)) await transferToActiveDevice(); } Defensive patterns
Strategy: try-catch
Validate before calling
// Pre-checks that avoid common upstream 4xx/403/404:
// Premium required for playback; ensure a device is active before play/pause/volume calls.
const devices = await api('GET', '/me/player/devices');
if (!devices?.devices?.some(d => d.is_active)) await api('PUT', '/me/player', { device_ids: [devices.devices[0].id] }); Try / catch
try {
return await api('PUT', `/me/player/volume?volume_percent=${level}`);
} catch (e) {
if (e.code === 'API_ERROR') {
if (e.message.includes('401')) await reAuth();
else if (/404|device/i.test(e.message)) console.error('Open a Spotify player first');
else if (e.message.includes('429')) await backoff();
else if (/403|premium/i.test(e.message)) console.error('Premium account required');
}
throw e;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- arXiv API HTTP ${resp.status}
- Midjourney API request failed: ${message}
- mubu: ${path}: HTTP ${result.status} ${result.error ?? ''}
- API_ERROR
- API_ERROR
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/1ba089f6f5b21e1e.
Report an issue: GitHub.