Build an NBA prop model.

Three endpoints and real fields: train on graded lines, add recent form from game logs, and score today’s board. The base rates to beat are in the panel above.

Pixel-art magnifying glass examining a bar chart.

Base rates to beat, NBA graded lines

Graded lines45,126
Standard over rate45.6%
Goblin over rate64.4%
Demon over rate20.3%
MarketStandard over rateStandard rowsAll tier rows
Points43.9%7265,417
Pts+Rebs46.0%6955,042
Pts+Rebs+Asts44.4%7234,633
Rebounds47.9%4224,037
Pts+Asts46.2%5634,011
Rebs+Asts49.7%4593,643
Assists44.8%2813,026
3-PT Made53.9%1652,753

Games from 2026-10-28 to 2026-11-02. Pushes excluded. A short sample, so expect these to move. Every market and player

Step 1: pull the graded lines to train on

Start from the training set: one flat row per graded NBA line, with the features known before the game and the result. Drop the pushes, keep the rows where the before-game features are filled in, and split features from label.

import os, requests

API = "https://prizepicksapi.com/api/v1"
H = {"Authorization": f"Bearer {os.environ['PRIZEPICKS_API_KEY']}"}

rows = requests.get(f"{API}/training-set", headers=H, params={
    "league": "NBA", "tier": "standard", "limit": 1000,
}).json()["data"]
rows = [r for r in rows if r["result"] != "push"]
rows = [r for r in rows if r["rest_days"] is not None and r["home_away"] is not None]

X = [[r["closing_line"], r["rest_days"], r["home_away"] == "home"] for r in rows]
y = [r["result"] == "over" for r in rows]

Step 2: add recent form from game logs

The line alone is weak. Add the player’s recent box scores as features, read before the game you are scoring. Game logs are full-game box scores; a stat missing from stats was not reported and is not zero, so handle absent keys rather than defaulting them.

logs = requests.get(f"{API}/game-logs", headers=H, params={
    "player": "Jayson Tatum", "league": "NBA", "last": 10,
}).json()["data"]
# Average only the games before the one you are scoring; never the same game.
pts = [g["stats"].get("points") for g in logs if g.get("minutes")]

Step 3: score today’s board

Fetch the current board for the market, build the same features for each line, and compare your estimate with the line. Read observed_at and only act on lines that are still current.

board = requests.get(f"{API}/projections", headers=H, params={
    "league": "NBA", "stat": "Points", "view": "compact", "limit": 1000,
}).json()["data"]

Avoid the two mistakes that ruin a model

  • Leakage: minutes, actual_value and result are known only after the game. Use them as labels or as past-game features, never as features of the same game.
  • Survivorship: do not train only on lines that still exist today; that drops everything that went unavailable. Use the recorded history as of the decision time.

Try it

Endpoint docs:GET /api/v1/training-setGET /api/v1/game-logsPlans and limits

Frequently asked questions

What endpoints does an NBA prop model use?

Three: /api/v1/training-set for one graded row per line to train on, /api/v1/game-logs for recent-form features, and /api/v1/projections to score today’s board. /api/v1/opponent-stats adds the matchup.

Do I need a paid plan?

Free is enough to inspect fields and try a small baseline. Developer removes the training-row sample and ten-game log limits when the requested training window needs more data. Paying does not guarantee enough history for a reliable model.

Continue building

References