jackwener/OpenCLI · error · CliError
API_ERROR
API_ERROR
Error message
API_ERROR: ${data.errmsg ?? `WeRead API error ${data.errcode}`} What it means
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.
Source
Thrown at clis/weread/utils.js:177
}
catch (error) {
throw new CliError('FETCH_ERROR', `Failed to fetch ${path}: ${error instanceof Error ? error.message : String(error)}`, 'WeRead API may be temporarily unavailable');
}
let data;
try {
data = await resp.json();
}
catch {
throw new CliError('PARSE_ERROR', `Invalid JSON response for ${path}`, 'WeRead may have returned an HTML error page');
}
if (isAuthErrorResponse(resp, data)) {
throw new CliError('AUTH_REQUIRED', 'Not logged in to WeRead', 'Please log in to weread.qq.com in Chrome first');
}
if (!resp.ok) {
throw new CliError('FETCH_ERROR', `HTTP ${resp.status} for ${path}`, 'WeRead API may be temporarily unavailable');
}
if (data?.errcode != null && data.errcode !== 0) {
throw new CliError('API_ERROR', data.errmsg ?? `WeRead API error ${data.errcode}`);
}
return data;
}
function getUniqueRawBookIds(snapshot) {
return Array.from(new Set(snapshot.rawBooks
.map((book) => String(book?.bookId || '').trim())
.filter(Boolean)));
}
/** Mirror of hasTrustedIndexes in buildShelfSnapshotPollScript — keep in sync */
function getTrustedIndexedBookIds(snapshot) {
const rawBookIds = getUniqueRawBookIds(snapshot);
if (rawBookIds.length === 0)
return [];
const rawBookIdSet = new Set(rawBookIds);
const projectedIndexedBookIds = Array.from(new Set(snapshot.shelfIndexes
.filter((entry) => Number.isFinite(entry?.idx))
.sort((left, right) => Number(left?.idx ?? Number.MAX_SAFE_INTEGER) - Number(right?.idx ?? Number.MAX_SAFE_INTEGER))
.map((entry) => String(entry?.bookId || '').trim())View on GitHub (pinned to 49907e53dc)
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
Example fix
// before
const data = await fetchPrivateApi('shelf/friendCommon', { user });
// after
try {
const data = await fetchPrivateApi('shelf/friendCommon', { user });
} catch (e) {
if (e.code === 'API_ERROR') console.error(`WeRead rejected the request: ${e.message} (re-login if AUTH_REQUIRED persists)`);
throw e;
} Defensive patterns
Strategy: try-catch
Validate before calling
// best pre-check: ensure login cookies exist before calling
if (!hasWeReadCookies()) throw new Error('Log in to weread.qq.com in Chrome first'); Type guard
function isApiError(e) { return e instanceof Error && e.code === 'API_ERROR'; } Try / catch
try {
const data = await fetchPrivateApi(path, params);
} catch (e) {
if (e.code === 'API_ERROR') {
// e.message is upstream errmsg or `WeRead API error <errcode>`; re-auth may fix it
} else if (e.code === 'AUTH_REQUIRED') {
// prompt login flow
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ${label} returned HTTP ${resp.status}: ${summarizeApiError(p
- archive search failed: HTTP ${resp.status}
- HTTP ${result.httpStatus} from /api/organizations
- coingecko derivatives returned HTTP ${resp.status}
- ${label} returned HTTP ${res.status}
AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29).
Data as JSON: /api/errors/3818f83fe3818485.
Report an issue: GitHub.