DEV Community

SharpAPI
SharpAPI

Posted on

How to Get Real-Time Sports Betting Odds from an API

If you have ever tried to build a betting tool, a line-shopping app, or a model that needs live odds, you have probably hit the same wall: the sportsbooks themselves do not give you an API. DraftKings, FanDuel, BetMGM, Caesars, Pinnacle, and the rest have no public developer program. Their odds move constantly, they differ book to book, and there is no supported way to pull them.

This guide walks through how developers actually solve that, and the concepts you need to turn raw odds into something useful: fair prices, expected value, and arbitrage.

Why the books have no public API

Sportsbooks make money on the margin baked into their prices (the "vig"). Exposing a clean, real-time feed would help competitors and sharp bettors more than it helps the book, so none of the major US operators publish one. The odds you see in their apps come from private internal feeds.

That leaves two options: scrape each book yourself (fragile, rate-limited, and a maintenance treadmill across dozens of sites), or use an aggregator that has already normalized every book into one schema. This guide uses SharpAPI, which aggregates 45+ sportsbooks and betting exchanges into a single format, but the concepts apply no matter how you source the data.

Odds formats, and why you convert everything to probability

The same price shows up three ways:

  • American: -150 (bet 150 to win 100) or +130 (bet 100 to win 130)
  • Decimal: 1.67 or 2.30 (total return per 1 unit staked)
  • Implied probability: the decimal odds inverted, 1 / 2.30 = 0.435

Always convert to implied probability before you compare anything. Two books quoting -150 and 1.67 are quoting the same price; only probability makes that obvious.

def american_to_decimal(a):
    return 1 + (a / 100 if a > 0 else 100 / abs(a))

def decimal_to_prob(d):
    return 1 / d

print(decimal_to_prob(american_to_decimal(-150)))  # 0.6 (before vig removal)
Enter fullscreen mode Exit fullscreen mode

Removing the vig to get a fair price

Add up the implied probabilities on both sides of a market and you will get more than 100 percent. That overround is the vig. To estimate the "fair" (no-vig) probability, normalize the two sides so they sum to 1:

def no_vig(prob_a, prob_b):
    total = prob_a + prob_b
    return prob_a / total, prob_b / total
Enter fullscreen mode Exit fullscreen mode

Sharp books like Pinnacle run thin margins and move on real money, so their no-vig line is the closest thing to a market consensus. That consensus is the reference you measure every other book against. Pinnacle has no public API either, so pulling Pinnacle odds is one of the most common reasons developers reach for an aggregator in the first place.

Finding +EV bets

A bet has positive expected value when a book is offering better odds than the fair price implies. If the no-vig fair probability of an outcome is 0.50 (fair decimal 2.00), and some book is offering 2.15, that book is paying you more than the risk warrants:

def expected_value(offered_decimal, fair_prob):
    return offered_decimal * fair_prob - 1

# fair prob 0.50, a book offering 2.15
print(expected_value(2.15, 0.50))  # 0.075  -> +7.5% edge
Enter fullscreen mode Exit fullscreen mode

Do this across every book and every market and you have a +EV scanner. The work is not the math, it is getting clean, synchronized odds from every book at once.

Arbitrage and middles

When two books disagree enough, you can back both sides and lock a profit regardless of outcome. If the best price on each side sums to an implied probability under 100 percent, it is an arb:

def is_arb(best_decimal_a, best_decimal_b):
    return (1 / best_decimal_a) + (1 / best_decimal_b) < 1
Enter fullscreen mode Exit fullscreen mode

These windows are small and close fast, which is why latency matters. Polling every few seconds misses most of them.

Pulling it together with a live feed

Here is the whole loop against a normalized aggregator: get the odds, remove the vig against a sharp reference, and surface anything +EV.

import requests

odds = requests.get(
    "https://api.sharpapi.io/api/v1/odds",
    headers={"X-API-Key": "YOUR_KEY"},
    params={"sport": "nfl", "market": "moneyline"},
).json()

# group by event + selection, compare each book to the no-vig reference,
# flag offered prices whose EV clears your threshold
Enter fullscreen mode Exit fullscreen mode

For anything real-time, polling is the wrong tool. Odds change continuously, and the value windows you care about open and close in seconds. A push-based stream (Server-Sent Events or WebSocket) hands you each change as it happens instead of you asking repeatedly. SharpAPI pushes updates over SSE with sub-89ms latency, which is the difference between catching a middle and reading about it afterward.

Where to start

You do not need all 45 books or a streaming pipeline on day one. Start with one sport and one market, convert everything to probability, remove the vig against a sharp reference, and print anything with positive EV. Once that works, add books, add markets, then move from polling to streaming.

If you want a normalized feed to build against, SharpAPI has a free tier (12 requests/minute, no credit card): sharpapi.io. The concepts here are the same whichever source you use, the hard part was never the math, it was getting clean odds from every book at the same instant.

Top comments (0)