I've been building a small actor that compares perpetual futures funding rates across exchanges to flag arbitrage spreads. Most exchanges (OKX, Gate.io, KuCoin, Bitget, MEXC) have clean, key-less public APIs that just work from any server, including cloud/datacenter IPs.
Binance is not one of them. Hit its public funding rate endpoint from a datacenter IP and you get a flat HTTP 451 — geo-blocked, no way around it with a direct connection.
I'd shipped without Binance for months, figuring that wall was permanent. Then, while extending the actor to more exchanges, I noticed something I hadn't planned for: Bybit — which had worked fine from day one — started returning HTTP 403 from the exact same server. Same code, same IP range, different day. No announcement, no changelog entry I could find. It just started blocking.
That's the actual lesson here: an API block you hit once isn't necessarily permanent, and a block you've never hit isn't necessarily safe either. Both directions can change without warning.
The fix: proxy only what actually needs it
The obvious fix is "route everything through a residential proxy." The problem: residential proxy bandwidth costs real money, and 5 of my 7 exchanges don't need it at all. Paying proxy rates for every single request when only two exchanges are blocking would be wasteful — and slower, since residential circuits add latency direct connections don't have.
So the actual fix is per-exchange:
- Detect the blocked ones by status code (451 for a hard geo-block, 403 for the softer anti-bot flavor)
- Only those two calls get an explicit proxy URL
- Everything else keeps hitting the exchange directly, fast and free
async def _get_json(client, url, params=None, *, proxy_url=None):
if proxy_url:
async with httpx.AsyncClient(proxy=proxy_url) as proxied:
r = await proxied.get(url, params=params, ...)
else:
r = await client.get(url, params=params, ...)
...
Enter fullscreen mode Exit fullscreen mode
One conditional client, one proxy URL passed down only where it's needed. The other 5 exchanges never touch the proxy at all.
Worth re-testing what you gave up on
The bigger habit change: I'd marked Binance as "permanently excluded" in my own notes and never looked back. That was fine for Binance (still 451, still needs the proxy). But I would have shipped Bybit broken silently if I hadn't happened to be touching that code again for an unrelated reason.
If you maintain anything that scrapes or calls third-party APIs long enough, the honest move is to periodically re-run the sources you've already integrated, not just the ones you're adding — blocks appear on a schedule you don't control.
I'm 0xGollum, I build small data actors on Apify — mostly scrapers and monitors for signals I found annoying to track by hand. If you're curious what else I'm shipping: linktr.ee/0xGollum
Enter fullscreen mode Exit fullscreen mode
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.