Finance and markets

coingecko.com

CoinGecko - Data Extraction

Notes an AI agent reads before it works on coingecko.com: what to open, what to call, what breaks. Written by browser-harness contributors after real runs, and kept on GitHub under the MIT license.

Give it to your agent

Any coding agent

Start the task with this line. The Markdown version is the same notes without the page around them.

Read https://browser-use.com/skills/coingecko.md, then <your task>

Browser Harness

Save the files into your workspace and turn domain skills on. The code below calls its helpers, such as http_get and js. What Browser Harness is.

d="${BH_AGENT_WORKSPACE:-$HOME/.config/browser-harness/agent-workspace}/domain-skills/coingecko"
mkdir -p "$d" && cd "$d"
curl -sLO https://raw.githubusercontent.com/browser-use/browser-harness/main/agent-workspace/domain-skills/coingecko/scraping.md
export BH_DOMAIN_SKILLS=1

https://api.coingecko.com/api/v3 - no API key needed for free tier. Pure JSON REST API, no browser required.

Do this first

Use the API directly with http_get - no browser, no parsing, fully structured JSON.

import json
data = json.loads(http_get("https://api.coingecko.com/api/v3/simple/price?ids=bitcoin&vs_currencies=usd"))
print(data['bitcoin']['usd'])   # 76286

Rate limit is tight: ~3 calls per minute on the free tier. The API returns HTTP 429 with Retry-After: 60 when you exceed it. Always add time.sleep(5) between calls in a loop. Confirmed: rapid-fire calls hit 429 on call 3-4 with no delay; with 5s gaps you stay safe.

Rate limits (confirmed live)

  • Free tier: ~3 calls/minute per IP (no API key)
  • 429 response: includes Retry-After: 60 header - wait 60 seconds before retrying
  • Coin ID lookup (/coins/list) counts against the limit - call it once and cache
  • /ping still counts - don't use it as a keep-alive
import time, urllib.error, json
 
def safe_get(url, retries=2):
    for attempt in range(retries + 1):
        try:
            return json.loads(http_get(url))
        except urllib.error.HTTPError as e:
            if e.code == 429 and attempt < retries:
                print(f"Rate limited, sleeping 65s...")
                time.sleep(65)
            else:
                raise

Coin ID vs symbol - critical distinction

IDs are kebab-case strings, not ticker symbols. The API ignores symbols entirely.

IntentWrongRight
Bitcoin priceids=BTCids=bitcoin
Solana priceids=SOLids=solana
Ethereumids=ETHids=ethereum
  • Unknown or wrong IDs return an empty {} dict - no error, no warning
  • Symbols are not unique: 17+ coins share the symbol sol (bridged versions, wrapped, etc.)
  • Use /coins/list to resolve symbol → id, or just know the canonical id
# Resolve symbol to id
coins_list = json.loads(http_get("https://api.coingecko.com/api/v3/coins/list"))
# 17,564 entries as of April 2026
# Each: {'id': 'bitcoin', 'symbol': 'btc', 'name': 'Bitcoin'}
sol_coins = [c for c in coins_list if c['symbol'].lower() == 'sol']
# Returns 5+ entries — pick by name to get the real Solana: id='solana'

Common workflows

Simple price (one or many coins)

import json
data = json.loads(http_get(
    "https://api.coingecko.com/api/v3/simple/price"
    "?ids=bitcoin,ethereum,solana"
    "&vs_currencies=usd,eur"
    "&include_market_cap=true"
    "&include_24hr_change=true"
))
for coin, info in data.items():
    print(f"{coin}: ${info['usd']:,.0f} | 24h: {info['usd_24h_change']:.1f}% | MCap: ${info['usd_market_cap']/1e9:.1f}B")
# bitcoin: $76,286 | 24h: 1.4% | MCap: $1528.0B
# ethereum: $2,361 | 24h: 0.8% | MCap: $284.9B
# solana: $87 | 24h: -1.0% | MCap: $50.2B

