Guide / Python 3 / requests
Build an odds scanner in Python (about 50 lines)
Build a betting odds scanner that reads NFL prices, finds the best price for each side at each line, and prints a table. One request includes the requested markets across books.
What you will build
This is the script's output from a saved sample response. Moneyline prices use the Eagles and Bears example on our homepage. Spread and total prices are test data chosen to exercise the arbitrage check.
Fixture Market Side / line Price Book
Philadelphia Eagles @ Chicago Bears h2h Philadelphia Eagles -182 Novig
Philadelphia Eagles @ Chicago Bears h2h Chicago Bears +176 Novig
Philadelphia Eagles @ Chicago Bears spreads Philadelphia Eagles -3.5 +110 FanDuel
Philadelphia Eagles @ Chicago Bears spreads Chicago Bears +3.5 +110 DraftKings
Philadelphia Eagles @ Chicago Bears spreads Philadelphia Eagles -4.5 +150 FanDuel
Philadelphia Eagles @ Chicago Bears spreads Chicago Bears +4.5 -170 FanDuel
Philadelphia Eagles @ Chicago Bears totals Over +44.5 +105 FanDuel
Philadelphia Eagles @ Chicago Bears totals Under +44.5 +105 DraftKings
Philadelphia Eagles @ Chicago Bears totals Over +45.5 +140 FanDuel
Philadelphia Eagles @ Chicago Bears totals Under +45.5 -160 FanDuel
Philadelphia Eagles @ Chicago Bears totals/1h Over +21.5 -110 FanDuel
Philadelphia Eagles @ Chicago Bears totals/1h Under +21.5 -110 FanDuel
ARB flag: Philadelphia Eagles @ Chicago Bears | spreads (line -3.5) | 95.24% combined
ARB flag: Philadelphia Eagles @ Chicago Bears | totals (line +44.5) | 97.56% combined
The table keeps alternate lines and periods separate. ARB flags are arithmetic checks on the sample prices.
Get a free key
Create an account and get an API key. The Free plan includes 1,000 requests a month and all 46 books. One run of this script uses one request.
Save the script below as odds_scanner.py. Install requests, set your API key in the environment, then run it.
python3 -m pip install requests
export ODDSLINK_API_KEY="your-api-key"
python3 odds_scanner.pyOn PowerShell, set the key with $env:ODDSLINK_API_KEY="your-api-key" and use python odds_scanner.py.
The full Python odds scanner
The documented endpoint is https://oddslink.app/v1/sports/nfl/odds. Authentication uses the X-API-Key header. The request selects h2h,spreads,totals and oddsFormat=american.
import os
import requests
def implied(price):
return 100 / (price + 100) if price > 0 else -price / (-price + 100)
def american(probability):
return round(100 / probability - 100 if probability <= 0.5
else -100 * probability / (1 - probability))
def scan(fixtures):
print(f"{'Fixture':<38} {'Market':<14} {'Side / line':<30} {'Price':>6} Book")
for fixture in fixtures:
label = f"{fixture['away_team']} @ {fixture['home_team']}"
best, groups, three_way = {}, {}, set()
for book in fixture.get('bookmakers', []):
for market in book.get('markets', []):
kind, period = market['key'], market.get('period', '')
if kind == 'h2h' and any(s['name'] == 'Draw' for s in market['outcomes']):
three_way.add(period)
if kind not in ('h2h', 'spreads', 'totals'):
continue
for side in market['outcomes']:
price, point = side.get('price'), side.get('point')
if price is None or abs(price) < 100:
continue
if kind != 'h2h' and point is None:
continue
key = (kind, period, side['name'], point)
if key not in best or implied(price) < implied(best[key][0]):
best[key] = (price, book['title'])
for (kind, period, name, point), (price, book) in best.items():
side = name if point is None else f"{name} {point:+g}"
market = kind + (f'/{period}' if period else '')
print(f"{label:<38} {market:<14} {side:<30} {price:+6d} {book}")
line = -point if kind == 'spreads' and name == fixture['home_team'] else point
groups.setdefault((kind, period, line), {})[name] = implied(price)
for (kind, period, line), sides in groups.items():
expected = {'Over', 'Under'} if kind == 'totals' else {
fixture['away_team'], fixture['home_team']}
if set(sides) != expected or (kind == 'h2h' and period in three_way):
continue
combined = sum(sides.values())
if combined < 1:
market = kind + (f'/{period}' if period else '')
suffix = '' if line is None else f' (line {line:+g})'
print(f"ARB flag: {label} | {market}{suffix} | {combined:.2%} combined")
def main():
base = os.getenv('ODDSLINK_BASE_URL', 'https://oddslink.app').rstrip('/')
response = requests.get(
f'{base}/v1/sports/nfl/odds',
params={'markets': 'h2h,spreads,totals', 'oddsFormat': 'american'},
headers={'X-API-Key': os.environ['ODDSLINK_API_KEY']}, timeout=30,
)
response.raise_for_status()
scan(response.json())
if __name__ == '__main__':
main()
ODDSLINK_BASE_URL is an override for local mock testing. Leave it unset when using the API.
How each part works
1. Fetch fixtures and prices
main() reads ODDSLINK_API_KEY and calls the NFL endpoint. A timeout stops the request from waiting indefinitely. raise_for_status() reports an HTTP error before the script tries to read prices.
The JSON response is an array of fixtures. Each fixture contains bookmakers[], then markets[], then outcomes[]. On moneylines and spreads, including periods and alternate lines, a team outcome's name equals the fixture's home_team or away_team. Totals use Over or Under.
2. Find the best price per side and line
best belongs to one fixture. Its key is (market, period, side, point). Eagles -3.5 stays separate from Eagles -4.5. A first-half total stays separate from a full-fixture total.
side.get('price') returns None for an absent price, just as a JSON null does. The script skips both and skips invalid American prices, including zero. It does not fill a missing price with zero.
For positive American odds A, implied probability is 100 / (A + 100). For negative odds it is -A / (-A + 100). A lower implied probability means a higher payout for the same side and line.
The reverse conversion is 100 / p - 100 when p <= 0.5, and -100 * p / (1 - p) otherwise, for 0 < p < 1. The american() helper rounds to integer odds. For example, +150 is 40% and -200 is about 66.67%.
3. Print a table
Each row prints the fixture, market, side with its line, price and book. Positive prices retain their plus sign. Equal prices keep the first book encountered.
Extend it: flag arbitrage
The script already includes a two-sided arbitrage check. Add the best implied probabilities for both sides. When the combined value is below 100%, print an ARB flag.
In the sample, +110 on Eagles -3.5 and +110 on Bears +3.5 imply 100 / 210 + 100 / 210 = 95.24%. The +105 prices for Over and Under 44.5 imply 97.56%.
For spreads, the grouping uses the away team's line: home +3.5 pairs with away -3.5. Totals require the same point. The check also requires exactly the expected sides, so a moneyline containing a Draw is excluded.
A flag identifies a price combination to inspect. Account for fees, available size and settlement rules in your scanner. Whole-number lines can push. This calculation does not model those details.
For +EV screens, compare the book's implied probability with your own fair probability. For middles scanners, keep different lines and identify their overlapping outcomes. OddsLink supplies the data; it does not place bets or give picks.
Run it on a schedule
Use cron on Linux or macOS, or Task Scheduler on Windows. This cron example runs once a day at 09:00 in the scheduler's timezone and appends output to a file.
0 9 * * * cd /path/to/scanner && /usr/bin/python3 odds_scanner.py >> scanner.log 2>&1Set ODDSLINK_API_KEY in the scheduler's environment. A scheduled job does not inherit the export from your interactive terminal. Use the Python path where you installed requests.
Choose a schedule within your request allowance. Each run costs one request, even when the response contains multiple fixtures and markets. See plan allowances.
Questions
Can I build a free odds scanner?
Yes. The permanent Free plan includes 1,000 requests a month and all 46 books. This script makes one price request per run. It uses Python 3 and the requests package.
What does this sports betting API Python example return?
The NFL endpoint returns fixtures with bookmakers, markets and outcomes. Each outcome names a side and its price. Spreads and totals also include a point, which identifies the line.
Why compare only the same line?
Eagles -3.5 and Eagles -4.5 settle differently. Keep each side and point separate when selecting a price. For an arbitrage check, pair opposite spreads or Over and Under at the same total.
Can I use it for arbitrage, +EV or middles?
Yes. The script flags two-sided markets whose combined implied probability is below 100%. Add your own fair probabilities for +EV screens, or compare different lines for middles. OddsLink supplies data and does not place bets or give picks.
Does the Free plan include in-play odds?
The endpoint in this guide returns fixtures that have not started. In-play odds and props start on Growth. See pricing for plan features and request allowances.
Build your scanner with one response shape.
Use the odds scanner API for price comparison, arbitrage, +EV and middles tools. Start with the Free plan.