{"record":{"id":"3818f83fe3818485","repo":"jackwener/OpenCLI","slug":"api-error-3818f8","errorCode":"API_ERROR","errorMessage":"API_ERROR: ${data.errmsg ?? `WeRead API error ${data.errcode}`}","messagePattern":"API_ERROR: (.+?)`\\}","errorType":"error_code","errorClass":"CliError","httpStatus":null,"severity":"error","filePath":"clis/weread/utils.js","lineNumber":177,"sourceCode":"    }\n    catch (error) {\n        throw new CliError('FETCH_ERROR', `Failed to fetch ${path}: ${error instanceof Error ? error.message : String(error)}`, 'WeRead API may be temporarily unavailable');\n    }\n    let data;\n    try {\n        data = await resp.json();\n    }\n    catch {\n        throw new CliError('PARSE_ERROR', `Invalid JSON response for ${path}`, 'WeRead may have returned an HTML error page');\n    }\n    if (isAuthErrorResponse(resp, data)) {\n        throw new CliError('AUTH_REQUIRED', 'Not logged in to WeRead', 'Please log in to weread.qq.com in Chrome first');\n    }\n    if (!resp.ok) {\n        throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');\n    }\n    if (data?.errcode != null && data.errcode !== 0) {\n        throw new CliError('API_ERROR', data.errmsg ?? `WeRead API error ${data.errcode}`);\n    }\n    return data;\n}\nfunction getUniqueRawBookIds(snapshot) {\n    return Array.from(new Set(snapshot.rawBooks\n        .map((book) => String(book?.bookId || '').trim())\n        .filter(Boolean)));\n}\n/** Mirror of hasTrustedIndexes in buildShelfSnapshotPollScript — keep in sync */\nfunction getTrustedIndexedBookIds(snapshot) {\n    const rawBookIds = getUniqueRawBookIds(snapshot);\n    if (rawBookIds.length === 0)\n        return [];\n    const rawBookIdSet = new Set(rawBookIds);\n    const projectedIndexedBookIds = Array.from(new Set(snapshot.shelfIndexes\n        .filter((entry) => Number.isFinite(entry?.idx))\n        .sort((left, right) => Number(left?.idx ?? Number.MAX_SAFE_INTEGER) - Number(right?.idx ?? Number.MAX_SAFE_INTEGER))\n        .map((entry) => String(entry?.bookId || '').trim())","sourceCodeStart":159,"sourceCodeEnd":195,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/weread/utils.js#L159-L195","documentation":"The WeRead CLI's fetchPrivateApi wraps every authenticated WeRead API call. When the HTTP response is OK but the JSON body carries a non-zero errcode, the adapter normalizes it into a CliError with code API_ERROR so callers get one consistent error type for upstream API rejections. The message is the server-provided errmsg, or a fallback naming the numeric errcode when the server omits one.","triggerScenarios":"Any fetchPrivateApi call (via the data/result helpers) where resp.ok is true but the response body has data.errcode set to a non-zero value, e.g. an expired session cookie, invalid bookId, or a WeRead-side business rejection.","commonSituations":"Session cookies from Chrome have expired or been rotated; querying a bookId the account cannot access; WeRead changes an endpoint's error contract and starts returning errcode in a 200 response; transient WeRead service degradation.","solutions":["Log out and back in to weread.qq.com in Chrome to refresh the session cookies, then retry","Print the raw body (data.errcode/errmsg) to see which upstream error code is being returned and consult WeRead's error semantics","Re-check the arguments passed to the API call (bookId, path, query params) for typos or stale IDs","Retry after a delay in case of transient WeRead-side failures","If an errcode appears consistently, update the CLI to handle the new upstream error code"],"exampleFix":"// before\nconst data = await fetchPrivateApi('shelf/friendCommon', { user });\n// after\ntry {\n    const data = await fetchPrivateApi('shelf/friendCommon', { user });\n} catch (e) {\n    if (e.code === 'API_ERROR') console.error(`WeRead rejected the request: ${e.message} (re-login if AUTH_REQUIRED persists)`);\n    throw e;\n}","handlingStrategy":"try-catch","validationCode":"// best pre-check: ensure login cookies exist before calling\nif (!hasWeReadCookies()) throw new Error('Log in to weread.qq.com in Chrome first');","typeGuard":"function isApiError(e) { return e instanceof Error && e.code === 'API_ERROR'; }","tryCatchPattern":"try {\n    const data = await fetchPrivateApi(path, params);\n} catch (e) {\n    if (e.code === 'API_ERROR') {\n        // e.message is upstream errmsg or `WeRead API error <errcode>`; re-auth may fix it\n    } else if (e.code === 'AUTH_REQUIRED') {\n        // prompt login flow\n    } else throw e;\n}","preventionTips":["Re-login to weread.qq.com whenever cookies may be stale","Log data.errcode values to map upstream codes to remediations","Wrap fetchPrivateApi calls with a retry that distinguishes transient vs auth errors","Keep the CLI updated for WeRead API contract changes"],"tags":["api","http","remote-api"],"backgroundTag":"upstream-api-error-response","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}