{"record":{"id":"db6275f0c5d87bcc","repo":"jackwener/OpenCLI","slug":"coingecko-returned-http-429-rate-limited-db6275","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/exchanges.js","lineNumber":45,"sourceCode":"        if (limit > 250) {\n            throw new ArgumentError('coingecko limit must be <= 250 (per_page upper bound)');\n        }\n        const page = Number(args.page ?? 1);\n        if (!Number.isInteger(page) || page <= 0) {\n            throw new ArgumentError('coingecko page must be a positive integer');\n        }\n        const url = new URL('https://api.coingecko.com/api/v3/exchanges');\n        url.searchParams.set('per_page', String(limit));\n        url.searchParams.set('page', String(page));\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 exchanges 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 exchanges returned HTTP ${resp.status}`);\n        }\n        let data;\n        try {\n            data = await resp.json();\n        }\n        catch (err) {\n            throw new CommandExecutionError(`coingecko exchanges returned malformed JSON: ${err?.message ?? err}`);\n        }\n        if (!Array.isArray(data) || !data.length) {\n            throw new EmptyResultError('coingecko exchanges', 'CoinGecko returned no exchange data.');\n        }\n        return data.map((ex, i) => ({","sourceCodeStart":27,"sourceCodeEnd":63,"githubUrl":"https://github.com/jackwener/OpenCLI/blob/49907e53dc3ade5c223ff0c4c2c2785687cec4e6/clis/coingecko/exchanges.js#L27-L63","documentation":"CoinGecko responded with HTTP 429, meaning the client exceeded the public API's rate limit (~30 calls/minute on the free tier). The library special-cases 429 and throws a CommandExecutionError with a hint to wait and retry rather than treating it as a generic HTTP error.","triggerScenarios":"Making more than ~30 requests per minute to api.coingecko.com from this key/IP — e.g. tight loops over `coingecko exchanges` pages or `coingecko global` calls without delay, or multiple scripts sharing the same egress IP.","commonSituations":"Batch jobs paginating through all exchange pages with no sleep, CI pipelines re-running frequently, several developers behind one NAT, or retry loops that hammer the API faster than the limit resets.","solutions":["Wait ~60 seconds and retry; the free-tier window resets per minute.","Add throttling/backoff between calls (e.g. 2-3 seconds sleep, or exponential backoff on 429).","Cache CoinGecko responses locally instead of re-fetching unchanged data.","Reduce call volume: fetch larger pages (limit up to 250) instead of many small pages.","Move to a paid/demo CoinGecko plan with an API key and higher limits."],"exampleFix":"// before\nfor (const p of pages) await exchanges.func({ page: p });\n// after\nfor (const p of pages) {\n  await exchanges.func({ page: p });\n  await new Promise(r => setTimeout(r, 2500)); // stay under ~30 req/min\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"async function fetchWithBackoff(fn, tries = 4) {\n  for (let i = 0; i < tries; i++) {\n    try { return await fn(); }\n    catch (e) {\n      if (!/429/.test(e.message) || i === tries - 1) throw e;\n      await sleep(Math.min(60000, 2 ** i * 1000));\n    }\n  }\n}","preventionTips":["Stay under ~30 calls/minute: sleep 2-3s between CoinGecko calls.","Batch with larger per_page (up to 250) to reduce request count.","Cache responses; global stats and exchange lists change slowly.","Never retry 429 in a tight loop — that prolongs the ban.","Consider a paid plan with an API key for production workloads."],"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"}