cookbook

Recipes

Task-oriented examples for the PropLine API. Each recipe maps a real question (“find +EV plays for tonight,” “track CLV on a placed bet,” “scan for arbitrage”) to working code. Most recipes use the Python SDK; curl equivalents work the same way.

Install: pip install propline (Python) or npm install propline (Node). Get an API key at prop-line.com.

Cross-book +EV

No-vig fair lines from a sharp anchor (Pinnacle preferred, Bovada fallback), then EV% computed for every other book at the same line. PropLine derives this server-side; the-odds-api and most competitors leave it as a client-side exercise.

01

Find tonight's top +EV plays for one event

Pro

Which prices on this event are mispriced relative to the sharp consensus?

GET /v1/sports/{sport}/events/{id}/ev

python
from propline import PropLine

client = PropLine(api_key="YOUR_KEY")
ev = client.get_event_ev("baseball_mlb", event_id=12345)

for line in ev["lines"]:
    plus = sorted(
        (o for o in line["outcomes"] if o["is_plus_ev"]),
        key=lambda o: -o["ev_pct"],
    )
    if plus:
        print(f"{line['description']} {line['point']}")
        for o in plus[:3]:
            print(f"  {o['book_title']:<12} {o['name']:<8} {o['price']:+5}  EV {o['ev_pct']:+.2f}%")
02

Slate-wide +EV scan across every event tonight

Pro

Show every +EV play across the whole MLB slate, sorted by EV%.

GET /v1/sports/{sport}/events → /v1/sports/{sport}/events/{id}/ev

python
events = client.list_events("baseball_mlb")
plays = []
for e in events:
    ev = client.get_event_ev("baseball_mlb", e["id"])
    for line in ev["lines"]:
        for o in line["outcomes"]:
            if o["is_plus_ev"]:
                plays.append((o["ev_pct"], e, line, o))

plays.sort(reverse=True, key=lambda p: p[0])
for ev_pct, e, line, o in plays[:25]:
    print(f"{e['home_team']} vs {e['away_team']:<25} "
          f"{line['description']:<25} {o['book_title']:<10} "
          f"{o['name']:<8} {o['price']:+5}  EV {ev_pct:+.2f}%")
03

Filter +EV plays to your books

Pro

I only have accounts at DraftKings and FanDuel — show only those.

GET /v1/sports/{sport}/events/{id}/ev

python
MY_BOOKS = {"draftkings", "fanduel"}
ev = client.get_event_ev("baseball_mlb", event_id=12345)

for line in ev["lines"]:
    keep = [o for o in line["outcomes"]
            if o["book"] in MY_BOOKS and o["is_plus_ev"]]
    for o in keep:
        print(f"{line['description']:<30} {o['book']:<12} "
              f"{o['name']:<8} {o['price']:+5}  EV {o['ev_pct']:+.2f}%")

Resolution & CLV tracking

PropLine grades every prop against real box scores after games close — strikeouts, hits, points, etc. Combined with snapshot history, this lets you compute closing-line value (CLV) for any bet you've placed.

04

Did my pick win, lose, or push?

Pro

Look up the resolution and the actual stat for one prop.

GET /v1/sports/{sport}/events/{id}/results

python
results = client.get_event_results("baseball_mlb", event_id=5885)
for market in results["markets"]:
    if market["key"] != "pitcher_strikeouts": continue
    for o in market["outcomes"]:
        print(f"{o['description']} {o['name']} {market['line']}: "
              f"{o['resolution']} (actual {o['actual_value']})")
# Bryan Woo Over 6.5: lost (actual 6.0)
# Bryan Woo Under 6.5: won (actual 6.0)
05

Compute CLV for a placed bet

Pro

I bet Over 6.5 strikeouts at -110 two hours before first pitch. How did I do vs the closing line?

GET /v1/sports/{sport}/events/{id}/odds/history

python
history = client.get_odds_history(
    "baseball_mlb", event_id=5885, markets=["pitcher_strikeouts"]
)
# Find the latest pre-game snapshot for the matching outcome
for book in history["bookmakers"]:
    for m in book["markets"]:
        for o in m["outcomes"]:
            if o["description"] != "Bryan Woo" or o["name"] != "Over": continue
            snaps = sorted(o["snapshots"], key=lambda s: s["recorded_at"])
            close = snaps[-1] if snaps else None
            if not close: continue
            print(f"{book['key']}: closed at {close['price']} "
                  f"(book reported: {close['book_updated_at'] or 'n/a'})")
            # Compare to your entry: -110 → closing -130 = +CLV
06

Hit rate by market type

What % of Over plays on pitcher_strikeouts hit, league-wide last 30 days?

