PHASE 1 resolved on our real keys, and a bad source was discarded on the way: a fetched rendering of PropLine's docs "tier matrix" claimed /odds/closing is 403 on free and that /odds returns prices nulled on free. Both are contradicted by direct observation (200-redacted, and 6,196 two-sided PRICED groups on MLB). Not cited. The report uses only the machine-readable OpenAPI contract and the verbatim detail bodies our keys received. Verdict: every one of the six endpoints behaves exactly as the Free tier's published contract says. error:"upgrade_required" with an explicit required_tier is unambiguous -- NOT a key-permission problem, NOT a plan problem. $9/mo Hobby buys /results + /odds/closing (the CLV instrument) + /movement (steam across 18 books); $19/mo Pro adds the 90-day settlement export. Priced and evidenced; not recommended here -- it is a decision. PHASE 2 fingerprint on the SERVED feed: 5 books -> 13, props rendered 546 -> 2,780 (5.1x), mean 4.22 books/prop. The unflattering half, stated up front: of 2,234 newly-visible props only 698 (31.2%) carry a real non-DFS market price; 1,536 (68.8%) are DFS-only pick'em rows. The honest headline is not "80% of the slate unlocked" -- the board is 5x fuller, about a third of the new depth is real market data, and the rest is pick'em inventory now shown but tagged. PHASE 3 verified: 546 gradeable props, unchanged. CURRENT_RULER_VERSION still v1_first_book. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QJs13VsyiSKYQP6rj3NNmc
7.5 KiB
Order Zero — TIER REALITY + AGGREGATOR WIDENING
Date: 2026-08-01 · Phase 1 verified on our real prod keys · Phase 2 LIVE · Phase 3 model path byte-identical.
Gates: 4,028 tests / 322 suites green · next build exit 0 · tier-reality
report per endpoint · widening fingerprinted on the served feed · model input unchanged.
PHASE 1 — THE FREE-TIER DISCREPANCY, RESOLVED
The order was right to demand this. I found and discarded a bad source on the
way. A fetched rendering of PropLine's docs "tier matrix" claimed
/odds/closing is 403 on free and that /odds returns prices nulled on
free. Both are contradicted by direct observation: we get 200 redacted on
/odds/closing, and 6,196 two-sided PRICED prop groups on MLB /odds. That
matrix is not cited anywhere below.
Two authoritative sources are used instead: the machine-readable OpenAPI
contract, and the verbatim detail bodies our own keys received.
The verdict per endpoint
| endpoint | our keys | verbatim reason | verdict |
|---|---|---|---|
/events/{id}/results |
403 | upgrade_required · required_tier: "hobby" · "Prop resolution is a paid feature — start at $9/mo Hobby" |
Free-tier exclusion. $9/mo. |
/exports/resolved-props |
403 ×4 sports | upgrade_required · required_tier: "pro" · "Resolved-prop exports are a Pro feature" |
Free-tier exclusion. $19/mo. |
/events/{id}/odds/closing |
200, 1,847 outcomes, 0 priced, upgrade_url present |
OpenAPI: "Paid tiers: full data. Free tier: redacted (structure only)." | Free-tier redaction. $9/mo. |
/events/{id}/movement |
200, 1,847 outcomes, 0 priced, steam: [] |
OpenAPI: "Paid tiers (Hobby+): full data. Free tier: redacted." | Free-tier redaction. $9/mo. |
/events/{id}/context (MLB) |
200, full data | — | Free. Works. |
/events/{id}/context (WNBA) |
404 · "No context on file for this event yet." | — | Not a tier issue — MLB-only coverage. |
It is NOT a key-permission problem and NOT a plan problem. The keys return
exactly what the Free tier's published contract says they should, on every one
of the six endpoints. error: "upgrade_required" with an explicit
required_tier is as unambiguous as this gets.
What money would buy
| tier | cost | unlocks |
|---|---|---|
| Hobby | $9/mo | /results (settled outcomes + actual values) · /odds/closing (the CLV instrument) · /movement (steam across 18 books) · /odds/history (30d) · /players/trends + /history · /ev |
| Pro | $19/mo | the above + /exports/resolved-props — 90-day bulk settlement CSV incl. closing price, the backtest input |
$9/mo buys the two instruments we have been unable to build — a real closing
line and cross-book steam. /exports/odds-history (the full tick firehose)
stays Enterprise-only at any price we'd consider.
I am not recommending the spend in this order. It is a decision, priced and evidenced, for you to make.
PHASE 2 — AGGREGATOR WIDENING (LIVE)
normalizeProps now emits every DISPLAY book instead of 5 of 18.
Fingerprint — the SERVED MLB feed, post-deploy
| before | after | |
|---|---|---|
| distinct books served | 5 | 13 |
| book rows | — | 11,737 |
| props rendered (player+stat) | 546 | 2,780 |
| mean books/prop | ~0.6 | 4.22 |
Props that render now and rendered nothing before: 2,234. The slate a user sees is 5.1× larger.
The part that isn't flattering, stated up front
Of those 2,234 newly-visible props:
- 698 (31.2%) carry at least one real non-DFS market price — genuinely new market data.
- 1,536 (68.8%) are DFS-only — PrizePicks/Underdog/Sleeper/Dabble rows, which are not a market at all.
So the honest headline is not "80% of the slate was unlocked". It is: the board is 5× fuller, about a third of the new depth is real market data, and the rest is pick'em inventory that is now shown but tagged.
Served role mix: dfs 8,515 · reference 2,042 · both 791 · offshore 389.
book_role is load-bearing, not decorative
Every row carries book_role ∈ {both, takeable, reference, dfs,
offshore} — through the normalizer and through the odds route's grouping,
which was silently dropping it.
Why it matters, from live data. On one MLB prop:
betmgm +450 / -750 ← a real two-way market
prizepicks +100 / +100 ← fixed-payout pick'em
Rendered side by side untagged, the PrizePicks row reads as a dramatically
better price. It is a different product. Untagged breadth would have been a
new way to mislead, dressed as a feature — which is exactly what the split
exists to prevent. Consumers gate on book_role !== 'dfs' before treating a row
as a market price.
The ?book= filter now accepts any DISPLAY book, since shopping a real book
against an exchange is the point.
PHASE 3 — THE MODEL DOES NOT MOVE
bookRoles.js splits MODEL_BOOKS (the legacy allow-list, character for
character) from DISPLAY_BOOKS. Both model paths re-filter before they pick
a line:
gradeSlateService.dedupeProps— filters toMODEL_BOOKSbefore first-row-wins and before the 25-prop limit, so the graded set is byte-identical through the widening. A test asserts exactly this.intradayRefreshService.indexOddsProps— the one that would have bitten. It re-grades props at the current line, so without a gate the widened feed would have silently moved locked lines onto DFS and exchange numbers the model has never been calibrated against.
Props the model can grade: 546 — unchanged.
CURRENT_RULER_VERSION is still v1_first_book.
The gate lifts only when the MLB calibration is re-run on the consensus ruler and v2 is promoted.
HONEST FRAMING — do not let this read as model progress
- This is an AGGREGATOR win. It does not fix the model. Not one grade changed.
- WNBA still abstains. It is a model problem, not a coverage problem — WNBA is better covered than MLB (4.21 vs 3.61 books/prop). Coverage was never its constraint.
- MLB isotonic
p_winis still PROVISIONAL until re-run on the consensus ruler. - The consensus is MARKET, not SHARP.
pinnacle,matchbookandpolymarketare 0% on both sports. No sharp anchor exists in our feed — and none is purchasable from this provider, because it is a coverage fact, not a tier gate. - A third of the new depth is real; two thirds is pick'em. Both are shown; only one is a market.
THREE SUPERSEDED TESTS, UPDATED TO STRONGER PROPERTIES (not deleted)
roleOf(dfs)returned'excluded'→ now names the kind ('dfs'), plus assertions that it is display-visible, non-reference and non-model.- "filters out books not in the allowed set" → now asserts the display set widens with correct role tags, unknown books are still dropped, and the model set does not move.
- "returns 400 for invalid book" (
bovada) → split into: an unknown book still 400s, and a newly-visible one no longer does.
OPEN
- $9/mo Hobby — buys the CLV instrument and steam. Evidenced above; your call.
- $19/mo Pro — adds the 90-day settlement export, which is the backtest input and the thing that makes soccer settle.
- The display layer should now surface multi-book line shopping — the data is live and tagged; no surface consumes it yet. That is a frontend order.