PrizePicks API: 394 NFL Projection Lines Moved Before Kickoff
Does PrizePicks have an API? PrizePicks publishes no developer API. Its payout page describes lineups and multipliers and documents no endpoint you can call (checked 5 October 2026).
OddsPapi carries PrizePicks projections under one slug, prizepicks, in the same JSON as 350+ sportsbooks and exchanges. That puts each PrizePicks line next to the sportsbook prices for the same player and the same stat, and it keeps every snapshot after the fixture is played. We pulled that history for all 15 played NFL Week 5 fixtures: 394 of 654 projections closed on a different line than they opened, and every pass-yards line moved. This guide shows you how to pull the lines, price them against the board and track them to kickoff.
Why every PrizePicks leg is priced at 1.73
PrizePicks is pick’em. You pick More or Less on a player’s projected stat, and the payout depends on how many picks you combine, not on the pick itself. So OddsPapi ships one flat price on every leg: 1.73 on all 550,596 historical snapshots we read across the 15 Week 5 fixtures, and on every live leg we pulled. 1.73 is the square root of 3, and PrizePicks pays a 2-pick Power Play at 3x. The price stays still. The line moves.
That changes how you read the feed. On a sportsbook you compare prices at one line. On PrizePicks you compare the line with the line the sportsbooks price at about 50/50, and you ask what the sportsbooks think your side is worth at PrizePicks’ number. Every Power Play leg must hit, so the break-even per leg is the multiplier’s root:
| Power Play size | Payout (PrizePicks) | Break-even per leg |
|---|---|---|
| 2 picks | 3x | 57.7% |
| 3 picks | 6x | 55.0% |
| 4 picks | 10x | 56.2% |
| 5 picks | 20x | 54.9% |
| 6 picks | 37.5x | 54.7% |
PrizePicks says that Demon, Goblin and other discounted projections change these rates, so check the payout page before you size a lineup. One more thing to know about the slug: the prizepicks row carries only markets. It has no suspended, bookmakerIsActive or fixturePath keys, because those states belong to standard odds. Read every book-level flag with .get(), or one PrizePicks row raises a KeyError in the middle of a board scan.
| Scraping the PrizePicks app | OddsPapi prizepicks |
|
|---|---|---|
| Documentation | None published | Documented REST endpoints and one apiKey parameter |
| Sportsbook prices for the same prop | A second integration per book, plus your own player matching | Same payload, same market ID, same player ID (291 books on Saints v Falcons) |
| Line history | Only what your own recorder saved | /v4/historical-odds on the free tier, from about six days before kickoff |
| Deep link to the pick | Build it yourself | betslip URL on linked legs (56 of 66 live legs on Saints v Falcons) |
| Other sports and venues | PrizePicks only | 350+ bookmakers, exchanges and prediction markets |
Where PrizePicks lines show up
On 5 October 2026 at 13:22 UTC we sampled up to four upcoming fixtures in four tournaments with bookmakers=prizepicks. All 13 carried PrizePicks projections:
| Tournament | Fixtures with lines | Projections per fixture | Stats |
|---|---|---|---|
| NFL | 1 of 1 | 33 | Pass, rush and receiving yards, receptions, attempts, longest rush and completion, sacks, kicking points |
| MLB (postseason) | 4 of 4 | 14 to 22 | Strikeouts, outs, hits allowed, earned runs, walks, hits, bases, singles, hits + runs + RBIs |
| NHL | 4 of 4 | 7 to 17 | Shots on goal, points, assists, blocks |
| NBA Preseason | 4 of 4 | 4 to 18 | Points, points + rebounds + assists |
The NFL window held one fixture, Monday night’s Saints v Falcons. PrizePicks also listed player shots and goalkeeper saves on two UEFA Nations League fixtures that afternoon. In college football, Troy v Southern Miss carried five yardage projections.
Step 1: pull the PrizePicks projections for a fixture
Authentication is the apiKey query parameter. The helper waits out a 429 for as long as the error body says. One call to /v4/markets turns each market ID into a stat name and a line: on PrizePicks, as on every prop market, the line is the market’s handicap, and the player sits under the player’s ID, not under "0". Save this file as s1_projections.py; the later steps import from it.
import os, time, requests
API_KEY = os.environ["ODDSPAPI_API_KEY"]
BASE = "https://api.oddspapi.io/v4"
def get(path, **params):
params["apiKey"] = API_KEY
for _ in range(5):
r = requests.get(f"{BASE}/{path}", params=params, timeout=120)
if r.status_code == 429: # the body says how long to wait
time.sleep(r.json()["error"].get("retryMs", 1500) / 1000 + 0.2)
continue
r.raise_for_status()
return r.json()
raise RuntimeError(f"{path} kept returning 429")
# One catalogue call: market ID -> name, line and outcome names
MARKETS = {m["marketId"]: m for m in get("markets")}
OUTCOME = {o["outcomeId"]: o["outcomeName"] for m in MARKETS.values() for o in m["outcomes"]}
def prizepicks_board(fixture_id):
data = get("odds", fixtureId=fixture_id, bookmakers="prizepicks")
book = data.get("bookmakerOdds", {}).get("prizepicks")
rows = []
if not book:
return rows
for market_id, market in book["markets"].items():
meta = MARKETS[int(market_id)]
for outcome_id, outcome in market["outcomes"].items():
for player_id, p in outcome["players"].items(): # keyed by player ID, not "0"
rows.append({
"player_id": player_id,
"player": p["playerName"],
"stat": meta["marketName"],
"line": meta["handicap"],
"side": OUTCOME[int(outcome_id)],
"price": p["price"],
"active": p["active"],
"betslip": p["betslip"],
"changed": p["changedAt"],
})
return rows
if __name__ == "__main__":
FIXTURE = os.environ.get("FIXTURE", "id1400003171515870") # Saints v Falcons, MNF Week 5
rows = prizepicks_board(FIXTURE)
live = [r for r in rows if r["active"]]
print(f"{len(rows)} legs, {len(live)} active, prices: {sorted({r['price'] for r in rows})}")
print(f"{sum(1 for r in live if r['betslip'])} active legs carry a PrizePicks betslip link")
for r in sorted((r for r in live if r["side"] == "Over" and r["betslip"]),
key=lambda r: -r["line"])[:6]:
stat = r["stat"].replace("Over Under Player ", "").replace(" (incl. overtime)", "")
print(f"{r['player']:<20} {stat:<16} {r['line']:>6} {r['betslip']}")
248 legs, 66 active, prices: [1.73]
56 active legs carry a PrizePicks betslip link
Penix, Michael Pass Yards 224.5 https://app.prizepicks.com/?projections=15266981-o-224.5
Robinson, Bijan Rush Yards 87.5 https://app.prizepicks.com/?projections=15266980-o-87.5
Olave, Chris Receiving Yards 85.5 https://app.prizepicks.com/?projections=15310642-o-85.5
London, Drake Receiving Yards 78.5 https://app.prizepicks.com/?projections=15266983-o-78.5
Vele, Devaughn Receiving Yards 44.5 https://app.prizepicks.com/?projections=15310643-o-44.5
Robinson, Bijan Receiving Yards 39.5 https://app.prizepicks.com/?projections=15266982-o-39.5
Read the active flag before anything else. The Saints v Falcons board held 248 PrizePicks legs and 66 of them were live: the rest are lines PrizePicks has already replaced. The betslip field opens the pick in the PrizePicks app, and the projection ID in it matches the one the app uses. Key on player_id rather than the name, because names arrive as “Last, First” for most players and “First Last” for some.
This call is cheap. Filtered to prizepicks, the Monday-night board came back at 52 KB. The full board for the same fixture was 76.1 MB.
Step 2: find fixtures with PrizePicks lines
Probe the slug alone. A 200 with prizepicks missing means PrizePicks has not posted that fixture yet. Filter /v4/fixtures by tournamentId so you do not sample the KHL when you want the NHL.
import datetime as dt, time
from s1_projections import get, prizepicks_board
# (sportId, tournamentId): NFL, MLB, NHL, NBA Preseason
TOURNAMENTS = {(14, 31): "NFL", (13, 109): "MLB", (15, 234): "NHL", (11, 2382): "NBA Preseason"}
today = dt.date.today()
window = {"from": today.isoformat(), "to": (today + dt.timedelta(days=3)).isoformat()}
for (sport_id, tournament_id), name in TOURNAMENTS.items():
try:
fixtures = get("fixtures", sportId=sport_id, tournamentId=tournament_id, **window)
fixtures = [f for f in fixtures if f.get("hasOdds")]
except Exception: # a 404 here means an empty window, not a failure
fixtures = []
time.sleep(1.0)
found = []
for f in fixtures[:4]: # sample four fixtures a sport
live = [r for r in prizepicks_board(f["fixtureId"]) if r["active"]]
time.sleep(1.0)
if live:
found.append((f["participant1Name"], f["participant2Name"],
len({(r["player_id"], r["stat"]) for r in live})))
print(f"{name}: {len(found)} of {min(4, len(fixtures))} sampled fixtures carry PrizePicks lines")
for home, away, n in found:
print(f" {home} v {away}: {n} projections")
NFL: 1 of 1 sampled fixtures carry PrizePicks lines
New Orleans Saints v Atlanta Falcons: 33 projections
MLB: 4 of 4 sampled fixtures carry PrizePicks lines
Cleveland Guardians v Chicago White Sox: 20 projections
Tampa Bay Rays v New York Yankees: 14 projections
Atlanta Braves v Los Angeles Dodgers: 22 projections
San Diego Padres v Milwaukee Brewers: 19 projections
NHL: 4 of 4 sampled fixtures carry PrizePicks lines
Tampa Bay Lightning v Philadelphia Flyers: 7 projections
Boston Bruins v Ottawa Senators: 17 projections
Pittsburgh Penguins v Winnipeg Jets: 12 projections
Dallas Stars v San Jose Sharks: 11 projections
NBA Preseason: 4 of 4 sampled fixtures carry PrizePicks lines
Atlanta Hawks v Memphis Grizzlies: 4 projections
Detroit Pistons v Phoenix Suns: 8 projections
Philadelphia 76ers v New York Knicks: 18 projections
Milwaukee Bucks v Minnesota Timberwolves: 6 projections
Step 3: price each PrizePicks line against the sportsbooks
Every prop market ID in the catalogue is one stat at one line. So when DraftKings, Caesars or bet365 quote the same market ID for the same player ID, they quote the exact bet PrizePicks offers. The screener below pulls the full board once, collects every two-way sportsbook quote at PrizePicks’ line, drops duplicate price pairs (many slugs copy one feed), and removes the margin from each pair. The median of those no-vig numbers is the board’s probability for your side. Set FIXTURE to a fixture ID from Step 2.
import os, statistics
from s1_projections import get, MARKETS, OUTCOME
# PrizePicks Power Play payouts (prizepicks.com payout page): picks -> multiplier.
# Every leg must hit, so the per-leg break-even is (1 / multiplier) ** (1 / picks).
POWER_PLAY = {2: 3, 3: 6, 4: 10, 5: 20, 6: 37.5}
BREAK_EVEN = {n: (1 / m) ** (1 / n) for n, m in POWER_PLAY.items()}
def screen(fixture_id, min_prices=3):
books = get("odds", fixtureId=fixture_id)["bookmakerOdds"] # full board, no filter
pp = books.get("prizepicks")
if not pp: # PrizePicks has not posted this fixture
return []
results = []
for market_id, market in pp["markets"].items():
outcome_ids = [str(o["outcomeId"]) for o in MARKETS[int(market_id)]["outcomes"]]
for outcome_id, outcome in market["outcomes"].items():
other = next(o for o in outcome_ids if o != outcome_id)
for player_id, leg in outcome["players"].items():
if not leg["active"]:
continue
pairs = set() # dedupe on the price tuple: many slugs copy one feed
for slug, book in books.items():
if slug == "prizepicks" or book.get("suspended") or book.get("bookmakerIsActive") is False:
continue
m = book["markets"].get(market_id)
try:
side = m["outcomes"][outcome_id]["players"][player_id]
opp = m["outcomes"][other]["players"][player_id]
except (TypeError, KeyError):
continue
if side["active"] and opp["active"]:
pairs.add((side["price"], opp["price"]))
if len(pairs) < min_prices:
continue
fair = statistics.median((1 / a) / (1 / a + 1 / b) for a, b in pairs)
results.append((fair, leg["playerName"], MARKETS[int(market_id)]["marketName"],
MARKETS[int(market_id)]["handicap"], OUTCOME[int(outcome_id)], len(pairs)))
return sorted(results, reverse=True)
if __name__ == "__main__":
print("Power Play break-even per leg:", {n: f"{p:.1%}" for n, p in BREAK_EVEN.items()})
rows = screen(os.environ.get("FIXTURE", "id1400003171515870"))
print(f"{len(rows)} PrizePicks legs priced by 3+ distinct sportsbook prices")
for fair, player, stat, line, side, n in rows[:6]:
stat = stat.replace("Over Under Player ", "").replace(" (incl. overtime)", "")
flag = "clears 5-pick" if fair > BREAK_EVEN[5] else ""
print(f"{fair:6.1%} {player:<18} {stat:<18} {side:<5} {line:>6} {n:>2} prices {flag}")
Power Play break-even per leg: {2: '57.7%', 3: '55.0%', 4: '56.2%', 5: '54.9%', 6: '54.7%'}
60 PrizePicks legs priced by 3+ distinct sportsbook prices
54.2% Young, Chase Sacks Over 0.5 3 prices
52.5% Kamara, Alvin Rush Attempts Under 11.5 17 prices
52.3% Penix, Michael Interceptions Under 0.5 22 prices
52.1% Penix, Michael Longest Rush Yards Under 5.5 6 prices
51.6% London, Drake Receiving Yards Over 78.5 6 prices
51.4% Shough, Tyler Pass Completions Over 23.5 21 prices
On Monday’s fixture, 60 live PrizePicks legs had three or more distinct sportsbook prices at the same line. The board rated the best of them at 54.2%, under the 54.9% you need on a 5-pick Power Play. PrizePicks lines sit where the sportsbook lines sit: at 13:10 UTC the PrizePicks line equalled the most common sportsbook main line on 55 of 55 MLB projections and 24 of 24 NHL projections. NFL yardage was looser, with 24 of 38 on the consensus line. Kendre Miller’s rush yards sat at 22.5 on PrizePicks, where the most common sportsbook main line was 26.5. Bijan Robinson’s sat at 87.5 against a most common 81.5. Even there the books that quoted PrizePicks’ exact number priced it close to even, which tells you the books disagree with each other on that player too.
The screener earns its keep when a line moves on one side and not the other. Run it on a timer, sort by probability, and alert when a leg clears the break-even for the lineup size you play. If you would rather get alerts than run your own script, JediBets is a player-props screener that sends them to Discord. For the no-vig maths behind the median, see our three de-vig methods, and for a weighted fair price across the whole board, see consensus odds.
Step 4: track PrizePicks line moves to kickoff
Because the price never changes, a line move on PrizePicks shows up as one market ID going inactive and another market ID going live for the same player. /v4/historical-odds keeps both. The function below takes the first line that went live and the line that was live at kickoff for each projection, then counts how often they differ across a week of played NFL fixtures. Played fixtures return no player names, so join names from a live Step 1 pull on player_id if you want to print them.
import time, collections, datetime as dt
from s1_projections import get, MARKETS, OUTCOME
def ts(s):
return dt.datetime.fromisoformat(s.replace("Z", "+00:00"))
def open_and_close(fixture_id, kickoff):
"""PrizePicks prices every leg the same, so the line IS the price.
Return {(player_id, stat): (opening line, line live at kickoff)}."""
hist = get("historical-odds", fixtureId=fixture_id, bookmakers="prizepicks")
lines = collections.defaultdict(dict) # (player, stat) -> line -> (first active, last state)
for market_id, market in hist["bookmakers"]["prizepicks"]["markets"].items():
meta = MARKETS[int(market_id)]
for outcome_id, outcome in market["outcomes"].items():
if OUTCOME[int(outcome_id)] != "Over":
continue
for player_id, snaps in outcome["players"].items():
pre = sorted((ts(s["createdAt"]), s["active"]) for s in snaps if ts(s["createdAt"]) < kickoff)
first = [t for t, active in pre if active]
if first:
lines[(player_id, meta["marketName"])][meta["handicap"]] = (first[0], pre[-1][1])
out = {}
for key, by_line in lines.items():
live = [line for line, (_, last) in by_line.items() if last]
if len(live) == 1: # one live line at kickoff
opening = min((first, line) for line, (first, _) in by_line.items())[1]
out[key] = (opening, live[0])
return out
if __name__ == "__main__":
week = get("fixtures", sportId=14, tournamentId=31, **{"from": "2026-10-01", "to": "2026-10-06"})
played = [f for f in week if f.get("statusName") == "Finished"]
moved, total = collections.Counter(), collections.Counter()
for f in played:
for (player, stat), (opening, closing) in open_and_close(f["fixtureId"], ts(f["startTime"])).items():
stat = stat.replace("Over Under Player ", "").replace(" (incl. overtime)", "")
total[stat] += 1
moved[stat] += opening != closing
time.sleep(4.6) # /historical-odds needs a longer cooldown
print(f"{len(played)} played fixtures, {sum(total.values())} projections, "
f"{sum(moved.values())} closed on a different line than they opened")
for stat, n in total.most_common(8):
print(f" {stat:<24} {moved[stat]:>3} of {n:<3} moved ({moved[stat] / n:.0%})")
15 played fixtures, 654 projections, 394 closed on a different line than they opened
Receiving Yards 161 of 201 moved (80%)
Longest Rush Yards 59 of 84 moved (70%)
Rush Yards 63 of 69 moved (91%)
Receptions 7 of 58 moved (12%)
Rush Attempts 16 of 42 moved (38%)
Tackles + Assists 3 of 34 moved (9%)
Longest Pass Completion 26 of 30 moved (87%)
Pass Yards 29 of 29 moved (100%)
Six in ten Week 5 projections closed somewhere other than where they opened. The median move was 2 units (yards, on most of them), the 90th percentile 7, and the largest a pass-yards line that went from 232.5 to 258.5 in Bengals v Jaguars. Yardage lines did most of the moving. Count stats held: receptions moved on 12% of projections and tackles + assists on 9%, where one unit is a large share of a 3.5 line.
The history starts early. PrizePicks’ first snapshot on these fixtures landed a median 6.0 days before kickoff, between 3.8 and 7.9 days. That gives you a week of line movement per fixture to test against, and a way to ask the useful question: when PrizePicks moved, had the sportsbooks moved first? Pull the same fixtures for draftkings or pinnacle (three slugs a call, and wait 4.6 seconds between calls) and line up the timestamps. Our backtesting guide covers the loop.
For the prop markets themselves, our NFL player props guide maps the yardage ladders the sportsbooks post, and the MLB player props guide covers strikeouts, hits and home runs. For a one-page view of the slug, see the PrizePicks page.
PrizePicks API FAQ
Does PrizePicks have an API?
PrizePicks publishes no developer API for outside use. OddsPapi carries PrizePicks projections under the prizepicks slug, with the line, the player ID and a betslip link, next to 350+ sportsbooks and exchanges.
Why is every PrizePicks price 1.73?
PrizePicks pays on lineup size, not on the individual pick, so the feed ships one flat price. 1.73 is the square root of the 3x payout on a 2-pick Power Play. The line is the number that moves.
How do I get PrizePicks lines in Python?
Call /v4/odds with a fixtureId and bookmakers=prizepicks, passing your key as the apiKey query parameter. Each market ID maps to a stat and a line in /v4/markets, and each leg sits under the player’s ID.
Can I get historical PrizePicks lines?
Yes. /v4/historical-odds returns every PrizePicks snapshot on a played fixture. On 15 NFL Week 5 fixtures, 394 of 654 projections closed on a different line than they opened.
Which sports does the prizepicks slug cover?
On 5 October 2026 it carried projections on NFL, MLB, NHL and NBA Preseason fixtures, plus college football and UEFA Nations League player props.
Put PrizePicks lines next to the board
Every call in this guide runs on the free key: 350+ bookmakers in one schema, free historical odds for the line-move check, and a WebSocket feed when polling is too slow. Get a free API key and run Step 3 on this week’s NFL slate.