{"record":{"id":"6ecf6f3fb5162503","repo":"jackwener/OpenCLI","slug":"label-returned-http-429-rate-limited-6ecf6f","errorCode":null,"errorMessage":"${label} returned HTTP 429 (rate limited)","messagePattern":"(.+?) returned HTTP 429 \\(rate limited\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":429,"severity":"error","filePath":"clis/wikidata/utils.js","lineNumber":76,"sourceCode":"    return raw;\n}\n\nexport async function wikidataFetch(url, label) {\n    let resp;\n    try {\n        resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });\n    }\n    catch (err) {\n        throw new CommandExecutionError(\n            `${label} request failed: ${err?.message ?? err}`,\n            'Check that www.wikidata.org is reachable from this network.',\n        );\n    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `Wikidata returned 404 for ${url}.`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(\n            `${label} returned HTTP 429 (rate limited)`,\n            'Wikidata throttles anonymous traffic; back off and retry.',\n        );\n    }\n    if (!resp.ok) {\n        throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);\n    }\n    let body;\n    try {\n        body = await resp.json();\n    }\n    catch (err) {\n        throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);\n    }\n    return body;\n}\n\n/**","sourceCodeStart":58,"sourceCodeEnd":94,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/wikidata/utils.js#L58-L94","documentation":"Wikidata rate-limits anonymous (keyless) traffic. When the endpoint answers HTTP 429, wikidataFetch raises a CommandExecutionError advising the caller to back off and retry. This is a deliberate retryable-failure signal, not a bug in your code.","triggerScenarios":"Sending many wikidataFetch calls in a tight loop (e.g. fetching hundreds of Q-IDs sequentially without delay) so anonymous requests to www.wikidata.org exceed the per-IP quota; shared/proxy IPs already exhausted by other traffic.","commonSituations":"Batch scripts iterating over large entity lists; CI runners making repeated lookups from datacenter IPs with shared quotas; re-running a failed pipeline immediately after a 429.","solutions":["Add a delay/backoff between requests and retry after waiting (respect Retry-After if present)","Cache responses so the same entity is not fetched repeatedly","Reduce batch size or split the work across a longer window","Apply for a Bot username/API agreement with Wikidata for higher limits"],"exampleFix":"// before\nfor (const qid of qids) results.push(await fetchEntity(qid));\n// after\nfor (const qid of qids) {\n  results.push(await fetchEntity(qid));\n  await new Promise(r => setTimeout(r, 500)); // stay under anonymous rate limit\n}","handlingStrategy":"retry","validationCode":"if (pendingRequests > 50) await new Promise(r => setTimeout(r, 1000)); // self-throttle before calling","typeGuard":null,"tryCatchPattern":"const fetchWithRetry = async (url, label, retries = 3) => {\n  for (let i = 0; ; i++) {\n    try { return await wikidataFetch(url, label); }\n    catch (err) {\n      if (!String(err.message).includes('429') || i >= retries) throw err;\n      await new Promise(r => setTimeout(r, 1000 * 2 ** i));\n    }\n  }\n};","preventionTips":["Throttle anonymous requests (e.g. >=200-500ms between calls)","Cache entity lookups to avoid repeat fetches","Implement exponential backoff on 429","Prefer batched wbgetentities calls over per-ID requests"],"tags":["rate-limit","http-429","wikidata","network"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}