{"record":{"id":"f3ca5eb06661451a","repo":"jackwener/OpenCLI","slug":"coingecko-returned-http-429-rate-limited-f3ca5e","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/trending.js","lineNumber":27,"sourceCode":"    site: 'coingecko',\n    name: 'trending',\n    access: 'read',\n    description: 'Top trending cryptocurrencies on CoinGecko in the last 24h (search-volume based).',\n    domain: 'api.coingecko.com',\n    strategy: Strategy.PUBLIC,\n    browser: false,\n    args: [],\n    columns: ['rank', 'id', 'symbol', 'name', 'marketCapRank', 'priceBtc', 'thumb'],\n    func: async () => {\n        const url = 'https://api.coingecko.com/api/v3/search/trending';\n        let resp;\n        try {\n            resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });\n        } catch (error) {\n            throw new CommandExecutionError(`coingecko trending request failed: ${error?.message || error}`);\n        }\n        if (resp.status === 429) {\n            throw new CommandExecutionError('coingecko returned HTTP 429 (rate limited)', 'Wait and retry.');\n        }\n        if (!resp.ok) {\n            throw new CommandExecutionError(`coingecko trending failed: HTTP ${resp.status}`);\n        }\n        let data;\n        try {\n            data = await resp.json();\n        } catch (error) {\n            throw new CommandExecutionError(`coingecko returned malformed JSON: ${error?.message || error}`);\n        }\n        const coins = Array.isArray(data?.coins) ? data.coins : [];\n        if (coins.length === 0) {\n            throw new EmptyResultError('coingecko trending', 'coingecko returned no trending coins.');\n        }\n        return coins.map((entry, i) => {\n            const c = entry?.item || {};\n            return {\n                rank: i + 1,","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/coingecko/trending.js#L9-L45","documentation":"This CommandExecutionError is thrown when the CoinGecko trending endpoint responds with HTTP 429, meaning the client exceeded CoinGecko's rate limit. CoinGecko's free tier allows roughly 5-15 calls per minute per IP, so rapid repeated calls to the same endpoint trip this. The library detects the specific 429 status before the generic !resp.ok branch and attaches a remediation hint ('Wait and retry.').","triggerScenarios":"Calling the `coingecko trending` command too frequently from the same IP such that api.coingecko.com returns 429 Too Many Requests. The check is `if (resp.status === 429)` right after a successful fetch — any other non-OK status falls through to the generic HTTP error instead.","commonSituations":"CI pipelines polling trending data in a loop, cron jobs running more often than the rate-limit window, multiple scripts behind a shared NAT/proxy IP hitting the limit collectively, or running the command repeatedly during debugging.","solutions":["Wait 60+ seconds and retry — 429 is transient","Back off programmatically: catch the error and schedule a retry with exponential delay (e.g. 30s, 60s, 120s)","Cache trending results and reduce call frequency (trending data changes slowly, hourly is plenty)","Subscribe to CoinGecko's paid/demo API tier and attach an API key for higher rate limits"],"exampleFix":"// before\nfor (const _ of Array(20)) await runCli('coingecko trending'); // 429\n// after\nfor (const _ of Array(20)) {\n  await runCli('coingecko trending').catch(e => {\n    if (/429/.test(e.message)) return sleep(60000);\n    throw e;\n  });\n}","handlingStrategy":"retry","validationCode":"const MIN_INTERVAL_MS = 15000;\nlet lastCall = 0;\nfunction throttle() {\n  const wait = lastCall + MIN_INTERVAL_MS - Date.now();\n  if (wait > 0) Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, wait);\n  lastCall = Date.now();\n}\nthrottle(); // call before each coingecko command","typeGuard":"function isRateLimited(e) {\n  return /HTTP 429|rate limited/i.test(e?.message || '');\n}","tryCatchPattern":"try {\n  await runCli('coingecko trending');\n} catch (e) {\n  if (isRateLimited(e)) {\n    await new Promise(r => setTimeout(r, 60000));\n    return runCli('coingecko trending');\n  }\n  throw e;\n}","preventionTips":["Space out calls well beyond CoinGecko's free-tier limit (~5-15/min)","Cache trending results; it changes slowly — fetch at most hourly","Avoid parallel/looped invocations in CI from shared IPs","Use a CoinGecko demo/paid API key for higher limits"],"tags":["rate-limit","http-429","coingecko","retry","cli"],"backgroundTag":"http-429-rate-limited","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}