<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Kostiantyn Dobrovolskyi</title>
    <description>The latest articles on Forem by Kostiantyn Dobrovolskyi (@bajjy).</description>
    <link>https://forem.com/bajjy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3858408%2Ffab3a082-7c55-48bd-bda5-f89a87dbb9de.png</url>
      <title>Forem: Kostiantyn Dobrovolskyi</title>
      <link>https://forem.com/bajjy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/bajjy"/>
    <language>en</language>
    <item>
      <title>I got tired of every crypto price API giving me made-up numbers, so I built my own aggregator. Free, no registration. Here's how.</title>
      <dc:creator>Kostiantyn Dobrovolskyi</dc:creator>
      <pubDate>Thu, 02 Apr 2026 22:46:01 +0000</pubDate>
      <link>https://forem.com/bajjy/i-got-tired-of-every-crypto-price-api-giving-me-made-up-numbers-so-i-built-my-own-aggregator-30lf</link>
      <guid>https://forem.com/bajjy/i-got-tired-of-every-crypto-price-api-giving-me-made-up-numbers-so-i-built-my-own-aggregator-30lf</guid>
      <description>&lt;p&gt;I was building a portfolio tracker (&lt;a href="https://folio.bitmidpoint.com" rel="noopener noreferrer"&gt;folio.bitmidpoint.com&lt;/a&gt;) and hit the most basic question imaginable: &lt;strong&gt;what does Bitcoin cost right now?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Turns out, nobody can answer this correctly.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;But the real problem isn't access - it's the data itself. Every single one of these APIs gives you &lt;strong&gt;one number&lt;/strong&gt;. 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  OK but what's actually wrong with averaged prices?
&lt;/h2&gt;

&lt;p&gt;Right now, as I write this, here's what &lt;a href="https://bitmidpoint.com" rel="noopener noreferrer"&gt;BitMidpoint&lt;/a&gt; is reporting for Bitcoin from 10 exchanges simultaneously:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BTC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"avgPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;66859.49&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;66816.51&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;66886.05&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"spreadPct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.104&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exchangeCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"totalVolume"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48207280042&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"confidence"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"HIGH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exchanges"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"binance"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kraken"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"kucoin"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"gateio"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"bybit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"mexc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coingecko"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coinpaprika"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"coinmarketcap"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                &lt;/span&gt;&lt;span class="s2"&gt;"cryptocompare"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;0.1% spread on BTC - tight, as expected for the most liquid asset on earth. Now let's look at Ethereum:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ETH"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"avgPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2061.67&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2059.91&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;2062.60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"spreadPct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.131&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exchangeCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And Solana:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"symbol"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"SOL"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"avgPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;78.92&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"minPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;78.84&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"maxPrice"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;78.97&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"spreadPct"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;0.165&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exchangeCount"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice the pattern? BTC spread: 0.10%. ETH: 0.13%. SOL: 0.16%. Lower liquidity = wider spread. This is information that &lt;em&gt;every other API throws away&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;Now here's where it gets spicy. The &lt;code&gt;/api/v1/markets&lt;/code&gt; endpoint right now is showing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;ELON:     250.35% spread  (yeah... that token)
ULTIMA:   5.93% spread    ($3,429 vs $3,636 - $207 gap per token)
MOODENG:  1.97% spread
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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 &lt;em&gt;see it&lt;/em&gt; and decide for yourself, instead of some API silently smoothing it into a single number.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture (or: how "I need one endpoint" became three services)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5mllvxt6qwtbui1ltcb.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa5mllvxt6qwtbui1ltcb.png" alt="bitmidpoint arch" width="800" height="499"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Data&lt;/strong&gt; - the collector daemon. Hammers 10 exchange APIs every 30 seconds, normalizes everything into &lt;code&gt;{ symbol, price, volume, exchange, timestamp }&lt;/code&gt;, computes aggregates, writes to MongoDB. Currently tracking 500 tokens. It's the part that never sleeps and I spend half my debugging time on.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;bitmidpoint&lt;/strong&gt; (&lt;a href="https://bitmidpoint.com" rel="noopener noreferrer"&gt;bitmidpoint.com&lt;/a&gt;) - the REST API + web dashboard. Express 4 backend, Vue 3 frontend loaded straight from CDN (no build step, no webpack, no Vite - just &lt;code&gt;&amp;lt;script src="vue.js"&amp;gt;&lt;/code&gt; and vibes). Reads from MongoDB, serves the public API, renders charts with ECharts 5.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;folio&lt;/strong&gt; (&lt;a href="https://folio.bitmidpoint.com" rel="noopener noreferrer"&gt;folio.bitmidpoint.com&lt;/a&gt;) - 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Adapter Pattern (or: every exchange is a special snowflake)
&lt;/h2&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;The pattern that saved my sanity: adapters.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v7gzg7qu4adxmxtdlvx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F0v7gzg7qu4adxmxtdlvx.png" alt="flowchart of adapters job" width="800" height="200"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every adapter implements &lt;code&gt;.fetchPrices()&lt;/code&gt; 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.&lt;/p&gt;

&lt;p&gt;And yes, CoinGecko is one of the adapters. I use CoinGecko - I just don't &lt;em&gt;trust&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44k7or6wqvltugjvt12b.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F44k7or6wqvltugjvt12b.png" alt="/markets page showing the arbitrage opportunity cards with spread percentages" width="800" height="1181"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The Price Consensus Problem (the part where production caught fire)
&lt;/h2&gt;

