# Order Zero — BOOK-BREADTH TEST **Date:** 2026-07-31 · **Type:** measure-only · **Live adapter: UNTOUCHED** · **Our API key: NOT USED** (measurement ran entirely on PropLine's *public, unauthenticated* endpoints — zero quota spent, zero risk to the live feed). --- ## VERDICT UP FRONT **The order's premise is disproven, and the finding is better than any of the three anticipated outcomes.** The `bookmakers` param exists, but **omitting it already returns every book.** We are not being starved by the feed. **PropLine sends 18 books; we discard 13 of them ourselves**, in `src/utils/oddsNormalizer.js` `ALLOWED_BOOKS`. > **Outcome (d) — ALREADY OPEN.** The constraint is one allow-list in our own > code. No param, no cost, no tier upgrade, no second data source. This is why Step 0 exists. Firing a guessed `bookmakers=` request would have *restricted* the response and produced exactly the false negative the order warned about — "doesn't open" — while the real cause sat in our repo. --- ## STEP 0 — THE PARAM, CONFIRMED FROM THE AUTHORITATIVE SPEC Source: `https://api.prop-line.com/openapi.json` (200, 78KB, 29 paths) — the OpenAPI contract itself, not a marketing page. `GET /v1/sports/{sport_key}/odds` parameters: | param | required | default | documented behaviour | |---|---|---|---| | `markets` | no | `h2h` | comma-separated market keys | | `bookmakers` | no | *(none)* | **"Comma-separated bookmaker keys (e.g. 'draftkings,fanduel'). Omitted = all books. Matches the-odds-api's `bookmakers` param."** | | `period` | no | *(none)* | full-game only unless set | **`Omitted = all books.`** Verbatim from the spec. The param is a *filter*, not an expander. `proplineAdapter.js:152` omitting it is **correct** and always was. §10.1 of MASTER-PLAN said the missing param was the root cause. **That was wrong.** Corrected in place. --- ## STEP 1 — THE MEASUREMENT Two public endpoints, no auth, no key, no quota: 1. `GET /v1/freshness` — per-bookmaker ingestion health = **the real book universe** 2. `GET /v1/exports/sample` — "last 7 days of MLB `pitcher_strikeouts` as CSV… no auth required" — **real resolved props with book, line, price, resolution, actual value, and closing price** ### 1a. The book universe — 18 keys, all live `/v1/freshness`, sampled `2026-08-01T03:13Z`, stale threshold 1800s: | book | markets | staleness | in our `ALLOWED_BOOKS`? | |---|---:|---:|---| | draftkings | 487,182 | 16s | ✅ | | **bovada** | **385,689** | 2s | ❌ dropped | | betmgm | 166,171 | 119s | ✅ | | betrivers | 151,845 | 12s | ✅ | | pinnacle | 144,615 | 6s | ✅ | | **novig** | 132,604 | 0s | ❌ dropped | | prizepicks | 111,475 | 14s | ❌ dropped | | unibet | 64,696 | 15s | ❌ dropped | | **smarkets** | 39,113 | 8s | ❌ dropped | | underdog | 33,632 | 66s | ❌ dropped | | fanduel | 32,189 | 0s | ✅ | | **kalshi** | 27,649 | 5s | ❌ dropped | | **matchbook** | 24,682 | 5s | ❌ dropped | | onexbet | 22,896 | 382s | ❌ dropped | | **polymarket** | 17,347 | 1s | ❌ dropped | | sleeper | 8,857 | 58s | ❌ dropped | | tab_au | 6,183 | 45s | ❌ dropped | | dabble | 4,409 | 51s | ❌ dropped | **Not one book is stale.** The feed is healthy; the loss is entirely ours. ### 1b. The arithmetic that explains "5 MLB books" exactly `ALLOWED_BOOKS` (11) ∩ PropLine (18) = **5**: `draftkings, fanduel, betmgm, betrivers, pinnacle`. **That is precisely the "5 MLB books" the 2.18 audit measured.** Cause identified — it is a set intersection, not a coverage fact. Two separate defects fall out: - **13 of PropLine's 18 discarded by us.** - **6 of our 11 allow-list entries do not exist at PropLine at all** — `caesars, fanatics, bet365, hardrockbet, pointsbet, thescore`. They are inert on the PropLine path. *(They are NOT dead code overall — the odds-api backup path may still carry them. Do not delete them on this evidence.)* ### 1c. Per-prop breadth — MLB `pitcher_strikeouts`, 6 events, 2026-07-31 1,000 rows. 5 complete events / 34 props used for breadth (the 6th event is truncated by the export's 1,000-row cap and is excluded — reporting it would manufacture single-book props out of a row limit). | | mean books/prop | single-book | props visible | |---|---:|---:|---:| | **PropLine sends (param omitted)** | **4.41** | 67.6% | 34 / 34 | | **After `ALLOWED_BOOKS`** | **1.50** | 67.6% | **22 / 34** | **Our filter costs 2.9 books per prop and makes 12 of 34 props invisible entirely** (zero allowed books → the prop does not exist to VYNDR). The distribution is **bimodal, and the shape is the real story**: - **13 "core" props** (probable starters) — **10–12 books each** - **21 tail props** — sole-posted: 13 by `draftkings` alone, 13 by `bovada` alone, 1 by `onexbet`. These are deep/reliever props no one else prices. The headline "67.5% single-book" is therefore **not** a statement about the market for the props we actually grade. It is the long tail of one-book deep props dragging the mean. **On the props that matter, the market is 10–12 books wide.** Book presence across the 13 core props: | book | coverage | role | |---|---|---| | underdog, novig, sleeper, prizepicks, dabble, smarkets, kalshi | **13/13** | 3 exchanges + 4 DFS | | draftkings, betmgm | **13/13** | ✅ ours | | onexbet | 12/13 | offshore | | fanduel | 12/13 | ✅ ours | | betrivers | 10/13 | ✅ ours | ### 1d. The sharp anchor — a real negative **`pinnacle` appears on 0 of 40 props.** It carries 144,615 markets globally but **no MLB player props in this sample.** The one sharp book already in our allow-list contributes **nothing** to MLB props. What *is* present on 100% of core props: **`novig`, `smarkets`, `kalshi`** — and `matchbook`/`polymarket` elsewhere in the feed. These are **exchanges**: order-book priced, so near-zero vig. Arguably a *better* fair-probability reference than Pinnacle. **Stated as a hypothesis, not a fact:** exchange prop books are thinner than their game-line books, and `kalshi`/`polymarket` are prediction markets whose prop coverage is recent. Their prices must be **validated on real data before being trusted as a ruler**, not assumed good because the theory is good. ### 1e. Three buckets, not one list — DFS is not a market price `prizepicks, underdog, sleeper, dabble` cover 13/13 core props, but they are **fixed-payout pick'em, not two-way markets.** Their lines are deliberately shaded and their effective juice is flat. **They must never enter a consensus fair price.** Admitting them "for breadth" would bend the ruler in a new direction — the exact error this order exists to fix. ### 1f. Cost and tier `/odds` returns **all books on the FREE tier**. Nothing about book breadth costs money. Our quota (3 free keys × 1,000/day = 3,000/day) is unchanged by this. Tier gates apply only to the *analytical* endpoints (§ below): Free $0/1k-day · Hobby $9/5k · Pro $19/25k · Streaming Lite $39/100k · Streaming $79/1M · Enterprise custom. ### 1g. ⚠️ WNBA — NOT MEASURED The public sample is **MLB `pitcher_strikeouts` only**. Every per-sport alternative needs the API key, and **`PROPLINE_API_KEY_1/2/3` are absent from the local `.env`** (prod/Coolify only). **Do not assume WNBA mirrors MLB.** WNBA's book set is likely thinner and its exchange coverage thinner still — which would tie directly to its anti-predictive result. **This measurement is outstanding and needs one thing: a PropLine key.** --- ## THE BIGGER FINDING — WE USE 1 OF 29 ENDPOINTS While confirming the param I read the full spec. Several §10 "gaps" are not gaps in the data — they are endpoints we never called. | §10 gap | PropLine endpoint | tier | |---|---|---| | CLV instrument | **`/events/{id}/odds/closing`** — *"the canonical CLV helper"*, last snapshot at/before commence_time per (book, market, outcome) | Paid | | Historical odds archive | `/events/{id}/odds/history` (`from`/`to`/`relative_to`/`interval`/`changes_only`) | Hobby+ | | Bulk archive / backtest | `/exports/odds-history` — full tick firehose | Enterprise / one-time backfill | | Steam detection | **`/events/{id}/movement`** — *"steam … computed across all 16 books we poll"* | Hobby+ | | Line shopping | `/events/{id}/best-line` — best price per (market, player, line) + `all_prices` best-first | appears free | | Cross-book +EV | `/events/{id}/ev` — no-vig via Pinnacle preferred, Bovada fallback | appears free | | **Settlement for non-MLB sports** | `/events/{id}/results`, `/exports/resolved-props` (line, price, resolution, actual value, **closing price**) | Hobby+ / Pro+ | | Park / weather / umpire / lineup | **`/events/{id}/context`** — probable pitchers, confirmed-lineup flag, home-plate umpire, first-pitch weather | **FREE** | | Base rates | `/markets/hit-rates` | — | | Player trend history | `/players/{name}/trends`, `/players/{name}/history` | Hobby+ | Two of these overturn standing beliefs in `CLAUDE.md`: 1. **"NBA/WNBA/soccer have no free settled-result feed"** — PropLine *resolves props across 33 sports* and exports them with actual values. Our settle-nothing-but-MLB-and-WNBA constraint may be a **$19/mo** problem, not a data-availability problem. 2. **We hand-built** probable pitchers, depth charts and lineup confirmation. `/context` serves them, free, joined to the graded outcome. **Caveat, stated plainly:** these are *documented* capabilities. I have verified the **`bookmakers` contract** (a spec-level guarantee) and the **book universe + per-prop breadth** (real data). I have **not** verified what these endpoints actually return for our tier — that needs the key. Do not plan on them as facts yet. --- ## STEP 3 — SCOPE OF THE RULER CHANGE *(scope only — NOT built)* **1. Split one allow-list into three roles.** Today `ALLOWED_BOOKS` answers "can we show this?" and "can we price against this?" with the same list. Those are different questions and conflating them is what bent the ruler. - `TAKEABLE_BOOKS` — a US user can actually bet here. Drives best-price, line-shopping, `takeable`. *(today's list, minus nothing)* - `REFERENCE_BOOKS` — the fair-probability ruler **only**; never surfaced as a place to bet: `pinnacle, novig, smarkets, matchbook, kalshi, polymarket, bovada` - `EXCLUDED_FROM_PRICING` — DFS pick'em (`prizepicks, underdog, sleeper, dabble`) and offshore/intl (`onexbet, unibet, tab_au`). May be surfaced as a separate DFS view; **never** in a consensus price. **2. `fair_prob_lock` becomes a consensus.** Per-book two-way de-vig (`devigTwoWay`, unchanged), then the **median** across reference books — median, not mean, so one stale exchange cannot drag the ruler. **3. Require n≥2 reference books, and label which ruler was used.** Below 2, fall back to today's single-book de-vig and stamp `fair_prob_source: 'consensus_n' | 'single_book'`. **Never silently mix the two** — that pools two different rulers inside one column, the same error class as pooling across a model-version boundary. **4. Stamp a `ruler_version` at the boundary.** Changing the denominator means **every edge and CLV number measured to date was measured on the old ruler and cannot be pooled with post-change rows.** **5. Gate the build on two things that do not exist yet:** (i) exchange prop-price sanity validated on real data (§1d), and (ii) the WNBA measurement (§1g). --- ## STEP 4 — MLB ISOTONIC `p_win` IS PROVISIONAL **Declared explicitly:** the MLB isotonic `p_win` recalibration was measured against the **bent single-book ruler**. It is **PROVISIONAL and GATED behind the ruler fix.** It does **not** finalize or promote until re-run on the real consensus ruler. **Plan sequencing edit:** MLB promotion is **downstream** of the ruler fix, not before it. --- ## WHAT CHANGED IN THE PLAN - §10.1 rewritten — the param premise was wrong; the allow-list is the cause. - §10.2 — book breadth reclassified from "needs a new source" to "one file". - §10.5 — item 1 is now a **build we can do**, not a test to run. - Accrual clock corrected to **sequential / post-completion** with a verification gate (separate ride-in, §11). **And the two interact:** the ruler change resets the denominator, so pre-change settled rows cannot be pooled with post-change rows. That is an *independent* derivation of Kev's sequential-clock rule — the no-pooling boundary is forced by the arithmetic, not just by discipline. --- ## REPRODUCE ```bash curl -s https://api.prop-line.com/openapi.json # the param contract curl -s https://api.prop-line.com/v1/freshness # the 18-book universe curl -s https://api.prop-line.com/v1/exports/sample # real resolved MLB props ``` All three are public. No key was used, printed, or committed. ## OPEN — needs one thing from Kev **A PropLine API key** (it is in prod/Coolify, not in the local `.env`). With it: the WNBA per-sport measurement, and live verification of the ten unused endpoints above — including whether **$19/mo settles NBA and soccer.**