Response keys for each coin (when all flags enabled): usd, usd_market_cap, usd_24h_change, eur, eur_market_cap, eur_24h_change

Top coins by market cap (paginated)

import json
data = json.loads(http_get(
    "https://api.coingecko.com/api/v3/coins/markets"
    "?vs_currency=usd"
    "&order=market_cap_desc"
    "&per_page=10"       # max 250
    "&page=1"            # 1-indexed; page=2 gives ranks 11-20 etc.
    "&sparkline=false"
    "&price_change_percentage=1h,7d,30d"  # optional extra columns
))
for c in data:
    print(f"#{c['market_cap_rank']} {c['symbol'].upper()} ${c['current_price']:,.2f} | {c['price_change_percentage_24h']:.1f}%")
# #1 BTC $76,281.00 | 1.4%
# #2 ETH $2,360.45 | 0.8%

Full fields per entry: id, symbol, name, image, current_price, market_cap, market_cap_rank, fully_diluted_valuation, total_volume, high_24h, low_24h, price_change_24h, price_change_percentage_24h, market_cap_change_24h, market_cap_change_percentage_24h, circulating_supply, total_supply, max_supply, ath, ath_change_percentage, ath_date, atl, atl_change_percentage, atl_date, roi, last_updated

Extra columns added by price_change_percentage=1h,7d,30d: price_change_percentage_1h_in_currency, price_change_percentage_7d_in_currency, price_change_percentage_30d_in_currency

Pagination: use page=2, page=3, etc. with per_page up to 250. Results are 1-indexed - page 2 with per_page=5 returns ranks 6–10.

Coin detail (full metadata)

import json
data = json.loads(http_get(
    "https://api.coingecko.com/api/v3/coins/bitcoin"
    "?localization=false"    # skip 40+ language translations
    "&tickers=false"         # skip exchange ticker list (can be huge)
    "&market_data=true"
    "&community_data=false"
    "&developer_data=false"
))
print(data['name'])                                    # Bitcoin
print(data['symbol'])                                  # btc
print(data['market_cap_rank'])                         # 1
print(data['market_data']['current_price']['usd'])     # 76279
print(data['market_data']['ath']['usd'])               # 126080
print(data['market_data']['ath_date']['usd'])          # 2025-10-06T18:57:42.558Z
print(data['market_data']['circulating_supply'])       # 20017459.0
print(data['description']['en'][:200])

Top-level keys: id, symbol, name, web_slug, asset_platform_id, platforms, categories, description, links, image, genesis_date, sentiment_votes_up_percentage, market_cap_rank, market_data, last_updated

market_data sub-keys include: current_price, ath, ath_change_percentage, ath_date, atl, atl_change_percentage, atl_date, market_cap, fully_diluted_valuation, total_volume, high_24h, low_24h, price_change_percentage_24h, price_change_percentage_7d, price_change_percentage_14d, price_change_percentage_30d, price_change_percentage_60d, price_change_percentage_200d, price_change_percentage_1y, circulating_supply, total_supply, max_supply

All price/market fields are objects keyed by currency code: data['market_data']['current_price']['usd'], ['eur'], ['btc'], etc.

Historical OHLCV

import json
# OHLCV candles: granularity auto-determined by `days`
# 1d = 30-min candles, 7d = 4-hr candles, 14d+ = daily candles
data = json.loads(http_get(
    "https://api.coingecko.com/api/v3/coins/ethereum/ohlc?vs_currency=usd&days=7"
))
print(len(data))         # 42 candles for 7-day window
print(data[-1])          # [1776499200000, 2407.32, 2412.96, 2402.21, 2405.03]
#                         [timestamp_ms,   open,    high,    low,     close]
 
# Convert timestamp:
import datetime
ts_ms = data[-1][0]
dt = datetime.datetime.fromtimestamp(ts_ms / 1000, tz=datetime.timezone.utc)

days options: 1, 7, 14, 30, 90, 180, 365, max

