MLB Odds API: Run Lines, Totals & Moneylines

MLB Odds API - OddsPapi API Blog
How To Guides March 20, 2026

MLB Season Is Here. Your Odds API Should Be Ready.

The 2026 MLB season means 2,430 regular-season games, each with moneylines, run lines, totals, and an expanding player props market. If you are building models, tracking line movement, or scanning for value — you need odds from more than 20 bookmakers. You need the sharp line. You need it in JSON.

Most odds APIs cover a handful of soft US books — DraftKings, FanDuel, maybe a dozen others. That is fine if you are building a basic tracker. But if you are serious about MLB modeling, you need Pinnacle. You need to see where the sharp money is moving. And you need it alongside the soft books so you can compare.

OddsPapi gives you 80+ bookmakers per MLB game — 100+ expected for regular season — including Pinnacle, the sharp benchmark that soft books follow. Moneylines, run lines, totals, alt lines, and player props. All in one API call. Free tier included.

MLB Odds Coverage: OddsPapi vs The Competition

Feature The Odds API SportsGameOdds OddsPapi
Bookmakers per game ~15-20 ~30 80-100+
Moneyline Yes Yes Yes (81+ books)
Run Line (-1.5) Yes Yes Yes (51+ books)
Game Totals (O/U) Basic Yes Multiple lines (42+ books)
Alt Run Lines No Limited Yes
Player Props No Limited Growing coverage
Pinnacle No No Yes
DraftKings/FanDuel Yes Yes Yes
NPB / KBO / International No No Yes
Free tier 500 req/mo Limited 250 req/mo

65 Tournaments: MLB, NPB, KBO & More

OddsPapi covers 65 baseball tournaments worldwide — not just MLB. If you are building models for international baseball, you are covered. Most APIs do not even list these tournaments.

Tournament ID Coverage
MLB Regular Season 109 Full — moneylines, run lines, totals, props
MLB Spring Training 2456 Pre-season — moneylines, run lines, totals
NPB (Japan) 1036 Full
KBO League (Korea) 2541 Full
CPBL (Taiwan) 32233 Full
Mexican League (LMB) 1030 Full
NCAA Baseball varies College
World Baseball Classic 640 Full

Japanese (NPB), Korean (KBO), Taiwanese (CPBL), and Mexican (LMB) leagues all get full odds coverage with the same bookmaker depth. Build one parser, cover every league.

MLB Markets: Moneylines, Run Lines & Totals

Moneyline (Market 131)

The simplest bet: who wins the game. Market ID 131 is stable across all fixtures. With 81+ bookmakers pricing each game, you can find the best line in seconds. No need to check three sportsbook apps — one API call returns every price.

Outcomes use bookmakerOutcomeId values of home and away. Clean and predictable.

Run Line (Market 1368)

The standard MLB handicap: -1.5 for the favorite, +1.5 for the underdog. Baseball’s run line is more impactful than other sports because of low-scoring games — a 1.5-run spread matters when roughly 30% of MLB games are decided by one run.

The bookmakerOutcomeId contains the line: -1.5/home or +1.5/away. 51+ bookmakers price this market per game.

Over/Under (Market 1338+)

Total runs scored. Each total line gets its own market ID. The bookmakerOutcomeId tells you the line — 8.5/over, 8.5/under. Multiple lines are available per game, so you can compare pricing across different totals (7.5, 8.0, 8.5, 9.0, etc.).

Alternative Lines

Need -2.5 instead of -1.5? Alt run lines (market IDs 1360, 1364) and alt totals (1340+) are available for bettors who want custom spreads and totals. Coverage ranges from 13-15 bookmakers on alt lines.

Python Tutorial: MLB Odds in 5 Steps

Step 1: Setup

import requests
from datetime import datetime, timedelta

API_KEY = "YOUR_API_KEY"  # Free at oddspapi.io
BASE_URL = "https://api.oddspapi.io/v4"
SPORT_ID = 13  # Baseball (NOT 16)

Important: Baseball is Sport ID 13. This is a common gotcha — Sport ID 16 is Dota 2, not baseball.

Step 2: Get Today’s MLB Games

