{"record":{"id":"45383be7e25814d4","repo":"jackwener/OpenCLI","slug":"suno-feed-api-rejected-http-result-status-re","errorCode":null,"errorMessage":"Suno feed API rejected (HTTP ${result.status}). Re-login.","messagePattern":"Suno feed API rejected \\(HTTP (.+?)\\)\\. Re-login\\.","errorType":"exception","errorClass":"AuthRequiredError","httpStatus":null,"severity":"error","filePath":"clis/suno/utils.js","lineNumber":375,"sourceCode":"    const idsJson = JSON.stringify(clipIds);\n\n    while (Date.now() < deadline) {\n        const result = unwrapEvaluateResult(await page.evaluate(`(async () => {\n            const res = await fetch('${STUDIO_API}/api/feed/v3', {\n                method: 'POST',\n                headers: ${sunoHeadersJs(deviceId, { 'Content-Type': 'application/json' })},\n                body: JSON.stringify({ clip_ids: ${idsJson} }),\n            });\n            const body = await res.json().catch(() => null);\n            return { status: res.status, body };\n        })()`));\n\n        if (!result) {\n            await page.wait(pollSeconds);\n            continue;\n        }\n        if (result.status === 401 || result.status === 403) {\n            throw new AuthRequiredError(SUNO_DOMAIN, `Suno feed API rejected (HTTP ${result.status}). Re-login.`);\n        }\n        if (result.status < 200 || result.status >= 300) {\n            throw new CommandExecutionError(`Suno feed API failed while polling clips (HTTP ${result.status || '?'})`);\n        }\n        if (!result.body || typeof result.body !== 'object' || Array.isArray(result.body)) {\n            throw new CommandExecutionError('Suno feed API returned malformed JSON while polling clips');\n        }\n\n        const allClips = result.body.clips || [];\n        if (!Array.isArray(allClips)) {\n            throw new CommandExecutionError('Suno feed API returned malformed clips payload');\n        }\n        const ourClips = allClips.filter(c => targetSet.has(c.id));\n        const finished = ourClips.filter(c => c.status === 'complete' || c.status === 'error');\n\n        if (typeof onProgress === 'function') {\n            onProgress({ total: clipIds.length, done: finished.length, statuses: ourClips.map(c => `${c.id.slice(0,8)}:${c.status}`) });\n        }","sourceCodeStart":357,"sourceCodeEnd":393,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/suno/utils.js#L357-L393","documentation":"pollSunoClips polls Suno's cookie-authenticated /api/feed/v3 endpoint. A 401 or 403 response means the browser's Suno session cookies are expired or invalid, so AuthRequiredError is thrown instructing the user to re-login on suno.com.","triggerScenarios":"During clip polling, the feed endpoint returns HTTP 401 or 403 — the session cookie expired mid-run, cookies were cleared, or the Suno session was invalidated server-side (e.g. logged in elsewhere).","commonSituations":"Long generation runs outlasting cookie TTL; user logged out or into another session on suno.com; cookie jar not persisted between CLI runs; IP/region change triggering session invalidation.","solutions":["Re-authenticate: run the suno login/auth command to refresh cookies in the browser session.","Restart the poll command immediately after re-login.","Increase cookie persistence (keep the profile/session between runs) to avoid mid-run expiry.","If it recurs quickly, check suno.com for concurrent-session or security events."],"exampleFix":"// before: polling with a stale session until it 401s\nconst clips = await pollSunoClips(page, ids, 300, deviceId);\n// after: verify session first and re-auth on AuthRequiredError\ntry {\n  await verifySunoSession(page);\n  const clips = await pollSunoClips(page, ids, 300, deviceId);\n} catch (e) {\n  if (e instanceof AuthRequiredError) { await sunoLogin(page); return pollSunoClips(page, ids, 300, deviceId); }\n  throw e;\n}","handlingStrategy":"retry","validationCode":"// pre-flight session check before polling\nconst res = await fetch(STUDIO_API + '/api/feed/v3', { method: 'POST', headers });\nif (res.status === 401 || res.status === 403) throw new AuthRequiredError(SUNO_DOMAIN, 'Session invalid — re-login first');","typeGuard":null,"tryCatchPattern":"try {\n  const clips = await pollSunoClips(page, ids, timeout, deviceId);\n} catch (e) {\n  if (e instanceof AuthRequiredError) {\n    await sunoLogin(page);\n    return pollSunoClips(page, ids, timeout, deviceId);\n  }\n  throw e;\n}","preventionTips":["Re-login before long generation runs","Use a persistent browser profile so cookies survive restarts","Avoid logging into suno.com elsewhere during a run"],"tags":["auth","session-expired","http-401","suno"],"backgroundTag":"session-expired","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}