{"record":{"id":"7b39549d6079ae3c","repo":"jackwener/OpenCLI","slug":"coingecko-returned-http-429-rate-limited","errorCode":null,"errorMessage":"coingecko returned HTTP 429 (rate limited)","messagePattern":"coingecko returned HTTP 429 \\(rate limited\\)","errorType":"http","errorClass":"CommandExecutionError","httpStatus":429,"severity":"error","filePath":"clis/coingecko/categories.js","lineNumber":47,"sourceCode":"            );\n        }\n        const limit = Number(args.limit ?? 20);\n        if (!Number.isInteger(limit) || limit <= 0) {\n            throw new ArgumentError('coingecko limit must be a positive integer');\n        }\n        if (limit > 100) {\n            throw new ArgumentError('coingecko limit must be <= 100');\n        }\n        const url = `https://api.coingecko.com/api/v3/coins/categories?order=${encodeURIComponent(sort)}`;\n        let resp;\n        try {\n            resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });\n        }\n        catch (err) {\n            throw new CommandExecutionError(`coingecko categories request failed: ${err?.message ?? err}`);\n        }\n        if (resp.status === 429) {\n            throw new CommandExecutionError(\n                'coingecko returned HTTP 429 (rate limited)',\n                'Free tier allows ~30 calls/min. Wait and retry.',\n            );\n        }\n        if (!resp.ok) {\n            throw new CommandExecutionError(`coingecko categories returned HTTP ${resp.status}`);\n        }\n        let data;\n        try {\n            data = await resp.json();\n        }\n        catch (err) {\n            throw new CommandExecutionError(`coingecko categories returned malformed JSON: ${err?.message ?? err}`);\n        }\n        if (!Array.isArray(data) || !data.length) {\n            throw new EmptyResultError('coingecko categories', 'CoinGecko returned no category data.');\n        }\n        return data.slice(0, limit).map((cat, i) => ({","sourceCodeStart":29,"sourceCodeEnd":65,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/coingecko/categories.js#L29-L65","documentation":"CoinGecko responded with HTTP 429 and the CLI short-circuits into a CommandExecutionError with guidance. The free tier of the CoinGecko public API is rate limited (~30 calls/min).","triggerScenarios":"Calling the categories command more than ~30 times per minute from one IP, or sharing an IP with other CoinGecko API consumers.","commonSituations":"Loops/scripts hammering CoinGecko without delays; shared CI runner IPs already rate limited; multiple team members polling from the same egress IP.","solutions":["Wait a minute and retry with backoff","Add throttling/delay between calls (<=30 req/min)","Move to a CoinGecko paid/demo API plan with a key and higher limits"],"exampleFix":"// before\nfor (const s of sorts) await run(`categories.js --sort ${s}`);\n// after\nfor (const s of sorts) {\n  await run(`categories.js --sort ${s}`);\n  await sleep(2500); // stay under ~30 req/min\n}","handlingStrategy":"retry","validationCode":"// client-side throttle: max ~30 req/min\nlet lastCall = 0;\nasync function throttled(fn, minGapMs = 2100) {\n  const wait = lastCall + minGapMs - Date.now();\n  if (wait > 0) await sleep(wait);\n  lastCall = Date.now();\n  return fn();\n}","typeGuard":"function isRateLimited(err) {\n  return err instanceof CommandExecutionError && /HTTP 429|rate limited/i.test(err.message);\n}","tryCatchPattern":"try {\n  await throttled(() => runCategories(args));\n} catch (err) {\n  if (isRateLimited(err)) {\n    await sleep(60_000);\n    return runCategories(args);\n  }\n  throw err;\n}","preventionTips":["Serialize CoinGecko calls with a >=2s gap","Use an API key / paid plan for higher rate limits","Cache category results instead of re-fetching repeatedly"],"tags":["rate-limit","http-429","network","coingecko"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}