jackwener/OpenCLI · warning · CommandExecutionError

coingecko returned HTTP 429 (rate limited)

Error message

coingecko returned HTTP 429 (rate limited)

What it means

This CommandExecutionError is thrown when CoinGecko responds with HTTP 429, indicating the client exceeded the public API rate limit. The free/unauthenticated tier allows roughly 30 calls per minute, so bursts of CLI invocations (loops over many coins) commonly trip it. The remediation hint is attached as a second argument.

Source

Thrown at clis/coingecko/coin.js:57

        const url = new URL(`https://api.coingecko.com/api/v3/coins/${id}`);
        url.searchParams.set('localization', 'false');
        url.searchParams.set('tickers', 'false');
        url.searchParams.set('market_data', 'true');
        url.searchParams.set('community_data', 'false');
        url.searchParams.set('developer_data', 'false');
        url.searchParams.set('sparkline', 'false');

        let resp;
        try {
            resp = await fetch(url, { headers: { 'User-Agent': 'Mozilla/5.0' } });
        } catch (error) {
            throw new CommandExecutionError(`coingecko coin request failed: ${error?.message || error}`);
        }
        if (resp.status === 404) {
            throw new EmptyResultError('coingecko coin', `coingecko has no coin with id "${id}".`);
        }
        if (resp.status === 429) {
            throw new CommandExecutionError('coingecko returned HTTP 429 (rate limited)', 'Free tier allows ~30 calls/min. Wait and retry.');
        }
        if (!resp.ok) {
            throw new CommandExecutionError(`coingecko coin failed: HTTP ${resp.status}`);
        }
        let data;
        try {
            data = await resp.json();
        } catch (error) {
            throw new CommandExecutionError(`coingecko returned malformed JSON: ${error?.message || error}`);
        }
        if (data?.error) {
            throw new CommandExecutionError(`coingecko returned error: ${data.error}`);
        }

        const md = data.market_data || {};
        const pick = (obj, key) => (obj && obj[key] != null ? obj[key] : null);
        const isoFromMaybe = (s) => (s ? String(s).slice(0, 10) : '');
        const price = pick(md.current_price, currency);

View on GitHub (pinned to 49907e53dc)

Solutions

  1. Wait ~60 seconds and retry with backoff
  2. Add throttling (e.g. 2+ second sleep between calls) when looping over multiple coins
  3. Get a free demo/paid CoinGecko API key and use the pro endpoint for higher limits

Example fix

// before
for (const id of ids) await run(['coingecko', 'coin', id]);
// after
for (const id of ids) { await run(['coingecko', 'coin', id]); await new Promise(r => setTimeout(r, 2500)); }
Defensive patterns

Strategy: retry

Validate before calling

// throttle before each call in loops
await new Promise(r => setTimeout(r, 2500)); // keeps under ~30 calls/min

Type guard

null

Try / catch

try { await run(['coingecko', 'coin', id]); } catch (e) { if (/HTTP 429/.test(e.message)) { await sleep(60000); return retryOnce(); } throw e; }

Prevention

When it happens

Trigger: Calling `coingecko coin` more than ~30 times per minute from a shared IP; scripted loops over many coins; multiple processes/users behind one NAT; the 404/429 branch order means this only fires on a 429 status.

Common situations: Batch scripts fetching prices for a portfolio of hundreds of coins without delays; CI jobs hitting the shared public API; other apps on the same network consuming the same rate budget.

Understand the failure class

Related errors


AI-assisted analysis of jackwener/OpenCLI@49907e53dc (2026-08-29). Data as JSON: /api/errors/224971ea84d6a574. Report an issue: GitHub.