{"record":{"id":"a4d71853864296c8","repo":"jackwener/OpenCLI","slug":"stack-exchange-api-error-json-error-id-json","errorCode":null,"errorMessage":"Stack Exchange API error ${json.error_id} (${json.error_name}) for ${label}: ${json.error_message || ''}","messagePattern":"Stack Exchange API error (.+?) \\((.+?)\\) for (.+?): (.+?)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":null,"severity":"error","filePath":"clis/stackoverflow/read.js","lineNumber":60,"sourceCode":"    }\n    if (!res.ok) {\n        throw new CommandExecutionError(\n            `Stack Exchange API HTTP ${res.status} for ${label}`,\n            'Check the question id and quota (300/day per IP)',\n        );\n    }\n    let json;\n    try {\n        json = await res.json();\n    } catch (e) {\n        const detail = e instanceof Error ? e.message : String(e);\n        throw new CommandExecutionError(\n            `Malformed JSON from Stack Exchange API for ${label}: ${detail}`,\n            'The API returned a non-JSON body — likely a transient outage',\n        );\n    }\n    if (json && json.error_id) {\n        throw new CommandExecutionError(\n            `Stack Exchange API error ${json.error_id} (${json.error_name}) for ${label}: ${json.error_message || ''}`,\n            'Common causes: invalid filter, throttled, or quota exhausted',\n        );\n    }\n    return json;\n}\n\n/**\n * CLI args may arrive as strings (`--limit 5` → `'5'`) when not coerced by the\n * arg type system. Coerce-then-validate so `Number.isInteger` actually catches\n * the bad cases, and reject NaN explicitly.\n */\nfunction coerceInt(value) {\n    if (value === undefined || value === null || value === '') return NaN;\n    const n = typeof value === 'number' ? value : Number(value);\n    return Number.isFinite(n) && Number.isInteger(n) ? n : NaN;\n}\n","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/stackoverflow/read.js#L42-L78","documentation":"This CommandExecutionError is thrown by fetchJson when the Stack Exchange API responds with HTTP 200 but the JSON body contains an error_id field, meaning the API itself rejected the request (e.g. invalid filter, throttled, or daily quota exhausted). The message includes the API's numeric error_id, symbolic error_name (e.g. throttle_violation, no_app), and error_message. It surfaces the API's own error contract rather than a transport failure.","triggerScenarios":"Any of the four fetchJson call sites (qData, answersData, qCommentsData, ansCommentsData, acceptedData) gets a 200 response whose body has error_id set — most commonly error 400 bad_parameter (invalid filter value), error 502 throttle_violation (too many requests per IP), or error 402 no_app when the 300/day anonymous IP quota is exhausted.","commonSituations":"Hitting the 300/day unauthenticated quota from a shared IP or CI runner; passing a --filter value the API rejects; rapid repeated invocations triggering per-IP throttling; API version (2.3) deprecating a parameter previously used.","solutions":["Wait for the daily quota to reset or authenticate the request with an access_token/key to raise the quota","Retry after a delay (backoff) if error_name is throttle_violation","Check that the filter parameter (withbody) is valid against https://api.stackexchange.com/docs/filters","Inspect json.error_message in the error text for the API's specific complaint and fix that parameter"],"exampleFix":"// before\nconst json = await fetchJson(url, label); // throws on error_id\n// after\nlet json;\ntry {\n    json = await fetchJson(url, label);\n} catch (e) {\n    if (/throttle_violation/.test(e.message)) {\n        await new Promise((r) => setTimeout(r, 5000));\n        json = await fetchJson(url, label);\n    } else throw e;\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"function isSeApiError(json) {\n  return json != null && typeof json === 'object' && typeof json.error_id === 'number';\n}","tryCatchPattern":"try {\n  const data = await fetchJson(url, label);\n} catch (e) {\n  if (e instanceof CommandExecutionError && /throttle_violation/.test(e.message)) {\n    // exponential backoff, then retry once\n  } else if (/quota/.test(e.message)) {\n    // surface a 'quota exhausted, resets in N hours' message; do not retry\n  } else throw e;\n}","preventionTips":["Register an app and pass a key/access_token to raise the 300/day anonymous quota","Add exponential backoff for throttle_violation responses","Cache API responses to reduce quota consumption in scripts/CI","Check error_name in the message before choosing a recovery path"],"tags":["api","rate-limit","quota","stack-exchange"],"backgroundTag":"api-quota-exceeded","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}