DEV Community

Nikita Iakovlev
Nikita Iakovlev

Posted on

PrizePicks Gave Me Nine Pass-Yard Lines for One Quarterback. Eight Are Over-Only

I run a visa agency in Bali. The other half of my week goes into Apify Actors, and one of them reads the two big pick'em apps — PrizePicks and Underdog Fantasy — and returns both boards as one table.

Pick'em is not sportsbook betting. There is no moneyline and no spread; the app posts a number for a player and a stat, and you say over or under. The number is the product. So the obvious thing to build is a comparison: same player, same stat, two apps, who asks for less.

That join is where everyone — me included, on the first try — gets it wrong. Not because the data is dirty, but because the two boards are not the same shape, and nothing in either payload announces that.

Here is a run from this afternoon, 16 October 2026: props mode, both apps, NFL, 600 rows, one run.

The shape problem in one table

PrizePicks Underdog
Rows returned 473 127
Distinct player + stat pairs 125 127
Rows per pair 3.78 1.00
Prices published none all 127 rows

Underdog shipped exactly one row for every player-and-stat pair — 127 of 127, no duplicates, every one two-sided and priced. PrizePicks shipped 473 rows covering 125 pairs.

Those extra 348 rows are not a paging bug. PrizePicks posts a ladder: the board line plus boosted alternates at other numbers. oddsType names them — standard is the board line, a demon is a harder number that pays more, a goblin an easier one that pays less.

Across the whole pull:

oddsType Rows
demon 249
goblin 128
standard 96
balanced (all Underdog) 127

Only 96 of 473 PrizePicks rows are the number on the board. If you average line grouped by player and stat, you are averaging a marketing ladder.

Nine rungs, one board number

The deepest ladder in the run, C.J. Stroud's passing yards:

line oddsType allowedPicks
189.5 goblin over
199.5 goblin over
224.5 goblin over
246.5 goblin over
256.5 standard under_or_over
264.5 demon over
266.5 demon over
269.5 demon over
374.5 demon over

Nine rows, one player, one stat, a 185-yard spread from bottom to top. The board number is 256.5. Everything else is a priced-up offer sitting on the same player.

And look at the third column, because that is the one that actually bites. Eight of those nine rows are over-only. You cannot take the under on a goblin at 189.5. It isn't a line in the sense you mean when you say "line" — it is a one-sided offer that happens to be rendered as a number. Pull these rows into a model as symmetric over/under lines and you have invented bets the app does not sell.

Ladder depth across the 125 pairs:

Rows for the pair 1 2 3 4 5 6 7 8 9
Pairs 38 10 17 10 16 9 12 11 2

Nearly 70% of pairs carry at least two rows, and in the compare mode only 2 of 46 standard lines had no alternates behind them at all. The median standard line was hiding six or seven.

oddsType and allowedPicks disagree, so read both

The tempting shortcut is oddsType == 'standard'. I used it, then cross-tabbed the two fields on the same 473 rows:

oddsType allowedPicks Rows
demon over 240
goblin over 117
standard under_or_over 75
standard null 16
goblin under_or_over 11
demon under_or_over 9
standard over 5

Three surprises in that table. Twenty boosted alternates are two-sided. Five standard lines are not — they are over-only despite being the board number. And 16 rows don't say at all: allowedPicks comes back null on every Anytime TDs (Combo) row and, less explicably, on seven plain Rec Targets rows.

So the payout type and the permitted sides are independent facts, and one of them is sometimes missing. Treat null as unknown, never as two-sided.

The other thing you lose with a standard filter: 29 of the 125 pairs have no standard row at all. Chris Rodriguez Jr.'s longest rush, Nico Collins' receptions — demons and goblins only. Filter to standard and 23% of the board silently disappears, which is worse than keeping the ladder, because a short list looks like a correct list.

Seven of thirty stat names match

Now the join. PrizePicks used 30 distinct stat names in this run, Underdog 31. Names that match exactly: seven.

FG Made, Kicking Points, Longest Rush, Pass Attempts, Pass TDs, Pass Yards, Rush Yards. That's it. Everything else needs translating:

PrizePicks Underdog
Rec Yards Receiving Yards
Recs Receptions
Rec Targets Targets
Rush Atts Rush Attempts
Longest Rec Longest Reception
Rush+Rec Yds Rush + Rec Yards