GET /v1/markets/hit-rates?days=30

python
import httpx
r = httpx.get(
    "https://api.prop-line.com/v1/markets/hit-rates",
    params={"days": 30, "apiKey": "YOUR_KEY"},
)
for row in r.json()["markets"]:
    if row["key"] == "pitcher_strikeouts":
        print(f"Over hit rate (30d): {row['won']}/{row['total']} "
              f"({100*row['won']/row['total']:.1f}%)")

Arbitrage scanning

When one book offers Over +120 and another Under +110 on the same line, both sides cover. PropLine returns every book's price on the same canonical (event, market, line), so the scan is just a comparison.

07

Two-way arbitrage on Over/Under props

Find player props where Over and Under at different books guarantee a profit.

GET /v1/sports/{sport}/events/{id}/odds

python
def implied(price):
    return 100 / (price + 100) if price > 0 else -price / (-price + 100)

odds = client.get_odds("baseball_mlb", event_id=12345,
                       markets=["pitcher_strikeouts"])
# Group by (player, line) across books
by_key = {}
for book in odds["bookmakers"]:
    for m in book["markets"]:
        for o in m["outcomes"]:
            key = (o["description"], o["point"], o["name"])
            by_key.setdefault(key, []).append((book["key"], o["price"]))

# Find Over/Under pairs where best Over + best Under < 1.0 implied
for (player, line, _), entries in by_key.items():
    pass  # see propline-arb-finder repo for full implementation

Reference implementation: proplineapi/propline-arb-finder.

08

Cross-book moneyline arbs

Two-way (h2h) game-line arbitrage scan.

GET /v1/sports/{sport}/odds?markets=h2h

python
odds = client.get_odds("baseball_mlb", markets="h2h")
for ev in odds:
    by_team = {}  # team_name -> list of (book, price)
    for book in ev["bookmakers"]:
        for o in book["markets"][0]["outcomes"]:
            by_team.setdefault(o["name"], []).append((book["key"], o["price"]))
    if len(by_team) != 2: continue
    teamA, teamB = list(by_team.keys())
    bestA = max(by_team[teamA], key=lambda x: x[1])
    bestB = max(by_team[teamB], key=lambda x: x[1])
    impl = implied(bestA[1]) + implied(bestB[1])
    if impl < 1.0:
        print(f"{ev['home_team']} vs {ev['away_team']}: "
              f"{bestA[0]} {teamA} {bestA[1]:+}  +  "
              f"{bestB[0]} {teamB} {bestB[1]:+}  ({100*(1-impl):.2f}% edge)")

Line shopping

Cross-book best prices for the same prop. Useful both for bettors picking the best venue and for builders surfacing savings to end users.

09

Best Over price across books for one player

Where can I get the best price on Bryan Woo Over 6.5 strikeouts?

GET /v1/sports/{sport}/events/{id}/odds

python
odds = client.get_odds("baseball_mlb", event_id=12345,
                       markets=["pitcher_strikeouts"])
best = []
for book in odds["bookmakers"]:
    for m in book["markets"]:
        for o in m["outcomes"]:
            if o["description"] == "Bryan Woo" \
               and o["name"] == "Over" and o["point"] == 6.5:
                best.append((book["key"], o["price"]))

best.sort(key=lambda x: -x[1])
for book, price in best:
    print(f"{book:<12} {price:+}")
10

Spot reduced-juice props

Find pitcher_strikeouts O/U pairs where the combined juice is under 5%.

GET /v1/sports/{sport}/odds

python
odds = client.get_odds("baseball_mlb", markets=["pitcher_strikeouts"])
for ev in odds:
    for book in ev["bookmakers"]:
        for m in book["markets"]:
            pairs = {}  # (player, line) -> {Over: price, Under: price}
            for o in m["outcomes"]:
                k = (o["description"], o["point"])
                pairs.setdefault(k, {})[o["name"]] = o["price"]
            for (player, line), sides in pairs.items():
                if "Over" in sides and "Under" in sides:
                    juice = (implied(sides["Over"])
                             + implied(sides["Under"]) - 1) * 100
                    if juice < 5:
                        print(f"{book['key']:<12} {player:<25} {line}: "
                              f"O {sides['Over']:+} / U {sides['Under']:+} "
                              f"  juice {juice:.2f}%")

Player prop history & hit rates

Backtest strategies player-by-player using past resolved props with full snapshot history.

11

Hit rate for one player on one market

Pro

Did Bryan Woo cover his strikeout Over in his last 10 starts?

GET /v1/sports/{sport}/players/{name}/history

