DEV Community

Mlaz-code
Mlaz-code

Posted on

How I Built a Real-Time Sports Odds API with SSE Streaming

The Problem with Polling

Every sports odds API I tried had the same architecture: you poll an endpoint every few seconds and hope you don't miss anything.

For live betting, that's a problem. Odds shift in milliseconds. A +EV opportunity at -110 becomes -115 before your next poll. You're always behind.

I wanted push-based delivery — the server tells you when odds change, not the other way around. So I built SharpAPI around Server-Sent Events (SSE).

Why SSE Over WebSocket?

I evaluated three approaches:

Approach Pros Cons
REST Polling Simple, stateless Delayed, wasteful, misses short-lived lines
WebSocket Full-duplex, fast Complex reconnection, proxy issues, stateful
SSE Push-based, auto-reconnect, HTTP-native One-directional (fine for odds)

SSE won because:

  1. Auto-reconnect is built into the browser spec. If the connection drops, EventSource reconnects automatically with the Last-Event-ID header. With WebSocket, you build this yourself.

  2. It's just HTTP. Works through corporate proxies, load balancers, and CDNs without special configuration. WebSocket upgrade requests get blocked more than you'd think.

  3. One-directional is fine. Clients don't need to send odds data — they just need to receive it. SSE is purpose-built for this.

The Architecture

Sportsbook scrapers (Python)
        ↓
    Redis Pub/Sub
        ↓
  Node.js SSE server
        ↓
  Client (EventSource)
Enter fullscreen mode Exit fullscreen mode

Scrapers collect odds from 20+ sportsbooks every few seconds and publish changes to Redis channels.

SSE server subscribes to Redis, maintains client connections, and pushes only the deltas — not the full snapshot every time.

Clients connect once and receive a stream of updates:

const es = new EventSource(
  'https://api.sharpapi.io/api/v1/stream/odds?league=NBA',
  { headers: { 'Authorization': 'Bearer sk_live_...' } }
);

es.addEventListener('odds:update', (event) => {
  const data = JSON.parse(event.data);
  console.log(data.sportsbook, data.selection, data.odds_american);
});

es.addEventListener('ev:alert', (event) => {
  const data = JSON.parse(event.data);
  console.log(`+EV: ${data.ev_percent}% on ${data.selection}`);
});
Enter fullscreen mode Exit fullscreen mode

The +EV Detection Layer

Raw odds are table stakes. The real value is knowing which odds are mispriced.

I use Pinnacle as the sharp reference line. Pinnacle is a low-margin sportsbook that prices markets efficiently — if DraftKings is offering +150 and Pinnacle's no-vig fair price is +130, that's a +EV bet.

The math:

fair_probability = 1 / pinnacle_decimal_no_vig
implied_probability = 1 / draftkings_decimal
ev_percent = (fair_probability / implied_probability - 1) * 100
Enter fullscreen mode Exit fullscreen mode

Every odds response includes ev_percent, fair_odds, and no_vig_probability so developers don't have to build this themselves.

Lessons Learned

1. Redis Pub/Sub doesn't buffer. If a client disconnects and reconnects, they miss everything published during the gap. I added a snapshot endpoint so clients can hydrate state on reconnect, then switch to the stream for deltas.

2. SSE connections are long-lived. You need proper connection management — heartbeats every 15s, graceful cleanup on disconnect, and connection limits per API key to prevent abuse.

3. Rate limiting streaming is different. You can't just count requests per minute when a client holds one connection for hours. I rate-limit by concurrent streams per tier instead.

4. Free tiers matter. My free tier (12 req/min REST, no credit card) drives 80% of sign-ups. Developers want to build first, pay later. The ones who find value upgrade on their own.

Results

  • Sub-89ms P50 latency from odds change to client delivery
  • 20+ sportsbooks streaming simultaneously
  • Growing organically — ChatGPT is my #1 external traffic source (people ask it for sports odds APIs and it recommends SharpAPI)

Try It

Free tier: 12 requests/minute, 2 sportsbooks, all major sports. No credit card.

sharpapi.io
Docs

If you're building anything with sports data, I'd love to hear what features you'd want. Drop a comment or find me on Discord.

Top comments (0)