def get_mlb_fixtures():
    """Get today's MLB fixtures with odds."""
    today = datetime.now().strftime("%Y-%m-%d")
    tomorrow = (datetime.now() + timedelta(days=1)).strftime("%Y-%m-%d")

    response = requests.get(f"{BASE_URL}/fixtures", params={
        "apiKey": API_KEY,
        "sportId": SPORT_ID,
        "from": today,
        "to": tomorrow
    })

    fixtures = response.json()
    mlb = [f for f in fixtures if f.get("hasOdds") and "MLB" in f.get("tournamentName", "")]
    print(f"{len(mlb)} MLB games today")
    return mlb

games = get_mlb_fixtures()
for g in games:
    print(f"  {g['participant1Name']} vs {g['participant2Name']}")

Step 3: Fetch Odds from 80+ Bookmakers

def get_odds(fixture_id):
    """Get odds from all bookmakers for a game."""
    response = requests.get(f"{BASE_URL}/odds", params={
        "apiKey": API_KEY,
        "fixtureId": fixture_id
    })
    return response.json()

game = games[0]
odds = get_odds(game["fixtureId"])
bookmakers = list(odds.get("bookmakerOdds", {}).keys())
print(f"{len(bookmakers)} bookmakers pricing {game['participant1Name']} vs {game['participant2Name']}")

Step 4: Compare Moneylines

def compare_moneylines(odds_data):
    """Compare moneyline (market 131) across all bookmakers."""
    results = []

    for slug, bookie in odds_data.get("bookmakerOdds", {}).items():
        market = bookie.get("markets", {}).get("131")
        if not market:
            continue

        row = {"bookmaker": slug}
        for outcome_id, outcome in market.get("outcomes", {}).items():
            for player_id, player in outcome.get("players", {}).items():
                boid = str(player.get("bookmakerOutcomeId", ""))
                price = player.get("price")
                if "home" in boid:
                    row["home"] = price
                elif "away" in boid:
                    row["away"] = price

        if "home" in row and "away" in row:
            results.append(row)

    results.sort(key=lambda x: x.get("home", 0), reverse=True)

    print(f"\nMoneylines from {len(results)} bookmakers:")
    print(f"{'Bookmaker':<20} {'Home':>8} {'Away':>8}")
    print("-" * 38)
    for r in results[:10]:
        print(f"{r['bookmaker']:<20} {r.get('home', 'N/A'):>8} {r.get('away', 'N/A'):>8}")

    best_home = max(results, key=lambda x: x.get("home", 0))
    best_away = max(results, key=lambda x: x.get("away", 0))
    print(f"\nBest Home: {best_home['home']} @ {best_home['bookmaker']}")
    print(f"Best Away: {best_away['away']} @ {best_away['bookmaker']}")

compare_moneylines(odds)

Step 5: Parse Run Lines & Totals

def compare_run_lines(odds_data):
    """Compare the standard run line (-1.5/+1.5) across bookmakers.

    Market 1368 = Run Line (-1.5/+1.5)
    bookmakerOutcomeId: '-1.5/home' or '+1.5/away'
    """
    results = []

    for slug, bookie in odds_data.get("bookmakerOdds", {}).items():
        market = bookie.get("markets", {}).get("1368")
        if not market:
            continue

        row = {"bookmaker": slug}
        for outcome_id, outcome in market.get("outcomes", {}).items():
            for player_id, player in outcome.get("players", {}).items():
                boid = str(player.get("bookmakerOutcomeId", ""))
                price = player.get("price")
                if "-1.5" in boid:
                    row["fav_minus"] = price
                elif "+1.5" in boid:
                    row["dog_plus"] = price

        if "fav_minus" in row:
            results.append(row)

    results.sort(key=lambda x: x.get("fav_minus", 0), reverse=True)

    print(f"\nRun Line (-1.5/+1.5) from {len(results)} bookmakers:")
    print(f"{'Bookmaker':<20} {'Fav -1.5':>10} {'Dog +1.5':>10}")
    print("-" * 42)
    for r in results[:10]:
        print(f"{r['bookmaker']:<20} {r.get('fav_minus', 'N/A'):>10} {r.get('dog_plus', 'N/A'):>10}")

compare_run_lines(odds)


