{"record":{"id":"580de17b0f4dc37d","repo":"jackwener/OpenCLI","slug":"coingecko-returned-http-429-rate-limited-580de1","errorCode":null,"errorMessage":"coingecko returned HTTP 429 (rate limited)","messagePattern":"coingecko returned HTTP 429 \\(rate limited\\)","errorType":"http","errorClass":"CommandExecutionError","httpStatus":429,"severity":"warning","filePath":"clis/coingecko/global.js","lineNumber":31,"sourceCode":"    browser: false,\n    args: [\n        { name: 'currency', type: 'string', default: 'usd', help: 'Quote currency for total market cap / volume (usd, cny, eur, jpy, ...)' },\n    ],\n    columns: ['currency', 'totalMarketCap', 'totalVolume24h', 'marketCapChange24hPct', 'btcDominancePct', 'ethDominancePct', 'activeCryptocurrencies', 'markets', 'ongoingIcos', 'updatedAt'],\n    func: async (args) => {\n        const currency = String(args.currency ?? 'usd').trim().toLowerCase();\n        if (!/^[a-z0-9-]{2,20}$/.test(currency)) {\n            throw new ArgumentError(`coingecko currency must look like a currency slug (got \"${args.currency}\")`);\n        }\n        let resp;\n        try {\n            resp = await fetch('https://api.coingecko.com/api/v3/global', { headers: { 'User-Agent': 'Mozilla/5.0' } });\n        }\n        catch (err) {\n            throw new CommandExecutionError(`coingecko global 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 global returned HTTP ${resp.status}`);\n        }\n        let body;\n        try {\n            body = await resp.json();\n        }\n        catch (err) {\n            throw new CommandExecutionError(`coingecko global returned malformed JSON: ${err?.message ?? err}`);\n        }\n        const data = body?.data;\n        if (!data) {\n            throw new CommandExecutionError('coingecko global returned no data envelope');\n        }","sourceCodeStart":13,"sourceCodeEnd":49,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/coingecko/global.js#L13-L49","documentation":"CoinGecko's /api/v3/global endpoint responded with HTTP 429, indicating the free-tier rate limit (~30 calls/minute per IP/key) has been exceeded. The library raises a dedicated CommandExecutionError with the suggestion to wait and retry, distinguishing it from other HTTP failures.","triggerScenarios":"More than ~30 requests per minute to api.coingecko.com from the same IP — e.g. monitoring loops polling `coingecko global` every second, shared CI egress IPs, or other CoinGecko tooling on the same network consuming the quota.","commonSituations":"Dashboards auto-refreshing market-cap data too often, cron jobs running every few seconds, retry storms amplifying load after an initial failure.","solutions":["Wait about 60 seconds for the per-minute window to reset, then retry.","Throttle polling: CoinGecko global stats change slowly — poll at most every 1-5 minutes.","Implement exponential backoff with jitter on 429 in automation.","Cache the last successful result and serve from cache between refreshes.","Upgrade to a CoinGecko paid/demo plan with an API key for higher limits."],"exampleFix":"// before\nsetInterval(() => globalCmd.func({}), 5000);\n// after\nsetInterval(() => globalCmd.func({}).catch(e => log(e)), 5 * 60 * 1000); // 5 min","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function getGlobalSafe() {\n  try { return await globalCmd.func({ currency: 'usd' }); }\n  catch (e) {\n    if (/429/.test(e.message)) {\n      await sleep(60000);           // wait out the per-minute window\n      return globalCmd.func({ currency: 'usd' });\n    }\n    throw e;\n  }\n}","preventionTips":["Poll global stats at most every 1-5 minutes — the data changes slowly.","Share one scheduler for all CoinGecko calls so quotas aren't consumed independently.","Cache last successful results and serve stale data on 429.","Use exponential backoff with jitter; never retry immediately in a loop.","Move to an API-key plan for anything approaching production frequency."],"tags":["rate-limit","http-429","throttling"],"backgroundTag":"rate-limit-exceeded","analyzedSha":"49907e53dc3ade5c223ff0c4c2c2785687cec4e6","analyzedAt":"2026-08-29T08:14:47.543Z","schemaVersion":2},"datasetVersion":"2026-08-29T12:17:43.993Z"}