python
hist = client.get_player_history(
    "baseball_mlb", "Bryan Woo",
    market="pitcher_strikeouts", limit=10
)
won = sum(1 for e in hist["entries"] if e["over_result"] == "won")
print(f"Bryan Woo Over (last {len(hist['entries'])}): "
      f"{won}/{len(hist['entries'])} ({100*won/len(hist['entries']):.0f}%)")
for e in hist["entries"]:
    print(f"  {e['commence_time'][:10]} {e['bookmaker_title']} "
          f"line {e['line']} actual {e['actual_value']} → "
          f"Over {e['over_result']}")
12

Filter player history to one book

Pro

Show only DraftKings lines for backtest fidelity.

GET /v1/sports/{sport}/players/{name}/history?bookmaker=

python
hist = client.get_player_history(
    "baseball_mlb", "Bryan Woo",
    market="pitcher_strikeouts",
    bookmaker="draftkings",
    limit=20,
)

Webhooks (push)

Streaming tier subscribers get line-movement and resolution events pushed to their endpoint as soon as PropLine ingests them — HMAC-signed, with retry on failure.

13

Subscribe to line moves on one player

Streaming

Alert me whenever Aaron Judge's home-run prop ticks at any book.

POST /v1/webhooks

python
client.create_webhook({
    "url": "https://your-server.com/propline",
    "events": ["line_movement"],
    "filter_sport_key": "baseball_mlb",
    "filter_player_name": "Aaron Judge",
    "filter_market_key": "batter_home_runs",
    "min_price_change_pct": 1.0,
})
# Returns the secret ONCE — store it for HMAC verification.
14

Verify webhook signatures

Streaming

Confirm an incoming POST is really from PropLine and not a spoof.

X-PropLine-Signature header

python
from propline import PropLine

# In your webhook handler, after reading the raw body bytes:
ok = PropLine.verify_signature(
    secret=WEBHOOK_SECRET,
    timestamp=request.headers["X-PropLine-Timestamp"],
    body=request.body,
    signature=request.headers["X-PropLine-Signature"],
)
if not ok:
    return 401
15

Push line moves into Discord

Streaming

Skip the bot — embed the alerts natively in a Discord channel.

POST /v1/webhooks (format=discord)

python
client.create_webhook({
    "url": "https://discord.com/api/webhooks/.../...",
    "events": ["line_movement", "resolution"],
    "format": "discord",
    "filter_sport_key": "baseball_mlb",
    "min_price_change_pct": 5.0,
})

Walkthrough: /discord-webhooks.

Futures

Season-long markets — championship winner, MVP, division winner. Polled hourly.

16

World Series winner odds, sorted by favorite

Who's the favorite to win the World Series?

GET /v1/sports/baseball_mlb/futures

python
futures = client.get_futures("baseball_mlb")
for event in futures:
    if "World Series" not in event["title"]: continue
    for m in event["markets"]:
        if m["key"] != "world_series_winner": continue
        sorted_odds = sorted(m["outcomes"], key=lambda o: o["price"])
        for o in sorted_odds[:10]:
            print(f"  {o['name']:<25} {o['price']:+5}")

Bulk export & backtesting

Stream the full resolved-prop dataset as CSV — one row per (event, market, bookmaker, outcome) with line, price, resolution, and actual value. Pro tier; tier-gated lookback (90 days on Pro, 365 on Streaming, unlimited on Enterprise). The-odds-api doesn't offer this since they don't grade props.

17

Last 30 days of MLB pitcher strikeouts as CSV

Pro

Pull a CSV I can backtest a model against.

GET /v1/exports/resolved-props

bash
curl -s "https://api.prop-line.com/v1/exports/resolved-props?\
sport=baseball_mlb&market=pitcher_strikeouts&apiKey=YOUR_KEY" \
  -o mlb-strikeouts.csv

# Or in Python (streams to disk):
client.export_resolved_props(
    sport="baseball_mlb",
    market="pitcher_strikeouts",
    out_path="./mlb-strikeouts.csv",
)
18

Quick CSV preview before committing

What does the data look like? No auth required.

GET /v1/exports/sample

bash
curl -s https://api.prop-line.com/v1/exports/sample | head -3
# event_id,sport_key,commence_time,home_team,away_team,...,customer_token
# 5885,baseball_mlb,2026-04-19...,Seattle Mariners,...,public-sample

Note: every export carries a customer_token column tying it to your API key — see the redistribution terms.

Want one we don't cover?

Email hello@prop-line.com with the question and we'll add it (and likely answer you in code). Recipes are intentionally short and copy-paste-able; larger reference implementations live as standalone repos under github.com/proplineapi.