Market chart (price + volume + market cap time series)

import json
# interval='daily' gives one point per day; omit for auto (hourly for <=90 days)
chart = json.loads(http_get(
    "https://api.coingecko.com/api/v3/coins/bitcoin/market_chart"
    "?vs_currency=usd&days=7&interval=daily"
))
# Keys: 'prices', 'market_caps', 'total_volumes'
# Each is a list of [timestamp_ms, value]
print(len(chart['prices']))           # 8 points for 7-day daily
print(chart['prices'][-1])            # [1776508393000, 76286.699...]
print(chart['total_volumes'][-1])     # [1776508393000, 80459560788.47...]

Market chart by date range

import json, time
now = int(time.time())
thirty_days_ago = now - 30 * 86400
chart = json.loads(http_get(
    f"https://api.coingecko.com/api/v3/coins/bitcoin/market_chart/range"
    f"?vs_currency=usd&from={thirty_days_ago}&to={now}"
))
# Granularity: <1 day → minutely, 1-90 days → hourly, >90 days → daily
print(len(chart['prices']))    # 174 points for 7-day range (hourly)
import json
results = json.loads(http_get("https://api.coingecko.com/api/v3/search?query=solana"))
# Top-level keys: 'coins', 'exchanges', 'icos', 'categories', 'nfts'
for c in results['coins'][:3]:
    print(f"{c['id']} | {c['symbol']} | rank {c['market_cap_rank']}")
# solana | SOL | rank 7
# solana-name-service | SNS | rank 1902

Search returns coins ordered by relevance, not market cap. First result is usually the canonical coin.

import json
trending = json.loads(http_get("https://api.coingecko.com/api/v3/search/trending"))
# Top-level keys: 'coins', 'nfts', 'categories'
for item in trending['coins']:
    c = item['item']
    print(f"{c['name']} ({c['symbol']}) #{c['market_cap_rank']}")
# Item keys: id, coin_id, name, symbol, market_cap_rank, thumb, small, large,
#            slug, price_btc, score, data

data sub-object includes sparkline image URL, price/volume/market cap info if available.

Global market overview

import json
global_data = json.loads(http_get("https://api.coingecko.com/api/v3/global"))
gd = global_data['data']
print(f"Total market cap: ${gd['total_market_cap']['usd']/1e12:.2f}T")   # $2.66T
print(f"24h volume: ${gd['total_volume']['usd']/1e9:.1f}B")              # $156.6B
print(f"BTC dominance: {gd['market_cap_percentage']['btc']:.1f}%")       # 57.3%
print(f"Active coins: {gd['active_cryptocurrencies']}")                   # 17,564
print(f"Active exchanges: {gd['markets']}")                               # 1,475

Coin categories (market cap by sector)

import json
cats = json.loads(http_get(
    "https://api.coingecko.com/api/v3/coins/categories?order=market_cap_desc"
))
# 691 categories as of April 2026
for cat in cats[:5]:
    print(f"{cat['name']}: ${cat['market_cap']/1e9:.1f}B | 24h: {cat['market_cap_change_24h']:.1f}%")
# Smart Contract Platform: $2204.8B | 24h: 0.9%
# Layer 1 (L1): $2171.5B | 24h: 1.1%
 
# Category keys: id, name, market_cap, market_cap_change_24h, content,
#                top_3_coins_id, top_3_coins, volume_24h, updated_at

Token price by contract address (ERC-20 and other chains)

import json
# Platform IDs: ethereum, binance-smart-chain, polygon-pos, avalanche, solana, etc.
token = json.loads(http_get(
    "https://api.coingecko.com/api/v3/simple/token_price/ethereum"
    "?contract_addresses=0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48"  # USDC
    "&vs_currencies=usd"
))
print(token)
# {'0xa0b86991c6218b36c1d19d4a2e9eb0ce3606eb48': {'usd': 0.999861}}
# Key is the lowercased contract address

vs_currencies options

