Quick answer
CoinMarketCap's own ranked-listing endpoint stamps a lastUpdated timestamp on every coin — but if you ask for a currency other than USD, that currency's quote carries its own, separately-clocked lastUpdated, and it can trail the coin's own timestamp by over a minute. We caught it live: BTC, ETH, USDT, and BNB's EUR quotes were all stamped 2026-09-12T08:29:04Z, while the coins themselves (and their USD quotes) were stamped 2026-09-12T08:28:00Z — a 64-second gap, on a single API call, for four of the market's largest coins simultaneously.
Watching the clock split live, on the same response 🕐
CoinMarketCap's site renders its ranked table from an internal, keyless JSON endpoint. This Actor calls the same one. Here's a real request for the top coins, asking for both USD and EUR in one call:
from curl_cffi.requests import Session
s = Session(impersonate="chrome131")
r = s.get(
"https://api.coinmarketcap.com/data-api/v3/cryptocurrency/listing",
params={"start": 1, "limit": 10, "sortBy": "market_cap", "sortType": "desc",
"convert": "USD,EUR", "cryptoType": "all", "tagType": "all", "audited": "false"},
)
coins = r.json()["data"]["cryptoCurrencyList"]
for c in coins[:4]:
quotes = {q["name"]: q["lastUpdated"] for q in c["quotes"]}
print(c["symbol"], "coin:", c["lastUpdated"], "| USD quote:", quotes["USD"], "| EUR quote:", quotes["EUR"])
Run it, and here's the actual output, unedited:
BTC coin: 2026-09-12T08:28:00.000Z | USD quote: 2026-09-12T08:28:00.000Z | EUR quote: 2026-09-12T08:29:04.000Z
ETH coin: 2026-09-12T08:28:00.000Z | USD quote: 2026-09-12T08:28:00.000Z | EUR quote: 2026-09-12T08:29:04.000Z
USDT coin: 2026-09-12T08:28:00.000Z | USD quote: 2026-09-12T08:28:00.000Z | EUR quote: 2026-09-12T08:29:04.000Z
BNB coin: 2026-09-12T08:28:00.000Z | USD quote: 2026-09-12T08:28:00.000Z | EUR quote: 2026-09-12T08:29:04.000Z
The coin-level lastUpdated field and the USD quote's lastUpdated always agree — USD is CMC's reference currency, computed at the same instant the coin's own record refreshes. But EUR (and every other non-USD convert currency) comes from a separate FX-conversion pass with its own cadence. We fetched EUR alone, with no USD in the request at all, and got the identical 08:29:04.000Z stamp on the same four coins — confirming it's not an artifact of asking for two currencies together, it's how CMC's conversion service actually runs. The gap isn't fixed, either: XRP, in the same response, showed only a 4-second gap, because XRP's own base record had refreshed one minute later than BTC's. The lag is real and it moves with whichever currency's conversion batch last ran — not with the coin's own price update.
Ask for GBP, or JPY, or any of the ~90 currencies CMC's UI offers in its selector, and you're reading a price that was true as of a different moment than the row's headline lastUpdated claims. For a dashboard refreshed every few minutes this is invisible noise. For anything doing cross-currency arithmetic against an "as-of" timestamp — reconciling a EUR-denominated position against a USD benchmark at a specific second, for instance — it's the kind of gap that produces a number that's defensible but wrong.
The second trap: a typo'd currency code fails silently, not loudly 🔇
We went looking for what happens when convert asks for a currency CMC doesn't actually support — a typo, or a code from a different API's currency list. No error, no 4xx, no warning field in the response. The endpoint just quietly drops it:
r = s.get(url, params={**params, "convert": "USD,ZZZ", "limit": 3})
for c in r.json()["data"]["cryptoCurrencyList"]:
print(c["symbol"], [q["name"] for q in c["quotes"]])
# BTC ['USD']
# ETH ['USD']
# USDT ['USD']
ZZZ matches the shape of a real currency code — three uppercase letters — so a validator checking only the pattern (which is exactly what this Actor's own convert field validator does, by design: CMC accepts an open set of codes and rejects nothing loudly) will wave it through. You won't find out your convert list had a dead entry from an error. You'll find out because your row's quotes object is one key shorter than you expected, and only if you were counting.
What we handle for you 🛡️
Every request goes out with a real Chrome TLS fingerprint via curl-cffi impersonation, routed through Apify Proxy, and retried with exponential backoff on 408/429/5xx — up to 5 attempts, honouring Retry-After when CMC sends one. If CMC's undocumented endpoint ever changes shape entirely — a missing data object, a non-zero error_code — we fail the run loud rather than ship you empty or garbage rows; if instead a single coin object in an otherwise-healthy response is malformed, we skip that one coin, log it, and let the rest of your run land. And quotes comes back as a currency-keyed object, not a row-per-currency list — so asking for USD, EUR, and GBP together never triples your billed rows for one coin.
Output
{
"cmcRank": 1,
"coinId": 1,
"name": "Bitcoin",
"symbol": "BTC",
"slug": "bitcoin",
"tags": ["mineable", "pow", "sha-256", "store-of-value"],
"isActive": true,
"dateAdded": "2010-07-13T00:00:00.000Z",
"lastUpdated": "2026-09-12T08:28:00.000Z",
"marketPairCount": 12736,
"circulatingSupply": 20083237.0,
"totalSupply": 20083237.0,
"maxSupply": 21000000.0,
"quotes": {
"USD": {
"price": 77286.84526948321,
"volume24h": 31751187211.703285,
"marketCap": 1552170030529.36,
"percentChange1h": -0.01045304,
"percentChange24h": -0.01407739,
"percentChange7d": -2.96964527,
"percentChange30d": 21.26961017,
"dominance": 58.684
}
},
"coinmarketcapUrl": "https://coinmarketcap.com/currencies/bitcoin/",
"scrapedAt": "2026-09-12T08:30:00Z"
}
from apify_client import ApifyClient
client = ApifyClient("<YOUR_API_TOKEN>")
run = client.actor("DevilScrapes/coinmarketcap-crypto-scraper").call(
run_input={"limit": 100, "sortBy": "market_cap", "convert": ["USD", "EUR"]}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["cmcRank"], item["symbol"], item["quotes"]["USD"]["price"])
FAQ
Does this need a CoinMarketCap API key? No — it calls the same keyless endpoint CMC's own site uses to render its ranked table.
Will requesting more currencies cost more per coin? No. quotes is keyed by currency inside one row; you're billed once per coin regardless of how many currencies you ask for (max 3 per run, USD always included).
Is the lastUpdated field trustworthy? For the coin record and its USD quote, yes — they're stamped together. For any other convert currency, treat that quote's freshness as independent; CMC computes it on its own schedule, sometimes over a minute apart from the coin's own refresh, as shown above.
Pricing: $0.20 per run plus $0.003 per coin row — $3.00 per 1 000 results (≈$3.20 for 1,000 rows including the one-off start fee).
→ CoinMarketCap Crypto Scraper on Apify
Built by Devil Scrapes. We rotate real browser TLS fingerprints, retry with backoff, and go check what an undocumented endpoint actually does with a typo'd parameter before your pipeline finds out the hard way. → Try CoinMarketCap Crypto Scraper
Top comments (0)