I was building a portfolio tracker (folio.bitmidpoint.com) and hit the most basic question imaginable: what does Bitcoin cost right now?
Turns out, nobody can answer this correctly.
CoinGecko? Free tier rate-limits you like you personally insulted their mother. CoinMarketCap? Wants your email, phone, first-born, and a LinkedIn endorsement before giving you an API key. CryptoCompare? I genuinely forgot my CryptoCompare password three times during evaluation because their auth flow has more steps than a NASA launch sequence.
But the real problem isn't access - it's the data itself. Every single one of these APIs gives you one number. A weighted average. A number that doesn't actually exist on any exchange, anywhere, ever. It's like asking "what does a house cost in America?" and getting back "$412,000". Technically not wrong. Practically useless if you're actually trying to buy one.
So naturally, I abandoned the portfolio tracker for three months and yak-shaved my way into building the entire price infrastructure from scratch. As one does.
OK but what's actually wrong with averaged prices?
Right now, as I write this, here's what BitMidpoint is reporting for Bitcoin from 10 exchanges simultaneously:
{
"symbol": "BTC",
"avgPrice": 66859.49,
"minPrice": 66816.51,
"maxPrice": 66886.05,
"spreadPct": 0.104,
"exchangeCount": 10,
"totalVolume": 48207280042,
"confidence": "HIGH",
"exchanges": ["binance", "kraken", "kucoin", "gateio", "bybit",
"mexc", "coingecko", "coinpaprika", "coinmarketcap",
"cryptocompare"]
}
0.1% spread on BTC - tight, as expected for the most liquid asset on earth. Now let's look at Ethereum:
{
"symbol": "ETH",
"avgPrice": 2061.67,
"minPrice": 2059.91,
"maxPrice": 2062.60,
"spreadPct": 0.131,
"exchangeCount": 9
}
And Solana:
{
"symbol": "SOL",
"avgPrice": 78.92,
"minPrice": 78.84,
"maxPrice": 78.97,
"spreadPct": 0.165,
"exchangeCount": 9
}
Notice the pattern? BTC spread: 0.10%. ETH: 0.13%. SOL: 0.16%. Lower liquidity = wider spread. This is information that every other API throws away by averaging. If you're building anything that touches real money - a bot, a tracker, a notification system - you need to know the spread exists.
Now here's where it gets spicy. The /api/v1/markets endpoint right now is showing:
ELON: 250.35% spread (yeah... that token)
ULTIMA: 5.93% spread ($3,429 vs $3,636 - $207 gap per token)
MOODENG: 1.97% spread
ULTIMA has a $207 per-token spread across exchanges. That's not noise. That's either arbitrage or a broken adapter - and the whole point is that now you can see it and decide for yourself, instead of some API silently smoothing it into a single number.
The Architecture (or: how "I need one endpoint" became three services)
Data - the collector daemon. Hammers 10 exchange APIs every 30 seconds, normalizes everything into { symbol, price, volume, exchange, timestamp }, computes aggregates, writes to MongoDB. Currently tracking 500 tokens. It's the part that never sleeps and I spend half my debugging time on.
bitmidpoint (bitmidpoint.com) - the REST API + web dashboard. Express 4 backend, Vue 3 frontend loaded straight from CDN (no build step, no webpack, no Vite - just <script src="vue.js"> and vibes). Reads from MongoDB, serves the public API, renders charts with ECharts 5.
folio (folio.bitmidpoint.com) - the portfolio tracker that started all of this. Full PWA with Service Worker, offline support, background sync. Vanilla JS, no framework. JWT auth from CryBitView for cross-device sync, but also works entirely in localStorage if you don't want to create an account. More on CryFolio in a follow-up post.
The Adapter Pattern (or: every exchange is a special snowflake)
I have 10 exchange adapters and each one is a different flavor of pain. Binance gives you WebSocket streams. KuCoin has a REST API that occasionally returns numbers as strings (thanks). CoinGecko's free tier has such aggressive rate limiting that the adapter has a built-in backoff-and-retry dance. CoinPaprika doesn't paginate the same way as anyone else. I could go on.
The pattern that saved my sanity: adapters.
Every adapter implements .fetchPrices() and returns the same shape. Inside, they can do whatever cursed thing their API requires. Outside, it's all the same. Adding a new exchange is one file. Ripping out a broken one is deleting one file.
And yes, CoinGecko is one of the adapters. I use CoinGecko - I just don't trust CoinGecko alone. It goes through the same pipeline as Binance, Kraken, everyone. If CoinGecko disagrees wildly with 9 other sources, the consensus system flags it as an outlier instead of letting it corrupt the average. CoinGecko becomes a witness, not the judge.
The Price Consensus Problem (the part where production caught fire)
Here's my favorite "oh shit" moment.
After deploy, Bitcoin was displaying on the live dashboard:
MIN PRICE AVG PRICE MAX PRICE SPREAD %
$42,769.40 $64,916.98 $68,116.60 39.94%
If you're reading that and going "wait, 39.94% spread on BTC? That's insane" - correct. One adapter had gone completely sideways and was reporting BTC at $42K while the actual market was at ~$68K. The bad price sailed through two validation layers because both used a hardcoded 50% deviation threshold. 37% off from median? Under 50%! Looks good to me! To production we go!
The dashboard was confidently displaying a market crash that was not happening. For over an hour. On a service whose entire value proposition is "accurate price data."
Chef's kiss.
This led to building PriceConsensus.js - a proper dynamic consensus engine that computes trust scores per source, uses statistical deviation from the median across N sources, and adjusts thresholds based on token characteristics (BTC should have tight agreement, some freshly listed shitcoin might legitimately have 20% spread). It's a genuinely interesting problem with nasty edge cases: flash crashes, exchange maintenance windows, new listings, tokens that only exist on 2 exchanges.
I'm writing a separate deep-dive post on PriceConsensus because it deserves its own space and has enough "what the hell do we do about THIS" moments to fill a novel.
The API (the thing you can actually use right now)
No registration. No API key. No signup form. No email confirmation. Just:
curl https://bitmidpoint.com/api/v1/tokens/BTC
That's it. You get back min/max/avg/spread across 10 exchanges, volume, confidence score. 500 tokens tracked. Updated every 30 seconds.
Other endpoints:
# All tokens ranked by volume
curl https://bitmidpoint.com/api/v1/tokens
# Market overview + top arbitrage opportunities
curl https://bitmidpoint.com/api/v1/markets
# Price history (24h / 7d / 30d)
curl https://bitmidpoint.com/api/v1/tokens/ETH/history?range=7d
Rate limits - because I'm one guy on a DigitalOcean droplet, not AWS:
| Requests/min | Auth required? | |
|---|---|---|
| Anonymous | 30 | No |
| API key | 120 | GitHub/Google login (for key management UI only) |
Rate limit headers on every response: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-RateLimit-Window. You always know exactly where you stand. If you hit 429, Retry-After tells you when to come back.
API keys are SHA-256 hashed in the DB - raw key shown to you exactly once after generation, never stored. If you lose it, revoke and make a new one. I know it's annoying. It's also the only correct way to do API key security. Fight me.
CryFolio - the project that started this whole mess
folio.bitmidpoint.com - the original portfolio tracker. PWA, installable on any device, works offline. Full Service Worker with cache-first for assets and network-first for price data. If you add a transaction while offline, it queues in a background sync and posts when you're back online.
No registration required. Portfolio data lives in localStorage. Import/export your transactions as JSON. Track buys, sells, compute P&L, see current portfolio value powered by the BitMidpoint API. If you want cross-device sync, log in via GitHub or Google and transactions go server-side. But the entire app works without ever creating an account. Because honestly, I'm tired of every website demanding I create an account to use a calculator.
Price calculations run in a Web Worker - main thread stays smooth during live price updates. Not flashy, but the difference between 60fps and "why is this tab frozen" when you have 50 positions updating simultaneously.
Full CryFolio deep-dive coming in a follow-up post - there's a lot of PWA/Service Worker/Web Worker architecture worth unpacking.
Tech stack (for the bottom-scrollers, I respect you)
The frontend deserves a special callout: zero npm dependencies, zero build step. Vue 3 from CDN. ECharts 5 from CDN. Tailwind from CDN. JetBrains Mono from Google Fonts. You open the HTML file and it works. No node_modules folder. No 47-step webpack config. No "works on my machine but breaks in CI". It just loads. I will die on this hill.
The terminal/hacker theme (mint #daff7c, 0px border-radius, monospace everything) exists because I spent 2am debugging MongoDB aggregation pipelines and wanted to feel like I was in The Matrix. It stayed because users actually liked it.
Try it right now
# One command, real data, no signup
curl -s https://bitmidpoint.com/api/v1/tokens/BTC | python3 -m json.tool
Links:
- API + Dashboard: bitmidpoint.com
- API Documentation: bitmidpoint.com/docs
- Markets / Arbitrage: bitmidpoint.com/markets
- Token Explorer: bitmidpoint.com/tokens
- Portfolio Tracker: folio.bitmidpoint.com
It's free. It'll stay free. I built it because I needed it, got annoyed that everything else was either expensive or half-assed, and here we are.






Top comments (0)