{"record":{"id":"4406781860cb6f71","repo":"jackwener/OpenCLI","slug":"chess-com-api-returned-http-resp-status-for-u","errorCode":null,"errorMessage":"Chess.com API returned HTTP ${resp.status} for ${url}","messagePattern":"Chess\\.com API returned HTTP (.+?) for (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/chess/utils.js","lineNumber":55,"sourceCode":"            'Expected https://www.chess.com/game/live/<id> or https://www.chess.com/game/daily/<id>.',\n        );\n    }\n    return { kind: m[1].toLowerCase(), id: m[2] };\n}\n\nexport async function chessApi(path, fetchImpl = fetch) {\n    const url = path.startsWith('http') ? path : `${API_BASE}${path}`;\n    let resp;\n    try {\n        resp = await fetchImpl(url, { headers: { 'User-Agent': UA, accept: 'application/json' } });\n    } catch (error) {\n        throw new CommandExecutionError(`Failed to fetch Chess.com API ${url}: ${error?.message || error}`);\n    }\n    if (!resp || typeof resp !== 'object') {\n        throw new CommandExecutionError(`Chess.com API returned an invalid response object for ${url}`);\n    }\n    if (resp.status === 404) throw new EmptyResultError(`Chess.com returned 404 for ${url}`);\n    if (!resp.ok) throw new CommandExecutionError(`Chess.com API returned HTTP ${resp.status} for ${url}`);\n    let payload;\n    try {\n        payload = await resp.json();\n    } catch (error) {\n        throw new CommandExecutionError(`Chess.com API returned malformed JSON for ${url}: ${error?.message || error}`);\n    }\n    if (!isPlainObject(payload)) {\n        throw new CommandExecutionError(`Chess.com API returned an unexpected payload shape for ${url}`);\n    }\n    return payload;\n}\n\n/** Pull rating + record fields out of a stats sub-object (`chess_rapid` etc). */\nexport function summarizeStats(stats, kind) {\n    const k = stats?.[kind];\n    if (!k) return null;\n    if (!isPlainObject(k)) {\n        throw new CommandExecutionError(`Chess.com stats payload for ${kind} is not an object`);","sourceCodeStart":37,"sourceCodeEnd":73,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/chess/utils.js#L37-L73","documentation":"A CommandExecutionError thrown when the Chess.com API returns a non-2xx status other than 404 (resp.ok is false). Since the public pub API needs no auth, this typically indicates rate limiting or server-side trouble rather than credentials problems. 404 is intentionally handled separately as an empty result.","triggerScenarios":"HTTP 429 when polling /player/<username>/stats or monthly archives too aggressively (Chess.com throttles unauthenticated requests), 5xx during Chess.com outages, 301/3xx if a redirect is not followed by the fetch implementation.","commonSituations":"Loops over many usernames without delay hitting the rate limit; Chess.com maintenance windows; a custom fetchImpl with redirect: 'manual' turning a moved endpoint into a 3xx failure.","solutions":["Retry with exponential backoff on 429/5xx and throttle requests (e.g. wait a few seconds between calls).","Cache responses to reduce repeat hits on the same endpoint.","Check https://status.chess.com for ongoing outages.","If persistent 3xx, ensure your fetch follows redirects (default) and the API_BASE path is current."],"exampleFix":"// before\nfor (const u of users) await chessApi(`/player/${u}/stats`); // 429s\n// after\nfor (const u of users) {\n  const stats = await withRetry(() => chessApi(`/player/${u}/stats`), { retries: 3, backoffMs: 1000 });\n  await sleep(1500);\n}","handlingStrategy":"retry","validationCode":"// Throttle pre-emptively; Chess.com throttles unauthenticated pub API calls:\nconst sleep = (ms) => new Promise((r) => setTimeout(r, ms));\nawait sleep(1500); // between per-player requests in a batch","typeGuard":"function isHttpError(e) {\n  return e instanceof Error && /returned HTTP \\d+/.test(e.message);\n}","tryCatchPattern":"async function withRetry(fn, { retries = 3, backoffMs = 2000 } = {}) {\n  for (let i = 0; ; i++) {\n    try { return await fn(); }\n    catch (e) {\n      if (!isHttpError(e) || i >= retries) throw e;\n      await new Promise((r) => setTimeout(r, backoffMs * 2 ** i));\n    }\n  }\n}","preventionTips":["Rate-limit batch queries over usernames/months.","Cache responses to avoid repeat endpoint hits.","Retry only on 429/5xx; fail fast on other statuses.","Monitor https://status.chess.com during outages."],"tags":["http-error","rate-limit","retry","chess-com"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}