def find_totals(odds_data):
    """Find and compare Over/Under total runs.

    Totals use dynamic market IDs. Parse bookmakerOutcomeId
    for the line (e.g., '8.5/over', '8.5/under').
    """
    totals = {}

    for slug, bookie in odds_data.get("bookmakerOdds", {}).items():
        for mid, market in bookie.get("markets", {}).items():
            for oid, outcome in market.get("outcomes", {}).items():
                for pid, player in outcome.get("players", {}).items():
                    boid = str(player.get("bookmakerOutcomeId", ""))
                    price = player.get("price")

                    if "/over" in boid or "/under" in boid:
                        parts = boid.split("/")
                        try:
                            line = float(parts[0])
                            side = parts[1]
                        except (ValueError, IndexError):
                            continue

                        if line < 4 or line > 20:
                            continue

                        if line not in totals:
                            totals[line] = {}
                        if slug not in totals[line]:
                            totals[line][slug] = {}
                        totals[line][slug][side] = price

    print(f"\nAvailable total lines: {sorted(totals.keys())}")

    for line in sorted(totals.keys()):
        books = len(totals[line])
        if books > 5:
            print(f"\n  O/U {line} from {books} bookmakers:")
            for slug, prices in sorted(totals[line].items(), key=lambda x: x[1].get('over', 0), reverse=True)[:5]:
                print(f"    {slug:<20} Over {prices.get('over', 'N/A'):>6}  Under {prices.get('under', 'N/A'):>6}")

find_totals(odds)

Why Line Shopping Matters More in Baseball

Baseball is the ultimate line-shopping sport. With moneylines instead of point spreads as the primary market, even a 5-cent difference between bookmakers compounds over a 162-game season. Getting +115 instead of +110 on an underdog does not sound like much — but over 500 bets, it is the difference between profit and breakeven.

Sharp bettors know this. That is why they check Pinnacle first — it sets the market — then shop the soft books for better prices. With 80+ bookmakers in one API call, you are not checking 3 sportsbooks. You are checking all of them.

OddsPapi returns odds from Pinnacle, DraftKings, 1xBet, Unibet, Betway, and 75+ more in a single request. Build a best-price scanner in 20 lines of Python.

Free Historical MLB Odds

Backtest your MLB models against historical closing lines. Track CLV (Closing Line Value) to see if your predictions beat the market. Most APIs charge extra for historical data — OddsPapi includes it on the free tier.

Historical data lets you answer questions like: How often does the Pinnacle closing line beat DraftKings? What is the average margin difference between sharp and soft books on MLB moneylines? Does your model beat the closing line consistently?

If the answer to that last question is yes, you have an edge. If not, you need more data, not less.

FAQ: MLB Odds API

What sport ID is baseball/MLB?

Sport ID 13. MLB is tournament ID 109. A common mistake is using Sport ID 16, which is Dota 2 — not baseball.

How many bookmakers cover MLB games?

80+ per Spring Training game, with 100+ expected for the regular season. Confirmed bookmakers include Pinnacle, DraftKings, 1xBet, Unibet, and Betway. Bet365 and FanDuel are expected to be added for regular season games.

What is the run line?

Baseball’s standard handicap: -1.5 for the favorite, +1.5 for the underdog. Market ID 1368. The favorite needs to win by 2 or more runs. It is the MLB equivalent of a point spread.

Do you cover NPB and KBO?

Yes. Japanese (NPB, tournament ID 1036), Korean (KBO, tournament ID 2541), Taiwanese (CPBL, tournament ID 32233), and Mexican (LMB, tournament ID 1030) leagues are all included with full odds coverage.

Is the API free for MLB data?

The free tier gives 250 requests per month. That is enough to track lines for every MLB game daily. Paid plans start at $29/month for higher volume.

When does MLB data become available?

Odds appear as soon as bookmakers post lines, typically 24-48 hours before first pitch. Spring Training data is available now — start building and testing before the regular season begins.

Get Ready for the 2026 MLB Season

2,430 games. 80+ bookmakers per game. Moneylines, run lines, totals — all in JSON. Start building your models now with Spring Training data, and be ready when Opening Day hits.

Stop scraping. Stop paying enterprise prices for 20 bookmakers. OddsPapi gives you 300+ bookmakers across all sports, with real-time WebSocket feeds for live games and free historical data for backtesting.

Get Your Free API Key — no credit card, no enterprise sales call. Sign up and start pulling MLB odds in 60 seconds.