{"record":{"id":"b0bfe6956f5f4096","repo":"jackwener/OpenCLI","slug":"stack-exchange-returned-http-429-rate-limited","errorCode":null,"errorMessage":"stack exchange returned HTTP 429 (rate limited)","messagePattern":"stack exchange returned HTTP 429 \\(rate limited\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":429,"severity":"error","filePath":"clis/stackoverflow/utils.js","lineNumber":63,"sourceCode":"            url.searchParams.set(k, String(v));\n        }\n    }\n    if (!url.searchParams.has('site')) url.searchParams.set('site', SE_SITE);\n\n    let resp;\n    try {\n        resp = await fetch(url, {\n            headers: {\n                'Accept': 'application/json',\n                'Accept-Encoding': 'gzip',\n                'User-Agent': UA,\n            },\n        });\n    } catch (error) {\n        throw new CommandExecutionError(`stack exchange request failed: ${error?.message || error}`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError('stack exchange returned HTTP 429 (rate limited)', 'Wait a few seconds and retry, or lower --limit.');\n    }\n    if (!resp.ok) {\n        let body = '';\n        try { body = (await resp.json())?.error_message || ''; } catch { /* ignore */ }\n        throw new CommandExecutionError(`stack exchange HTTP ${resp.status}: ${body || resp.statusText}`);\n    }\n    let data;\n    try {\n        data = await resp.json();\n    } catch (error) {\n        throw new CommandExecutionError(`stack exchange returned malformed JSON: ${error?.message || error}`);\n    }\n    if (data?.error_id) {\n        throw new CommandExecutionError(\n            `stack exchange API error: ${data.error_message || data.error_name}`,\n            'Inspect the URL in a browser for the canonical error context.',\n        );\n    }","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/stackoverflow/utils.js#L45-L81","documentation":"CommandExecutionError thrown by seFetch when the Stack Exchange API responds with HTTP 429, meaning the client has exceeded the anonymous quota (300 requests/day per IP for unauthenticated calls, per the file's header comment). The suggestion field advises waiting or lowering --limit.","triggerScenarios":"More than ~300 api.stackexchange.com calls per day from one IP; a burst of automated queries/agents sharing an IP (CI runners, office NAT); repeatedly re-running searches with large pagesizes.","commonSituations":"CI pipelines hammering the API on every build; multiple team members behind one corporate egress IP; scripts looping over many question ids in a short window; shared cloud/VPN egress IPs that are already exhausted.","solutions":["Wait until the daily quota resets, then retry.","Reduce request volume: lower --limit, batch ids via /questions/{ids};-separated paths, cache responses.","Register a Stack Apps app key to raise the quota to 10,000 requests/day and pass key as a search param.","Authenticate with OAuth for a 30,000/day quota on behalf of a user.","Cache results locally so repeat invocations don't re-hit the API."],"exampleFix":"// before: immediate retry loop\nfor (const id of ids) await read(id);\n// after: batch + throttle\nconst chunks = chunk(ids, 100);\nfor (const c of chunks) {\n  await seFetch(`/questions/${c.join(';')}?site=stackoverflow&filter=withbody`);\n  await sleep(500);\n}","handlingStrategy":"retry","validationCode":"function withKey(url) { return url + (process.env.SE_APP_KEY ? `${url.includes('?') ? '&' : '?'}key=${process.env.SE_APP_KEY}` : ''); } // raises quota from 300 to 10,000/day","typeGuard":null,"tryCatchPattern":"async function fetchRespecting429(path) {\n  try {\n    return await seFetch(path);\n  } catch (e) {\n    if (String(e.message).includes('HTTP 429')) {\n      const retryAfter = 10 + Math.random() * 5;\n      await new Promise(r => setTimeout(r, retryAfter * 1000));\n      return seFetch(path);\n    }\n    throw e;\n  }\n}","preventionTips":["Register a Stack Apps app key and send it as `key` to get 10,000 requests/day.","Throttle batch jobs (e.g. sleep between calls) and cache responses to disk.","Batch ids via /questions/{id};{id} paths instead of one call per id.","Remember the quota is per-IP — coordinate across teammates/CI sharing an egress IP.","Lower --limit/pagesize; large pagesizes don't consume extra quota but big loops do."],"tags":["rate-limit","http-429","stack-exchange-api","throttling"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}