BlockMarketScan API
Real-time crypto market data for thousands of tokens. Prices, market caps, trading pairs, and OHLCV data.
Base URL
https://blockmarketscan.com/apiRate Limits
Public endpoints need no auth. Prices refresh every 60s.
Format
JSON responses. SDK available for JavaScript/Node.js.
Authentication
Every endpoint requires an API key. Send it in the x-api-key header. Requests without a valid key return 401. Keys are limited to 120 requests per minute; going over returns 429with a Retry-After header.
- Prices are live at request time. Every quote carries
price_source:binance,livecoinwatch,dexscreener, ordbfor a stored fallback. - A
nullfield means the value is genuinely unknown. Render it as a dash, never as zero. - Retry
429and5xxwith backoff. Never retry401.
curl -s "https://blockmarketscan.com/api/v1/cryptocurrency/listings/latest?limit=10" \ -H "x-api-key: YOUR_KEY"
No key yet? . Treat it like a password: keep it server side, never commit it, and never ship it in browser code.
Quick Start
Top 10 tokens by market cap:
fetch('https://blockmarketscan.com/api/v1/cryptocurrency/listings/latest?limit=10', {
headers: { 'x-api-key': process.env.BMS_API_KEY },
})
.then(r => r.json())
.then(d => console.log(d.data));Prices in every response are live: each quote carries a price_source field showing which feed produced it.
Endpoints
CoinMarketCap-compatible market data routes for listings, quotes, metadata, OHLCV, categories, trending assets, simple price lookups, and global metrics.
Instant integration prompt
Paste this into Claude, Cursor, ChatGPT or any coding assistant. It carries the base URL, auth header, every endpoint, response shapes, rate limits and error handling, so the generated code works on the first run.
You are integrating the BlockMarketScan (BMS) crypto market data API.
Use ONLY the facts below. Do not invent endpoints, parameters or response fields.
## Base URL
https://blockmarketscan.com/api
## Authentication
Every endpoint requires an API key on EVERY request:
x-api-key: YOUR_BMS_API_KEY
Alternatives if you cannot set that header: "Authorization: Bearer YOUR_KEY", or
"?api_key=YOUR_KEY" (last resort, it leaks into logs and referrer headers).
Rules:
- Read the key from an environment variable, never hard code it.
- NEVER ship the key to a browser. Call BMS from your server or a serverless
route and pass the result to the client. A key in frontend JavaScript is
public.
- Missing or invalid key returns 401. Over the limit returns 429 with Retry-After.
## Rate limit
120 requests per minute per key. Design for it:
- Poll no faster than every 10 to 15 seconds.
- Ask for many assets in ONE call rather than one call per asset.
- Cache responses on your side for a few seconds.
- On 429, honour the Retry-After header and back off exponentially.
## Prices are live
Prices come from live exchange feeds at request time, not from a periodic dump.
Every quote carries a price_source field naming the feed that produced it:
"binance", "livecoinwatch", "dexscreener", or "db" (a stored fallback when no
live feed covers that asset). If you see "db" on a major asset, report it.
last_updated is an ISO timestamp for that price.
## Endpoints
CoinMarketCap-compatible (drop-in if you already consume CMC):
GET /v3/cryptocurrency/listings/latest ?start=1&limit=100&sort=market_cap&sort_dir=desc
GET /v3/cryptocurrency/quotes/latest ?symbol=BTC,ETH (or ?id= / ?slug=)
GET /v1/cryptocurrency/listings/new ?limit=50
GET /v1/cryptocurrency/map ?symbol=BTC
GET /v2/cryptocurrency/info ?symbol=BTC metadata, logo, links
GET /v2/cryptocurrency/market-pairs/latest ?symbol=BTC
GET /v2/cryptocurrency/ohlcv/latest ?symbol=BTC
GET /v2/cryptocurrency/ohlcv/historical ?symbol=BTC&count=30
GET /v2/cryptocurrency/price-performance-stats/latest ?symbol=BTC
GET /v1/cryptocurrency/categories ?limit=100
GET /v1/cryptocurrency/category ?id=defi
GET /v1/cryptocurrency/trending/latest ?limit=20
GET /v1/cryptocurrency/trending/gainers-losers ?limit=20
GET /v1/simple/price ?symbol=BTC cheapest price call
GET /v1/global-metrics/quotes/latest aggregates
BlockMarketScan native:
GET /tokens ?page=1&limit=100&sort=market_cap&order=desc
&search=&chain=&category=&new=true
GET /tokens/{slugOrId} full detail, including trading pairs
GET /tokens/{id}/ohlcv candles for one token
GET /prices/snapshot ?ids=uuid1,uuid2 compact rows for many tokens
GET /stats aggregates. Note the field names are short:
{ totalTokens, totalMcap, totalVolume, btcDom, ethDom }
## Filters on /tokens
chain: any of 460 networks. Aliases resolve, so "BNB", "bsc" and
"binance-smart-chain" are the same filter. Examples: ethereum, bsc, base,
solana, arbitrum-one, polygon-pos, hyperevm, tron, the-open-network.
category: defi, rwa, meme, gamefi, ai, stablecoins, layer1, layer2, depin, nft,
exchange, privacy, oracle, payments, altcoins.
sort: market_cap, price_usd, volume_24h, percent_change_1h, percent_change_24h,
percent_change_7d, cmc_rank, name, symbol. order: asc or desc.
## Response shapes
/v3/cryptocurrency/listings/latest and quotes/latest:
{
"data": [{
"id": 1, "name": "Bitcoin", "symbol": "BTC", "slug": "bitcoin",
"cmc_rank": 1, "circulating_supply": 20049875, "total_supply": 20049875,
"quote": { "USD": {
"price": 62889.21,
"volume_24h": 26598704408,
"percent_change_1h": 0.51,
"percent_change_24h": -2.54,
"percent_change_7d": -6.08,
"market_cap": 1260738972745,
"fully_diluted_market_cap": 1320673410000,
"last_updated": "2026-07-31T21:13:17.000Z",
"price_source": "livecoinwatch"
}}
}],
"status": { "timestamp": "...", "error_code": 0, "error_message": null, "elapsed": 12 }
}
/tokens:
{
"data": [{
"id": "uuid", "symbol": "BTC", "name": "Bitcoin", "slug": "bitcoin",
"logo_url": "...", "chains": ["ethereum"], "is_new_listing": false,
"market_data": {
"price_usd": 62889.21, "market_cap": 1260738972745,
"volume_24h": 26598704408, "percent_change_24h": -2.54,
"circulating_supply": 20049875, "cmc_rank": 1,
"last_updated": "...", "price_source": "livecoinwatch"
}
}],
"page": 1, "limit": 100, "total": 8468
}
## Handling missing data
A null field means the value is genuinely unknown. Render it as a dash, never as
zero and never as a guess. Market cap can be null when the reported supply is
implausible, which is deliberate: BMS drops figures it cannot stand behind.
## Errors
401 missing or invalid key. 403 cross-origin without a key. 429 rate limited,
respect Retry-After. 4xx and 5xx bodies carry status.error_message.
Retry 429 and 5xx with backoff. Never retry 401 or 403, the key will not improve.
## Task
Write the integration for my project:
1. Read the key from an environment variable named BMS_API_KEY.
2. A small server-side client with a 15 second timeout and retry-with-backoff on
429 and 5xx.
3. Cache responses briefly to stay inside 120 requests per minute.
4. Types or schemas matching the shapes above.
5. Handle nulls as dashes and surface price_source where useful.
An official SDK exists if you prefer it:
<script src="https://blockmarketscan.com/blockmarketscan-sdk.js"></script>
const bms = new BlockMarketScan({ apiKey: process.env.BMS_API_KEY });
const top = await bms.getListingsLatest({ limit: 50 });
JavaScript SDK
Download .js filev2.0.0. Copy it into your project or include it via script tag. Works in Node.js and the browser, with timeouts and automatic retry on 429 and 5xx.
Constructor takes your key: new BlockMarketScan({ apiKey }), or it reads BMS_API_KEY from the environment. Instantiate it server side only.
<script src="https://blockmarketscan.com/blockmarketscan-sdk.js"></script>/**
* BlockMarketScan JavaScript SDK v2.0.0
* https://blockmarketscan.com
*
* Every endpoint requires an API key. Request one at /developers.
*
* const bms = new BlockMarketScan({ apiKey: 'bms_...' });
* const top = await bms.getListingsLatest({ limit: 50 });
* const btc = await bms.getQuotesLatest({ symbol: 'BTC' });
*
* Prices are live: every quote carries a price_source field naming the feed that
* produced it ("binance", "livecoinwatch", "dexscreener" or "db").
*
* Keep your key server side. In a browser it is visible to anyone who opens
* devtools; proxy through your own backend instead.
*/
class BlockMarketScanError extends Error {
constructor(message, status, body) {
super(message);
this.name = 'BlockMarketScanError';
this.status = status;
this.body = body;
}
}
class BlockMarketScan {
/**
* @param {Object|string} options An options object, or the API key directly.
* @param {string} options.apiKey Required. Sent as the x-api-key header.
* @param {string} [options.baseUrl]
* @param {number} [options.timeoutMs=15000]
* @param {number} [options.retries=2] Retries on 429 and 5xx, with backoff.
*/
constructor(options = {}) {
const config = typeof options === 'string' ? { apiKey: options } : options;
this.apiKey = config.apiKey || (typeof process !== 'undefined' && process.env
? process.env.BMS_API_KEY
: undefined);
this.baseUrl = (config.baseUrl || 'https://blockmarketscan.com/api').replace(/\/$/, '');
this.timeoutMs = config.timeoutMs ?? 15000;
this.retries = config.retries ?? 2;
if (!this.apiKey) {
throw new BlockMarketScanError(
'An API key is required. Pass { apiKey } or set BMS_API_KEY. Request one at https://blockmarketscan.com/developers',
401,
null,
);
}
}
async _fetch(path, params = {}) {
const url = new URL(this.baseUrl + path);
Object.entries(params).forEach(([k, v]) => {
if (v !== undefined && v !== null && v !== '') url.searchParams.set(k, String(v));
});
let lastError;
for (let attempt = 0; attempt <= this.retries; attempt += 1) {
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
try {
const res = await fetch(url, {
headers: { 'x-api-key': this.apiKey, accept: 'application/json' },
signal: controller.signal,
});
if (res.ok) return await res.json();
const body = await res.json().catch(() => null);
const message = body?.status?.error_message || body?.error || 'Request failed';
// 401 and 403 are never retried: a bad key stays bad.
if (res.status === 401 || res.status === 403 || res.status === 404) {
throw new BlockMarketScanError(message, res.status, body);
}
// 429 and 5xx are transient. Honour Retry-After when present.
lastError = new BlockMarketScanError(message, res.status, body);
if (attempt < this.retries) {
const retryAfter = Number(res.headers.get('retry-after'));
const waitMs = Number.isFinite(retryAfter) && retryAfter > 0
? retryAfter * 1000
: 2 ** attempt * 500;
await new Promise((resolve) => setTimeout(resolve, waitMs));
continue;
}
} catch (error) {
if (error instanceof BlockMarketScanError) throw error;
lastError = error;
if (attempt < this.retries) {
await new Promise((resolve) => setTimeout(resolve, 2 ** attempt * 500));
continue;
}
} finally {
clearTimeout(timer);
}
}
throw lastError;
}
// ----------------------------------------------------- CoinMarketCap shape
/** Id map. */
getCryptoMap({ start, limit, sort, symbol } = {}) {
return this._fetch('/v1/cryptocurrency/map', { start, limit, sort, symbol });
}
/** Metadata: logo, description, links, contract addresses. */
getCryptoInfo({ id, slug, symbol, address } = {}) {
return this._fetch('/v2/cryptocurrency/info', { id, slug, symbol, address });
}
/** Ranked listings with live quotes. */
getListingsLatest({ start, limit, sort, sortDir } = {}) {
return this._fetch('/v3/cryptocurrency/listings/latest', {
start, limit, sort, sort_dir: sortDir,
});
}
/** Newly listed assets. */
getListingsNew({ start, limit } = {}) {
return this._fetch('/v1/cryptocurrency/listings/new', { start, limit });
}
/** Live quotes for specific assets. */
getQuotesLatest({ id, slug, symbol, address } = {}) {
return this._fetch('/v3/cryptocurrency/quotes/latest', { id, slug, symbol, address });
}
/** Exchange and DEX pairs for a token. */
getMarketPairs({ id, slug, symbol, limit } = {}) {
return this._fetch('/v2/cryptocurrency/market-pairs/latest', { id, slug, symbol, limit });
}
/** Latest candle. Synthesised from the live price when no candle is stored. */
getOhlcvLatest({ id, slug, symbol } = {}) {
return this._fetch('/v2/cryptocurrency/ohlcv/latest', { id, slug, symbol });
}
/** Historical candles. */
getOhlcvHistorical({ id, slug, symbol, timeStart, timeEnd, count } = {}) {
return this._fetch('/v2/cryptocurrency/ohlcv/historical', {
id, slug, symbol, time_start: timeStart, time_end: timeEnd, count,
});
}
/** Price and percent changes over multiple windows. */
getPricePerformance({ id, slug, symbol } = {}) {
return this._fetch('/v2/cryptocurrency/price-performance-stats/latest', { id, slug, symbol });
}
/** Category list. */
getCategories({ limit } = {}) {
return this._fetch('/v1/cryptocurrency/categories', { limit });
}
/** Tokens within one category. */
getCategory({ id, start, limit, sort, sortDir } = {}) {
return this._fetch('/v1/cryptocurrency/category', {
id, start, limit, sort, sort_dir: sortDir,
});
}
/** Trending by activity. */
getTrendingLatest(limit = 20) {
return this._fetch('/v1/cryptocurrency/trending/latest', { limit });
}
/** Top gainers and losers together. */
getGainersLosers(limit = 20) {
return this._fetch('/v1/cryptocurrency/trending/gainers-losers', { limit });
}
/** Lightweight price lookup: the cheapest call for a price. */
getSimplePrice({ id, slug, symbol } = {}) {
return this._fetch('/v1/simple/price', { id, slug, symbol });
}
/** Global market metrics. Aggregates, refreshed on the ingest cycle. */
getGlobalMetrics() {
return this._fetch('/v1/global-metrics/quotes/latest');
}
// ------------------------------------------------- BlockMarketScan native
/**
* Token list with filters.
* @param {Object} o
* @param {number} [o.page=1]
* @param {number} [o.limit=100] Max 200.
* @param {string} [o.sort=market_cap] market_cap, price_usd, volume_24h,
* percent_change_1h, percent_change_24h, percent_change_7d, cmc_rank, name, symbol
* @param {string} [o.order=desc] asc or desc
* @param {string} [o.search] Name, symbol, or contract address
* @param {string} [o.chain] Any of 460 networks: "ethereum", "bsc",
* "base", "solana", "hyperevm". Aliases resolve, so "BNB" and
* "binance-smart-chain" are the same filter.
* @param {string} [o.category] defi, rwa, meme, gamefi, ai, stablecoins,
* layer1, layer2, depin, nft, exchange, privacy, oracle, payments, altcoins
* @param {boolean} [o.newOnly]
*/
getTokens({ page, limit, sort, order, search, chain, category, newOnly } = {}) {
const params = { page, limit, sort, order, search, chain, category };
if (newOnly) params.new = 'true';
return this._fetch('/tokens', params);
}
/** One token by slug or UUID, with pairs and full metadata. */
getToken(idOrSlug) {
return this._fetch('/tokens/' + encodeURIComponent(idOrSlug));
}
/** OHLCV for one token, by UUID. */
getOHLCV(tokenId) {
return this._fetch('/tokens/' + encodeURIComponent(tokenId) + '/ohlcv');
}
/** Compact price rows for many tokens at once, keyed by token id. */
getPriceSnapshot(tokenIds = []) {
return this._fetch('/prices/snapshot', {
ids: Array.isArray(tokenIds) ? tokenIds.join(',') : tokenIds,
});
}
/**
* Global stats. Field names are short and not the CMC ones:
* { totalTokens, totalMcap, totalVolume, btcDom, ethDom }
*/
getStats() {
return this._fetch('/stats');
}
// ---------------------------------------------------------- convenience
search(query) {
return this.getTokens({ search: query });
}
getTopGainers(limit = 20) {
return this.getTokens({ sort: 'percent_change_24h', order: 'desc', limit });
}
getTopLosers(limit = 20) {
return this.getTokens({ sort: 'percent_change_24h', order: 'asc', limit });
}
getNewlyLaunched(limit = 50) {
return this.getTokens({ newOnly: true, limit });
}
getByChain(chain, limit = 100) {
return this.getTokens({ chain, limit });
}
getByCategory(category, limit = 100) {
return this.getTokens({ category, limit });
}
/**
* Poll a set of symbols and call back whenever a price changes.
* Returns a stop function. Default cadence stays inside the 120 req/min limit.
*/
watchPrices(symbols, onUpdate, intervalMs = 15000) {
const list = Array.isArray(symbols) ? symbols : [symbols];
let stopped = false;
let previous = {};
const tick = async () => {
if (stopped) return;
try {
const res = await this.getQuotesLatest({ symbol: list.join(',') });
const changed = {};
Object.entries(res.data || {}).forEach(([key, token]) => {
const price = token?.quote?.USD?.price;
if (price != null && price !== previous[key]) {
previous[key] = price;
changed[key] = token;
}
});
if (Object.keys(changed).length) onUpdate(changed, res);
} catch (error) {
onUpdate(null, null, error);
}
};
void tick();
const timer = setInterval(tick, intervalMs);
return () => { stopped = true; clearInterval(timer); };
}
}
if (typeof module !== 'undefined' && module.exports) {
module.exports = BlockMarketScan;
module.exports.BlockMarketScan = BlockMarketScan;
module.exports.BlockMarketScanError = BlockMarketScanError;
}
if (typeof window !== 'undefined') {
window.BlockMarketScan = BlockMarketScan;
window.BlockMarketScanError = BlockMarketScanError;
}
BlockMarketScan API - https://blockmarketscan.com/api
Prices update every 60 seconds. Need higher rate limits? .