Thirty of the 46 comparison rows in my compare run pair two differently-spelled names. A join on raw strings would have returned 16 rows and looked like it worked.

Fuzzy matching is worse than useless here, and this run has the proof sitting right in it. PrizePicks publishes eight (Combo) markets — multi-player parlay-style props — and one of them is literally named Receiving Yards (Combo). Any substring or token-overlap matcher pairs that against Underdog's single-player Receiving Yards. Meanwhile Underdog's plain NFL board carries eight partial-game markets inline — 1H Rush Yards, 1Q Receptions, 1H Rush + Rec TDs — which share every meaningful token with the full-game stat and are a completely different bet. Rush+Rec Yds versus Receiving Yards scores high on most similarity metrics too.

The only thing that works is an explicit alias table and an explicit exclusion list. Boring, finite, and it does not quietly invent pairs on a slate you didn't look at.

What the comparison actually found

Compare mode, same slate, 46 pairs:

Line difference Pairs
0 17
0.5 – 2 14
3 – 5 11
6 – 7 4

The marketing claim about these apps is that they rarely agree. On this slate they agreed exactly on 37% of shared props. Where they differed, PrizePicks had the easier over 16 times and Underdog 13.

The widest gaps, and the thing to notice about them:

Player Stat PrizePicks Underdog Gap Easier over PP alternates
C.J. Stroud Pass Yards 256.5 249.5 7 underdog 8
Dalton Schultz Rec Yards 31.5 24.5 7 underdog 7
Bijan Robinson Rush+Rec Yds 123.5 129.5 −6 prizepicks 6
Bhayshul Tuten Rec Yards 8.5 14.5 −6 prizepicks 5

Seven yards on Stroud's 250 is noise. Seven yards on Schultz's 24.5 is a 29% disagreement about a tight end. Absolute line difference is the wrong unit for comparing across stats, and the field is deliberately raw so you can divide by whatever you like.

Also: easierOver is null on all 17 ties rather than naming a winner. Those are ties, not wins.

Calling it

One player, both boards, everything including the ladder:

curl -X POST "https://api.apify.com/v2/acts/lergassy~dfs-props-scraper/run-sync-get-dataset-items?token=$APIFY_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"mode":"props","players":["Stroud"],"maxRows":50}'
Enter fullscreen mode Exit fullscreen mode

Board lines only, with the ladder depth kept as a number:

from apify_client import ApifyClient

client = ApifyClient("<TOKEN>")
run = client.actor("lergassy/dfs-props-scraper").call(run_input={
    "mode": "compare", "leagues": ["NFL"], "maxRows": 200,
})
for row in client.dataset(run["defaultDatasetId"]).iterate_items():
    rel = abs(row["lineDifference"]) / row["underdogLine"]
    print(row["player"], row["market"], row["prizePicksLine"],
          row["underdogLine"], f"{rel:.1%}", row["prizePicksAlternateLines"])
Enter fullscreen mode Exit fullscreen mode

Both endpoints are public JSON — no account, no cookies, no proxy, no browser. PrizePicks answers 403 to a filtered request on its main host and 429 to bursts, so requests go to the partner host and get spaced out; a rate limit is retried slowly rather than hammered. My three runs today — 11 league rows, 600 props, 46 comparisons — cost about $0.15 in total, with no per-run fee. The Actor is DFS Player Props API if you want to point it at tonight's slate.

When this is the wrong tool

  • You want sportsbook odds. Pick'em apps don't publish a moneyline or a vig. For prices across DraftKings, Pinnacle and the like you need an odds feed and a de-vig step — a different tool (this one, in my case).
  • You want implied probabilities on both apps. PrizePicks publishes a payout type, not a price. There is no honest implied probability to compute from goblin. Quote oddsType for PrizePicks rows and leave the percentage column empty.
  • You want the whole board, historically. Boards move all day and these endpoints serve now. Schedule the run if you want a time series; nobody will hand you yesterday.
  • You want picks. This returns a table. It has no opinion.

One run, two boards, one asymmetry worth internalising: Underdog hands you a row per bet, PrizePicks hands you a row per offer. Count rows and you will think PrizePicks has four times the board. It has roughly the same board, sold four ways.

Top comments (0)