63 currencies supported (confirmed live). Common ones:

Fiat: usd, eur, gbp, jpy, aud, cad, chf, cny, inr, krw, brl, mxn, sgd, hkd, nok, sek, dkk, nzd, zar, thb, try, aed, sar, myr, php, idr, pln, czk, huf, ron

Crypto: btc, eth, ltc, bch, bnb, eos, xrp, xlm, link, dot, yfi, sol

Commodities: xag (silver), xau (gold)

Get the full list:

currencies = json.loads(http_get("https://api.coingecko.com/api/v3/simple/supported_vs_currencies"))
# Returns list of 63 strings

Endpoints that require Pro API (return HTTP 401)

  • /coins/{id}/history?date=DD-MM-YYYY - historical price on a specific date
  • /coins/markets with category= filter (the parameter is silently ignored, not 401)
  • /coins/{id}/contract/{address} - full contract token details

Free tier alternatives:

  • For historical price on date: use /market_chart/range with a narrow time window
  • For category filtering: fetch /coins/markets unfiltered and filter client-side using id from /coins/categories

Ping / health check

import json
ping = json.loads(http_get("https://api.coingecko.com/api/v3/ping"))
print(ping)   # {'gecko_says': '(V3) To the Moon!'}

Note: ping still counts against the rate limit. Don't use it to check if a 429 has resolved - just wait 65 seconds and retry your actual call.

Gotchas

  • Rate limit is much stricter than advertised - The official docs say "30 calls/min" but in practice you get 429 on call 3-4 with no delay between calls. Observed Retry-After: 60 in the response header. Treat it as "3 calls/minute, wait 65s on 429." Using time.sleep(5) between calls in a loop is safe.

  • Unknown coin IDs return {}, not an error - ?ids=BTC (uppercase) and ?ids=not_a_real_coin both return an empty dict {}. Always check that the key you expect exists before accessing it.

  • Symbol lookup requires /coins/list + client-side filter - There's no "get by symbol" endpoint. Multiple coins share any given symbol. After fetching the list (17,564 entries), filter by symbol and pick by name.

  • Coin ID casing matters - IDs are always lowercase kebab-case: bitcoin, ethereum, shiba-inu. Uppercase or camelCase will silently return {}.

  • OHLCV granularity is automatic - The days parameter determines candle size automatically: 1 → 30-min candles, 7/14 → 4-hr candles, 30+ → daily candles. You cannot override this on the free tier.

  • interval=daily in market_chart affects point count - Without interval=daily, a 7-day window returns hourly data (~168 points). With it, you get ~8 points. Choose based on whether you need resolution or summary.

  • market_chart timestamps are in milliseconds - Divide by 1000 for standard Unix time: datetime.fromtimestamp(ts / 1000).

  • /coins/list is expensive (rate-limit-wise) - It returns 17,564 entries and costs one API call. Fetch once, store in a variable, filter locally. Don't call it in a loop.

  • Pagination is 1-indexed - page=1 returns items 1–N, page=2 returns N+1–2N. page=0 returns the same as page=1 (it doesn't error).

  • per_page max is 250 - Requesting more than 250 per page silently returns 250. To get the full top-500, make two calls: page=1&per_page=250 then page=2&per_page=250.

  • Contract address keys are lowercased - When using /simple/token_price, the response key is the lowercased contract address regardless of what case you sent. Always call .lower() before using addresses as dict keys.

  • tickers=false is important for /coins/{id} - Without it, the response includes a massive list of exchange tickers that can make the payload very large and slow to parse. Always set tickers=false unless you specifically need exchange data.

  • ETH priced against BTC is supported - vs_currencies=btc works: ethereum returns {'btc': 0.03095861}. Crypto-to-crypto pairs work the same as fiat pairs.

scraping.md · updated Apr 28, 2026 · 1,667 words

Sites change. These notes describe what worked when they were last updated, not a guarantee they still do. If something broke, the fix is a pull request to browser-harness.