{"record":{"id":"2875579fdbef218e","repo":"jackwener/OpenCLI","slug":"label-returned-http-429-rate-limited-287557","errorCode":null,"errorMessage":"${label} returned HTTP 429 (rate limited)","messagePattern":"(.+?) returned HTTP 429 \\(rate limited\\)","errorType":"exception","errorClass":"CommandExecutionError","httpStatus":429,"severity":"warning","filePath":"clis/rest-countries/utils.js","lineNumber":67,"sourceCode":"    return raw;\n}\n\nexport async function restCountriesFetch(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 restcountries.com is reachable from this network.',\n        );\n    }\n    if (resp.status === 404) {\n        throw new EmptyResultError(label, `REST Countries returned 404 for ${url}.`);\n    }\n    if (resp.status === 429) {\n        throw new CommandExecutionError(`${label} returned HTTP 429 (rate limited)`);\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/** Convert REST Countries' `{cur: {name, symbol}}` map to a comma-joined list. */\nexport function joinCurrencies(currencies) {\n    if (!currencies || typeof currencies !== 'object') return '';\n    return Object.entries(currencies)","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/rest-countries/utils.js#L49-L85","documentation":"restCountriesFetch throws CommandExecutionError when the REST Countries API responds with HTTP 429, meaning the client exceeded the service's rate limit. The library surfaces the 429 as a distinct, explicitly-named 'rate limited' error so callers know the failure is temporary and quota-related, not a bad query or outage.","triggerScenarios":"Calling any rest-countries command (which internally calls restCountriesFetch) repeatedly in a short window, or sharing an IP/proxy with other heavy users of restcountries.com, until the API throttles the request with a 429 response.","commonSituations":"Batch scripts iterating over many country lookups without delay; CI jobs making parallel requests; shared corporate/VPN egress IP already rate-limited by REST Countries' free-tier quota.","solutions":["Wait (e.g. 30-60 seconds) and retry the same command, ideally with backoff","Add a delay between consecutive rest-countries calls in batch scripts","Cache previously fetched country data locally to reduce repeat API calls","Check whether a shared proxy/VPN IP is causing the throttling and use a different egress"],"exampleFix":"// before\nfor (const c of ['france','germany','spain']) await getCountry(c);\n// after\nfor (const c of ['france','germany','spain']) {\n  await getCountry(c);\n  await new Promise(r => setTimeout(r, 1500)); // stay under rate limit\n}","handlingStrategy":"retry","validationCode":"null","typeGuard":"null","tryCatchPattern":"try {\n  await restCountriesCommand(args);\n} catch (e) {\n  if (String(e.message).includes('429')) {\n    await sleep(60000); // backoff, then retry once\n    return await restCountriesCommand(args);\n  }\n  throw e;\n}","preventionTips":["Throttle batch lookups with an inter-request delay (1-2s)","Cache country data locally; country facts rarely change","Avoid parallel requests to restcountries.com","Watch for 429 in logs and back off exponentially"],"tags":["http","rate-limit","network"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}