&lt;p&gt;Here's my favorite "oh shit" moment.&lt;/p&gt;

&lt;p&gt;After deploy, Bitcoin was displaying on the live dashboard:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;MIN PRICE     AVG PRICE      MAX PRICE      SPREAD %
$42,769.40    $64,916.98     $68,116.60     39.94%
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;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!&lt;/p&gt;

&lt;p&gt;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."&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Chef's kiss.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;This led to building &lt;code&gt;PriceConsensus.js&lt;/code&gt; - 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5p42lb01aav3txgpkp2.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy5p42lb01aav3txgpkp2.png" alt="tokens page showing the spread % column, confidence indicator, and exchange count" width="800" height="561"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  The API (the thing you can actually use right now)
&lt;/h2&gt;

&lt;p&gt;No registration. No API key. No signup form. No email confirmation. Just:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://bitmidpoint.com/api/v1/tokens/BTC
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's it. You get back min/max/avg/spread across 10 exchanges, volume, confidence score. 500 tokens tracked. Updated every 30 seconds.&lt;/p&gt;

&lt;p&gt;Other endpoints:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# All tokens ranked by volume&lt;/span&gt;
curl https://bitmidpoint.com/api/v1/tokens

&lt;span class="c"&gt;# Market overview + top arbitrage opportunities&lt;/span&gt;
curl https://bitmidpoint.com/api/v1/markets

&lt;span class="c"&gt;# Price history (24h / 7d / 30d)&lt;/span&gt;
curl https://bitmidpoint.com/api/v1/tokens/ETH/history?range&lt;span class="o"&gt;=&lt;/span&gt;7d
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv1lcr61atnzb3ihvanxf.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fv1lcr61atnzb3ihvanxf.png" alt="API docs page at bitmidpoint.com/docs" width="800" height="583"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Rate limits&lt;/strong&gt; - because I'm one guy on a DigitalOcean droplet, not AWS:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Requests/min&lt;/th&gt;
&lt;th&gt;Auth required?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Anonymous&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;API key&lt;/td&gt;
&lt;td&gt;120&lt;/td&gt;
&lt;td&gt;GitHub/Google login (for key management UI only)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Rate limit headers on every response: &lt;code&gt;X-RateLimit-Limit&lt;/code&gt;, &lt;code&gt;X-RateLimit-Remaining&lt;/code&gt;, &lt;code&gt;X-RateLimit-Reset&lt;/code&gt;, &lt;code&gt;X-RateLimit-Window&lt;/code&gt;. You always know exactly where you stand. If you hit 429, &lt;code&gt;Retry-After&lt;/code&gt; tells you when to come back.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;




&lt;h2&gt;
  
  
  CryFolio - the project that started this whole mess
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fzlc3vp1h7kcd4qmpfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5fzlc3vp1h7kcd4qmpfw.png" alt="CryFolio dashboard with holdings and P&amp;amp;L" width="800" height="640"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://folio.bitmidpoint.com" rel="noopener noreferrer"&gt;folio.bitmidpoint.com&lt;/a&gt; - 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.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No registration required.&lt;/strong&gt; Portfolio data lives in localStorage. Import/export your transactions as JSON. Track buys, sells, compute P&amp;amp;L, see current portfolio value powered by the BitMidpoint API. If you &lt;em&gt;want&lt;/em&gt; 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.&lt;/p&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;Full CryFolio deep-dive coming in a follow-up post - there's a lot of PWA/Service Worker/Web Worker architecture worth unpacking.&lt;/p&gt;




&lt;h2&gt;
  
  
  Tech stack (for the bottom-scrollers, I respect you)
&lt;/h2&gt;

&lt;p&gt;The frontend deserves a special callout: &lt;strong&gt;zero npm dependencies, zero build step.&lt;/strong&gt; 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 &lt;code&gt;node_modules&lt;/code&gt; folder. No 47-step webpack config. No "works on my machine but breaks in CI". It just loads. I will die on this hill.&lt;/p&gt;

&lt;p&gt;The terminal/hacker theme (mint &lt;code&gt;#daff7c&lt;/code&gt;, 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.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it right now
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# One command, real data, no signup&lt;/span&gt;
curl &lt;span class="nt"&gt;-s&lt;/span&gt; https://bitmidpoint.com/api/v1/tokens/BTC | python3 &lt;span class="nt"&gt;-m&lt;/span&gt; json.tool
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API + Dashboard: &lt;a href="https://bitmidpoint.com" rel="noopener noreferrer"&gt;bitmidpoint.com&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;API Documentation: &lt;a href="https://bitmidpoint.com/docs" rel="noopener noreferrer"&gt;bitmidpoint.com/docs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Markets / Arbitrage: &lt;a href="https://bitmidpoint.com/markets" rel="noopener noreferrer"&gt;bitmidpoint.com/markets&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Token Explorer: &lt;a href="https://bitmidpoint.com/tokens" rel="noopener noreferrer"&gt;bitmidpoint.com/tokens&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Portfolio Tracker: &lt;a href="https://folio.bitmidpoint.com" rel="noopener noreferrer"&gt;folio.bitmidpoint.com&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

</description>
      <category>api</category>
      <category>cryptocurrency</category>
      <category>showdev</category>
      <category>sideprojects</category>
    </item>
  </channel>
</rss>
