How to Scrape CoinMarketCap Crypto Prices
Scrape CoinMarketCap crypto prices, market cap, and historical data using the official API, the hidden data-api endpoints, and the __NEXT_DATA__ payload.

You can scrape CoinMarketCap for coin metadata, live prices, market cap, and historical OHLCV, but the naive approach (point BeautifulSoup at the ranking table) returns an empty page. CoinMarketCap runs on Next.js and sits behind Cloudflare: the table hydrates in the browser and paginates through an internal JSON API, so the HTML you download rarely holds the numbers you came for. This guide walks the three real data paths in the order you should reach for them. First the official CoinMarketCap API as the compliant choice, then the site's own data-api endpoints for what the free tier leaves out, then the __NEXT_DATA__ payload embedded in every page. Working Python is included, along with the Cloudflare and rate-limit handling that keeps a collector alive.
What You Can Pull from CoinMarketCap
CoinMarketCap tracks four broad kinds of data, and each has a cleanest source. Picking the wrong source is why most scrapers break.
| Data you want | Cleanest source | Needs an API key? |
|---|---|---|
| Coin list ranked by market cap | Official `listings/latest` or internal `data-api` listing | Official yes, internal no |
| Live price, market cap, 24h volume | Official `quotes/latest` or internal `data-api` | Official yes, internal no |
| Historical OHLCV / price series | Official `ohlcv/historical` (paid) or internal chart endpoint | Depends |
| Coin metadata (logo, links, supply) | Official `info` or `__NEXT_DATA__` on the coin page | Official yes, page no |
The rule of thumb: if the official API returns the field on your plan, use it. It is documented, versioned, and stays inside the terms. Drop to the internal endpoints only for the specific things the free plan gates off, and treat those as brittle. If your goal is broader than CoinMarketCap alone, the wider workflow across exchanges, on-chain explorers, and NFT feeds lives in proxies for crypto data.
Start With the Official API
Before you scrape anything, get a free key from the CoinMarketCap developer portal. The official API is the sanctioned path: stable JSON, clear docs, and no fight with Cloudflare. It follows the same official-API-first logic covered in how to scrape stock market data.
The base URL is https://pro-api.coinmarketcap.com and every call authenticates with the X-CMC_PRO_API_KEY header. A latest-quote lookup for Bitcoin and Ethereum is a few lines:
import requests
BASE = "https://pro-api.coinmarketcap.com"
headers = {"X-CMC_PRO_API_KEY": "YOUR_CMC_KEY", "Accept": "application/json"}
r = requests.get(
f"{BASE}/v2/cryptocurrency/quotes/latest",
headers=headers,
params={"symbol": "BTC,ETH", "convert": "USD"},
)
btc = r.json()["data"]["BTC"][0]["quote"]["USD"]
print(btc["price"], btc["market_cap"], btc["percent_change_24h"])
For the ranked table (the front page of CoinMarketCap), call listings/latest and page through it with start and limit:
r = requests.get(
f"{BASE}/v1/cryptocurrency/listings/latest",
headers=headers,
params={"start": "1", "limit": "100", "convert": "USD", "sort": "market_cap"},
)
for coin in r.json()["data"]:
q = coin["quote"]["USD"]
print(coin["cmc_rank"], coin["symbol"], q["price"], q["market_cap"])
One endpoint is worth learning early: /v1/cryptocurrency/map. It returns the CoinMarketCap numeric ID for every coin. That ID is your stable join key, because symbols collide (there are dozens of coins ticking "SOL" or "UNI" across forks and scams) and slugs occasionally change. Map once, cache it, and key everything on the ID.
Scraping at scale? Skip the blocks.
Fast, unblockable datacentre proxies with unlimited bandwidth.
What the Free API Tier Leaves Out
The free Basic plan is generous for live data and stingy for history. Two limits catch people out.
It is credit-based, not request-based. The Basic plan ships on the order of 10,000 call credits a month with a per-minute cap. CoinMarketCap has revised these numbers over time, so confirm the current figure on the pricing page, but the model matters more than the exact count. Credits are not requests. CoinMarketCap bills roughly one credit per 200 data points returned, and each extra convert currency multiplies the cost. A single listings/latest call for 5,000 coins in three fiat currencies can burn dozens of credits, not one. Request only the coins and conversions you need.
Historical OHLCV is gated. The ohlcv/historical and historical-quote endpoints have traditionally been reserved for paid tiers. Call one on a free key and you get a clean rejection rather than data:
r = requests.get(
f"{BASE}/v2/cryptocurrency/ohlcv/historical",
headers=headers,
params={"symbol": "BTC", "time_start": "2025-01-01",
"time_end": "2025-02-01", "interval": "daily", "convert": "USD"},
)
status = r.json()["status"]
print(status["error_code"], status["error_message"])
# 1006 "Your API Key subscription plan doesn't support this endpoint."
Those error_code values are worth memorizing because they tell you exactly what went wrong: 1006 means your plan cannot touch that endpoint, 1008 is the per-minute rate limit, 1010 is your monthly credit budget spent, and 1011 is an IP-level limit. When you need historical series without paying, the internal chart endpoint below returns the same data the coin page draws.
The Internal data-api Endpoints
The CoinMarketCap website fetches its own data from https://api.coinmarketcap.com/data-api/. These endpoints power the live site, take no API key, and return clean JSON. They are also undocumented, unversioned in practice, and can be renamed or removed without notice. Use them for the fields the free key gates off, and pin your parsing to what you observe today. The general discovery method (open DevTools, watch the Network tab, filter to Fetch/XHR, and read the request the page fires) is covered in depth in how to scrape hidden JSON API endpoints.
The listing endpoint is the internal twin of listings/latest:
import requests
url = "https://api.coinmarketcap.com/data-api/v3/cryptocurrency/listing"
params = {"start": 1, "limit": 100, "sortBy": "market_cap",
"sortType": "desc", "convert": "USD", "cryptoType": "all"}
r = requests.get(url, params=params,
headers={"User-Agent": "Mozilla/5.0", "Accept": "application/json"})
for c in r.json()["data"]["cryptoCurrencyList"]:
quote = c["quotes"][0]
print(c["cmcRank"], c["symbol"], quote["price"], quote["marketCap"])
For history, the coin detail page reads a chart endpoint. Pass the CoinMarketCap ID and a range such as 1D, 7D, 1M, 1Y, or ALL:
url = "https://api.coinmarketcap.com/data-api/v3/cryptocurrency/detail/chart"
r = requests.get(url, params={"id": 1, "range": "1Y"}, # id 1 = Bitcoin
headers={"User-Agent": "Mozilla/5.0"})
points = r.json()["data"]["points"]
# points maps a unix timestamp to a value array: [price, volume, marketCap, ...]
for ts, point in list(points.items())[:3]:
print(ts, point["v"][0])
Note the camelCase keys (cmcRank, marketCap, volume24h) versus the official API's snake_case (cmc_rank, market_cap, volume_24h). They are the same numbers under different names, so normalize both into one internal schema if you mix sources. The response shapes above are accurate as of writing; verify them in DevTools before you rely on them, because these are the endpoints most likely to shift.
Reading the __NEXT_DATA__ Payload
CoinMarketCap is built on Next.js. Every server-rendered page ships a tag holding the props the page was rendered with. For the first screen of data (a coin's current stats, the top slice of a listing), you can read that blob straight out of the HTML with no API call at all. This is also why a plain HTML scrape of the ranking table comes back empty: the rows are hydrated from this payload and from XHR calls after the page loads, not baked into the markup.
import json, requests
from bs4 import BeautifulSoup
html = requests.get("https://coinmarketcap.com/currencies/bitcoin/",
headers={"User-Agent": "Mozilla/5.0"}).text
soup = BeautifulSoup(html, "html.parser")
blob = soup.find("script", id="__NEXT_DATA__").string
data = json.loads(blob)
# Next.js stashes server props under props.pageProps. The exact key path
# shifts between releases, so print the tree once and pin your path.
print(json.dumps(data["props"]["pageProps"], indent=2)[:600])
The __NEXT_DATA__ route is the most fragile of the three, because the internal key structure changes whenever CoinMarketCap ships a frontend release. It is genuinely useful for a single coin's on-page snapshot, and a poor choice for paginated or historical collection. For those, the data-api endpoints are steadier. The general pattern of extracting hydrated JSON from JavaScript-heavy pages is expanded in how to scrape dynamic JavaScript websites.
Fields You Can Extract
These are the fields worth pulling, with the key name in each source so you can map between them.
| Field | Official API | Internal data-api | Meaning |
|---|---|---|---|
| CoinMarketCap ID | `id` | `id` | Stable numeric join key |
| Symbol | `symbol` | `symbol` | Ticker, e.g. BTC |
| Slug | `slug` | `slug` | URL slug, e.g. bitcoin |
| Rank | `cmc_rank` | `cmcRank` | Position by market cap |
| Price | `quote.USD.price` | `quotes[].price` | Last price in convert currency |
| Market cap | `quote.USD.market_cap` | `quotes[].marketCap` | Price times circulating supply |
| Fully diluted cap | `quote.USD.fully_diluted_market_cap` | `quotes[].fullyDilutedMarketCap` | Price times max supply |
| 24h volume | `quote.USD.volume_24h` | `quotes[].volume24h` | Trailing 24h traded value |
| % change 1h/24h/7d | `quote.USD.percent_change_*` | `quotes[].percentChange*` | Momentum windows |
| Circulating supply | `circulating_supply` | `circulatingSupply` | Coins live in the market |
| Total / max supply | `total_supply` / `max_supply` | `totalSupply` / `maxSupply` | Issued and hard-cap supply |
Two practical notes. Market cap equals price times circulating supply, so if you compute it yourself from a scraped price you will disagree with CoinMarketCap whenever their supply figure updates, which is often. Prefer their reported market_cap over recomputing it. And fully_diluted_market_cap uses max supply, which is null for coins with no hard cap, so guard for None before you sort on it.
Cloudflare and Rate Limits
Both coinmarketcap.com and the data-api subdomain sit behind Cloudflare. That has two consequences for a scraper.
First, a plain client can get challenged. Cloudflare fingerprints your TLS handshake (JA3/JA4), not just your headers, so a perfect User-Agent on a stock Python requests session still looks like a bot and can draw an HTTP 403 with a challenge page instead of JSON. The fixes, in rising order of effort, are a fingerprint-matching client like curl_cffi with impersonate="chrome", a real headless browser, or a managed scraping API that handles the handshake for you.
Second, both the API and the site enforce per-IP rate limits and answer overflow with HTTP 429. Back off on failure instead of hammering:
import time, requests
RETRY = {403, 429, 500, 502, 503, 504}
def fetch(url, params=None, tries=4):
for n in range(tries + 1):
r = requests.get(url, params=params, timeout=20,
headers={"User-Agent": "Mozilla/5.0",
"Accept": "application/json"})
if r.status_code == 200:
return r
if r.status_code not in RETRY or n == tries:
r.raise_for_status()
time.sleep(min(2 ** n, 30)) # 1s, 2s, 4s, 8s, capped at 30
return None
Three habits keep you unblocked: cache the ID map and anything that does not move minute to minute, space requests out rather than firing bursts, and rotate the source IP once you cross a few hundred calls so no single address trips a threshold. The full anti-block playbook is in how to avoid getting your proxy blocked.
Geo-Targeting and Avoiding Rate Blocks
Rate limits on CoinMarketCap are counted per IP, so the single most effective way to collect at volume without a block is to spread requests across many addresses. That is what rotating proxies buy you: each request leaves from a different exit, and no one IP crosses the per-minute or per-hour ceiling that triggers a 429 or a Cloudflare challenge.
Setting a country_code on the exit also stabilizes what you get back. Cloudflare's edge behaves differently by region, and a consistent exit geography makes your traffic look like an ordinary user in one place rather than a client teleporting between continents mid-session. Datacenter proxies are fast and cheap and handle the JSON data-api endpoints well. Move to residential IPs when Cloudflare tightens on the HTML pages and datacenter ranges start drawing challenges.
Scrape CoinMarketCap with the SparkProxy Scraping API
Matching TLS fingerprints, rotating IPs, and clearing Cloudflare challenges is a lot of plumbing to own. The SparkProxy Scraping API folds it into one request: you send a target URL, it picks and rotates the proxy, solves the anti-bot layer, renders JavaScript when needed, and returns the response. Because the internal data-api endpoints already return JSON, you can point the Scraping API straight at one and let it handle Cloudflare and IP rotation for you.
curl -G "https://scrape.sparkproxy.io/api/v1" \
-H "X-API-Key: YOUR_API_KEY" \
--data-urlencode "url=https://api.coinmarketcap.com/data-api/v3/cryptocurrency/listing?start=1&limit=100&convert=USD" \
--data-urlencode "render_js=false" \
--data-urlencode "country_code=us"
The same call in Python, requesting residential IPs for the tougher moments:
import requests
from urllib.parse import urlencode
cmc = "https://api.coinmarketcap.com/data-api/v3/cryptocurrency/listing?" + urlencode({
"start": 1, "limit": 200, "sortBy": "market_cap",
"sortType": "desc", "convert": "USD", "cryptoType": "all",
})
resp = requests.get(
"https://scrape.sparkproxy.io/api/v1",
headers={"X-API-Key": "YOUR_API_KEY"},
params={
"url": cmc,
"render_js": "false", # it is a JSON endpoint, no browser needed
"country_code": "us", # consistent exit geography
"premium_proxy": "true", # residential IPs when Cloudflare tightens up
},
timeout=90,
)
resp.raise_for_status()
coins = resp.json()["data"]["cryptoCurrencyList"] # upstream JSON is returned as-is
print(len(coins), "coins")
By default the API returns the upstream response body unchanged, so resp.json() here is CoinMarketCap's own payload. Set json_response=true if you would rather receive SparkProxy's metadata envelope (status code, credits used) wrapping the content. For the HTML coin pages, add render_js=true so the page hydrates before capture, then parse __NEXT_DATA__ as shown earlier. Keep render_js=false on the JSON endpoints to keep each call cheap, and reserve premium_proxy for targets that actually challenge datacenter IPs. Whether a managed API beats running your own pool comes down to volume and how hard the target fights back; the web scraping API vs self-managed proxies breakdown gives you a break-even to run against your own numbers.
Ethics, ToS, and Attribution
Reachable is not the same as permitted, and CoinMarketCap's data carries real strings.
- The data is licensed. CoinMarketCap's terms restrict automated access to the site and govern reuse of its data. The sanctioned route is the official API under its plan terms. The Basic (free) plan is for personal, non-commercial use; a commercial product needs a paid commercial plan.
- Attribution is required when you display the data. CoinMarketCap's API terms ask you to credit them (a visible "Powered by CoinMarketCap") wherever you show their numbers. Honor it.
- Undocumented does not mean unowned. The
data-apiendpoints and__NEXT_DATA__are public in the sense that a browser reaches them, but pulling from them at scale can still breach the site terms. In hiQ Labs v. LinkedIn the Ninth Circuit held that scraping publicly visible data does not by itself violate the US Computer Fraud and Abuse Act, yet that ruling does not touch contract terms or copyright, which is where the license lives. - Keep a light footprint. Respect
robots.txt, cache aggressively, throttle, and never present scraped figures as a real-time licensed feed. Personal research and backtesting sit very differently from reselling a data product built on scraped prices.
When in doubt, pay for the API tier that covers your use. It is cheaper than a dispute, and steadier than an endpoint that can vanish on the next frontend deploy.
Frequently asked questions
FAQ
Scraping public pages is generally legal for research, and hiQ v. LinkedIn held that collecting publicly visible data does not by itself violate the US anti-hacking law. That does not override CoinMarketCap's terms of service or the license on its data, so the compliant path is the official API under its plan terms, with attribution when you display the numbers. Undocumented endpoints are reachable, but using them at scale can still breach the site terms.
The free Basic plan is credit-based and aimed at personal use, and historical OHLCV endpoints have traditionally been reserved for paid tiers. Calling a gated endpoint on a free key returns error_code 1006, meaning your plan is not authorized. If you only need recent history for a few coins, the site's internal chart endpoint returns the same series the coin page draws, though it is undocumented and can change.
Two routes work without a key: the internal data-api endpoints the website itself calls (visible in your browser's DevTools under Network, Fetch/XHR), and the __NEXT_DATA__ JSON that CoinMarketCap embeds in every server-rendered page. Both sit behind Cloudflare and neither is documented, so treat their shapes as unstable and pin your parsing to what you see today.
CoinMarketCap is a Next.js app, so the ranking table is hydrated in the browser and paginated through a JSON API. When you download the raw HTML with requests, the table rows are not there yet. Read the __NEXT_DATA__ script tag for the first page of data, or call the data-api listing endpoint directly instead of parsing the visible table.
For a few hundred calls, one IP is usually fine. Past that, Cloudflare and per-IP rate limits start returning 403 and 429 responses, so rotating proxies spread the load across addresses. Datacenter proxies handle the JSON endpoints well; switch to residential IPs and set a country_code when Cloudflare tightens up on a target.
api.coinmarketcap.com/data-api is the internal API the CoinMarketCap website uses to fetch its own data. Paths such as /v3/cryptocurrency/listing (the ranked table) and /v3/cryptocurrency/detail/chart (historical series) return clean JSON without an API key. They are undocumented and unversioned in practice, so CoinMarketCap can rename or remove them at any time.
Get 20% off your first month
Premium datacentre proxies with unlimited bandwidth. Use the code at checkout.
Save up to 15% more on quarterly, half-yearly and yearly plans
Related articles
How to Bypass Radware Bot Manager When Scraping
Bypass Radware Bot Manager the honest way: confirm it by its __uzm cookies and stormcaster.js, read its crypto challenge, and scrape public data in code.
How to Identify Which Anti-Bot Protection a Site Uses
Identify anti-bot protection fast: the cookie names, response headers, block pages and challenge scripts that name Cloudflare, Akamai, DataDome and Kasada.

Paying for Proxies: Cards, Crypto and Invoicing Compared
How to pay for proxies without losing your recourse: what each rail costs, which can be reversed, why crypto discounts exist, and what finance actually needs.
