{"openapi":"3.1.0","info":{"title":"ParlayAPI (agent subset)","description":"Curated subset of the ParlayAPI contract, sized for AI agents. Full spec with every endpoint: https://parlay-api.com/openapi.json . Docs: https://parlay-api.com/docs . Free tier, self-serve signup.","version":"3.2.0","x-credit-currency":"credits","x-credit-cost-catalogue-url":"/v1/meta/credit-costs","x-pricing-url":"/v1/pricing","x-usage-url":"/v1/usage"},"servers":[{"url":"https://parlay-api.com","description":"Production (primary; HTTP/2, TLS 1.3)."},{"url":"https://api.parlay-api.com","description":"Production (high-volume; bypasses Cloudflare edge for trading bots above 30 req/min). Same origin, same auth, same endpoints."}],"paths":{"/health":{"get":{"summary":"Health Check","description":"Health check: verifies DB connectivity and data freshness.\n\nAliased at /healthz for the failover Cloudflare Worker (k8s\nconvention) and at /v1/health for symmetry with the rest of the\nversioned API surface (#043).","operationId":"health_check_health_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Health"]}},"/v1/sports/{sport_key}/middles":{"get":{"summary":"Find Middles","description":"Find middle opportunities across bookmakers. 3 credits.\n\nA *middle* is when you bet the Over at a low line on one book and the Under\nat a higher line on another, so there is a window of whole numbers where\nBOTH bets cash. Example: Over 7.5 runs at DraftKings and Under 9.5 at\nFanDuel; if the game lands on 8 or 9, both tickets win, otherwise you win\none and lose the other for a small net cost (the vig).\n\nUnlike arbitrage (guaranteed profit), a middle is a small known cost for a\nshot at a large payout when the result lands in the window. This scans\nevery over/under market with a numeric line, so it covers game totals,\nspreads, AND player-total props (points, rebounds, strikeouts, ...).\n\nEach result carries the window, the exact numbers that hit, the payout if\nit hits (per $100 on each leg), and the net cost if it misses above or\nbelow the window, so you can size the play yourself. Sorted widest-window\nfirst (best chance the result lands in the middle).\n\n**Example:** `GET /v1/sports/baseball_mlb/middles?min_gap=1`","operationId":"find_middles_v1_sports__sport_key__middles_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"min_gap","in":"query","required":false,"schema":{"type":"number","maximum":50.0,"minimum":0.0,"description":"Minimum middle-window width in points/runs/goals (default 1.0). A middle needs at least one whole number strictly inside the window to cash both sides, so 1.0 is the practical floor.","default":1.0,"title":"Min Gap"},"description":"Minimum middle-window width in points/runs/goals (default 1.0). A middle needs at least one whole number strictly inside the window to cash both sides, so 1.0 is the practical floor."},{"name":"min_books","in":"query","required":false,"schema":{"type":"integer","minimum":1,"description":"Minimum distinct books across the group (default 2).","default":2,"title":"Min Books"},"description":"Minimum distinct books across the group (default 2)."},{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of market_keys to limit the scan (e.g. markets=totals for game totals only, or markets=player_points). Omit to scan game totals, spreads, AND player-total props.","title":"Markets"},"description":"CSV of market_keys to limit the scan (e.g. markets=totals for game totals only, or markets=player_points). Omit to scan game totals, spreads, AND player-total props."},{"name":"include_props","in":"query","required":false,"schema":{"type":"boolean","description":"Include player-total props (points, strikeouts, ...) alongside game totals + spreads. Default true.","default":true,"title":"Include Props"},"description":"Include player-total props (points, strikeouts, ...) alongside game totals + spreads. Default true."},{"name":"max_width","in":"query","required":false,"schema":{"type":"number","maximum":50.0,"minimum":0.0,"description":"Optional cap on window width. 0 (default) = no cap. Useful to hide implausibly wide 'middles' that pair a main line with a deep, stale alternate line.","default":0.0,"title":"Max Width"},"description":"Optional cap on window width. 0 (default) = no cap. Useful to hide implausibly wide 'middles' that pair a main line with a deep, stale alternate line."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-credit-cost":3,"x-credit-cost-type":"fixed","x-credit-cost-description":"Cross-book middle opportunities","security":[{"apiKeyHeader":[]},{"apiKeyQuery":[]},{"bearerAuth":[]}],"tags":["Sports & Odds"]}},"/v1/verdict":{"get":{"summary":"Verdict","description":"One-call bet verdict: fair price vs the market + a plain-English call. 5 credits.\n\nGive it a specific bet and it returns the no-vig fair price, how the best\navailable (and your) price compares as EV, which book has the best number,\nand a verdict: BET / LEAN / FAIR / PASS (or NO_DATA). Reuses the same sharp\nno-vig math as /ev and /consensus.\n\nExamples:\n  /v1/verdict?sport=baseball_mlb&team=Yankees&market=h2h&side=Yankees&book=draftkings\n  /v1/verdict?sport=baseball_mlb&home=Toronto Blue Jays&away=Tampa Bay Rays&market=totals&side=over&line=8.5\n  /v1/verdict?sport=baseball_mlb&team=Braves&market=player_hits&player=Austin Riley&side=over&line=1.5&price=-115","operationId":"verdict_v1_verdict_get","parameters":[{"name":"sport","in":"query","required":true,"schema":{"type":"string","description":"sport_key, e.g. baseball_mlb","title":"Sport"},"description":"sport_key, e.g. baseball_mlb"},{"name":"market","in":"query","required":false,"schema":{"type":"string","description":"h2h | spreads | totals | a player prop key (player_points, ...)","default":"h2h","title":"Market"},"description":"h2h | spreads | totals | a player prop key (player_points, ...)"},{"name":"side","in":"query","required":true,"schema":{"type":"string","description":"Team name or home/away (h2h/spreads); over/under (totals/props)","title":"Side"},"description":"Team name or home/away (h2h/spreads); over/under (totals/props)"},{"name":"home","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Home team (with `away`, to identify the game)","title":"Home"},"description":"Home team (with `away`, to identify the game)"},{"name":"away","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Away team","title":"Away"},"description":"Away team"},{"name":"event","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alternative to home/away: 'Away @ Home'","title":"Event"},"description":"Alternative to home/away: 'Away @ Home'"},{"name":"team","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Alternative: one team name to find the game","title":"Team"},"description":"Alternative: one team name to find the game"},{"name":"player","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Player name (required for player-prop markets)","title":"Player"},"description":"Player name (required for player-prop markets)"},{"name":"line","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Line/point for spreads, totals, props","title":"Line"},"description":"Line/point for spreads, totals, props"},{"name":"book","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The book you'd bet at (grades that book's price)","title":"Book"},"description":"The book you'd bet at (grades that book's price)"},{"name":"price","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"The price you're offered (American or decimal). Overrides `book` price.","title":"Price"},"description":"The price you're offered (American or decimal). Overrides `book` price."},{"name":"region","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where you can bet: us (default) | eu | uk | au | ca. Scopes the best-price/shop recommendation to books you can actually use. Omit to use your saved preference or US.","title":"Region"},"description":"Where you can bet: us (default) | eu | uk | au | ca. Scopes the best-price/shop recommendation to books you can actually use. Omit to use your saved preference or US."},{"name":"books","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact CSV of books you can bet at (e.g. draftkings,fanduel,novig). Overrides region; use this for state geo-blocks. Omit to use your saved preference.","title":"Books"},"description":"Exact CSV of books you can bet at (e.g. draftkings,fanduel,novig). Overrides region; use this for state geo-blocks. Omit to use your saved preference."},{"name":"bankroll","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Your bankroll. When the bet is +EV, returns a suggested Kelly stake amount.","title":"Bankroll"},"description":"Your bankroll. When the bet is +EV, returns a suggested Kelly stake amount."},{"name":"kelly","in":"query","required":false,"schema":{"type":"number","maximum":1.0,"minimum":0.0,"description":"Kelly fraction for staking (default 0.5 = half-Kelly, the bankroll-safe standard).","default":0.5,"title":"Kelly"},"description":"Kelly fraction for staking (default 0.5 = half-Kelly, the bankroll-safe standard)."},{"name":"sharpBook","in":"query","required":false,"schema":{"type":"string","description":"Sharp book to anchor the fair line","default":"pinnacle","title":"Sharpbook"},"description":"Sharp book to anchor the fair line"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}}}},"/v1/parlay/verdict":{"post":{"summary":"Parlay Verdict","description":"Grade a multi-leg parlay in one call. 10 credits.\n\nPOST body:\n  {\"legs\": [ {sport, market, side, home, away | team, player, line}, ... ],\n   \"region\"?, \"books\"?, \"book\"?, \"stake\"?, \"sharpBook\"?}\n\nReturns each leg's fair-vs-best, the combined no-vig fair price, the single\nBEST BOOK to place the whole parlay at (a real parlay is one slip at one\nbook, not best-of-each), the parlay's EV, the weakest leg, same-game\ncorrelation warnings, and (with `stake`) the payout. Reuses the /v1/verdict\nengine so single-bet and parlay verdicts agree, and scopes to the books you\ncan bet at (region/books, same as /v1/verdict).","operationId":"parlay_verdict_v1_parlay_verdict_post","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Calculators"]}},"/v1/sports/{sport_key}/best-bets":{"get":{"summary":"Best Bets","description":"The bets worth making right now, ranked. 10 credits.\n\nThe discovery half of the verdict: instead of grading a bet you name, this\nscans the sport's board, grades every candidate with the same no-vig engine\nas /v1/verdict, keeps only bets that are +EV at a book YOU can bet at, and\nranks them by edge. Also returns `edge_alerts` (books showing a price far\noff the market, to grab fast or verify). Region/books/prefs aware.","operationId":"best_bets_v1_sports__sport_key__best_bets_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"region","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Where you can bet: us (default) | eu | uk | au | ca.","title":"Region"},"description":"Where you can bet: us (default) | eu | uk | au | ca."},{"name":"books","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Exact CSV of books you can bet at (overrides region).","title":"Books"},"description":"Exact CSV of books you can bet at (overrides region)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":50,"minimum":1,"description":"Max plays to return.","default":20,"title":"Limit"},"description":"Max plays to return."},{"name":"min_edge","in":"query","required":false,"schema":{"type":"number","maximum":30.0,"minimum":0.0,"description":"Minimum edge %% vs the no-vig fair line.","default":2.0,"title":"Min Edge"},"description":"Minimum edge %% vs the no-vig fair line."},{"name":"min_books","in":"query","required":false,"schema":{"type":"integer","maximum":20,"minimum":2,"description":"Minimum books pricing a play (higher = more reliable).","default":4,"title":"Min Books"},"description":"Minimum books pricing a play (higher = more reliable)."},{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional CSV of market_keys to restrict the scan.","title":"Markets"},"description":"Optional CSV of market_keys to restrict the scan."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Sports & Odds"]}},"/v1/sports":{"get":{"summary":"List Sports","description":"List available sports. FREE, no credits charged, no API key required.\n\nReturns every sport key we serve, including MLB, NFL, NBA, WNBA, NHL, MLS,\nMMA, Boxing, Cricket, horse racing, disc golf,\nesports, volleyball, table tennis, and the full soccer catalog.\n\nSoccer sport keys follow the pattern: soccer_epl, soccer_germany_bundesliga, etc.\n\nsleep_iter_46 #544: ETag + If-None-Match support. The sport list is\npre-warmed at module import and is bytewise-stable per worker, so\n304 round-trips save the full response body for polling clients.\nCache 1h.","operationId":"list_sports_v1_sports_get","parameters":[{"name":"all","in":"query","required":false,"schema":{"type":"boolean","description":"Include inactive sports","default":false,"title":"All"},"description":"Include inactive sports"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Discovery"]}},"/v1/bookmakers":{"get":{"summary":"List Bookmakers","description":"List supported bookmakers with their integration status.\n\nStatus values:\n  active                Currently ingested.\n  merged                Brand merged into another active bookmaker (see merged_into).\n  decommissioned        Bookmaker shut down or no longer publicly accessible.\n  not_yet_integrated    Known book we haven't wired up yet.\n\nDefaults to only active books. Pass ?all=true to see merged / decommissioned\nentries plus the explanation note for each.\n\nBy default each book carries an `endpoints` array (game_lines / live /\nprops / prediction / event_markets / historical) plus `example_paths`\nshowing how to query it. Pass `?include_endpoints=false` for the lean\ncatalog without those.\n\nsleep_iter_22 #521: emits ETag + supports If-None-Match. Registry\nchanges only on code edit (new book wired up; status flip).","operationId":"list_bookmakers_v1_bookmakers_get","parameters":[{"name":"all","in":"query","required":false,"schema":{"type":"boolean","description":"Include non-active (merged/decommissioned) entries","default":false,"title":"All"},"description":"Include non-active (merged/decommissioned) entries"},{"name":"include_endpoints","in":"query","required":false,"schema":{"type":"boolean","description":"Attach endpoints[] + example_paths per book. Set false for the lean catalog.","default":true,"title":"Include Endpoints"},"description":"Attach endpoints[] + example_paths per book. Set false for the lean catalog."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Discovery"]}},"/v1/sports/{sport_key}/events":{"get":{"summary":"List Events","description":"List upcoming events. FREE - no credits charged.","operationId":"list_events_v1_sports__sport_key__events_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"dateFormat","in":"query","required":false,"schema":{"type":"string","default":"iso","title":"Dateformat"}},{"name":"eventIds","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated event IDs","title":"Eventids"},"description":"Comma-separated event IDs"},{"name":"commenceTimeFrom","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commencetimefrom"}},{"name":"commenceTimeTo","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commencetimeto"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"tags":["Sports & Odds"]}},"/v1/sports/{sport_key}/odds":{"get":{"summary":"Get Odds","description":"Get odds for upcoming and live events.\n\nCredits: markets_count x regions_count (same formula as the-odds-api).\n\n**Every sport key in GET /v1/sports is supported**, including every soccer\ncompetition in that list, esports, and volleyball.\n\n**Regions:** us, us2, uk, eu, fr, au, ca, mx, latam, br, asia. Use eu for Pinnacle and European bookmakers.\n\n**Markets:** h2h (moneyline/3-way), spreads, totals.\n\n**Filter by bookmaker:** `?bookmakers=pinnacle,draftkings`\n\n**Live only:** `?live=true` returns events that have already started (same\ncost as a normal call, no extra charge). For a dedicated in-play endpoint\nthat exposes live-tagged player props alongside game lines, see\n`/v1/sports/{sport_key}/live`.\n\n**Slim payload:** `?include=slim` drops raw upstream JSON (~40% smaller).\n\n**Verified-at signal:** every bookmaker's `last_update` is the freshest of\n(price-change, no-change verification heartbeat). On hot-cycle sources\n(Pinnacle, FanDuel) that means a maximum age around the 2-second poll\ninterval, even if the price hasn't moved. Add `?include=verification` to\nalso receive:\n  - `verified_at`: the heartbeat timestamp (we polled and saw the same price)\n  - `line_changed_at`: the last actual price-move timestamp\n  - `is_current`: true iff verified_at is within the last 5 seconds\n\nCombine with shape tokens, e.g. `?include=slim,verification`.\n\n**Example:** `GET /v1/sports/soccer_epl/odds?regions=eu&markets=h2h,spreads&bookmakers=pinnacle`","operationId":"get_odds_v1_sports__sport_key__odds_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"regions","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated: us,us2,uk,eu,au. Default 'us'. iter_047 #420: was previously required (...). Made optional with 'us' default so consumers running pre-existing The-Odds-API-style code (which defaulted to us) don't 422.","default":"us","title":"Regions"},"description":"Comma-separated: us,us2,uk,eu,au. Default 'us'. iter_047 #420: was previously required (...). Made optional with 'us' default so consumers running pre-existing The-Odds-API-style code (which defaulted to us) don't 422."},{"name":"markets","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated: h2h,spreads,totals","default":"h2h","title":"Markets"},"description":"Comma-separated: h2h,spreads,totals"},{"name":"oddsFormat","in":"query","required":false,"schema":{"type":"string","default":"decimal","title":"Oddsformat"}},{"name":"dateFormat","in":"query","required":false,"schema":{"type":"string","default":"iso","title":"Dateformat"}},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker keys (overrides regions)","title":"Bookmakers"},"description":"Comma-separated bookmaker keys (overrides regions)"},{"name":"eventIds","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated event IDs","title":"Eventids"},"description":"Comma-separated event IDs"},{"name":"commenceTimeFrom","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commencetimefrom"}},{"name":"commenceTimeTo","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Commencetimeto"}},{"name":"date","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Shortcut: events whose commence_time falls on this UTC date (YYYY-MM-DD). Sugar for commenceTimeFrom=<date>T00:00:00Z and commenceTimeTo=<date>T23:59:59Z. Explicit commenceTime* values win.","title":"Date"},"description":"Shortcut: events whose commence_time falls on this UTC date (YYYY-MM-DD). Sugar for commenceTimeFrom=<date>T00:00:00Z and commenceTimeTo=<date>T23:59:59Z. Explicit commenceTime* values win."},{"name":"include","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated. Shape tokens: normalized (default), slim (drops raw_json), raw (ids + raw_json only). Add 'verification' to include verified_at, line_changed_at, and is_current per bookmaker.","default":"normalized","title":"Include"},"description":"Comma-separated. Shape tokens: normalized (default), slim (drops raw_json), raw (ids + raw_json only). Add 'verification' to include verified_at, line_changed_at, and is_current per bookmaker."},{"name":"verified","in":"query","required":false,"schema":{"type":"boolean","description":"Alias for include=verification. Adds verified_at, line_changed_at, and is_current per bookmaker.","default":false,"title":"Verified"},"description":"Alias for include=verification. Adds verified_at, line_changed_at, and is_current per bookmaker."},{"name":"live","in":"query","required":false,"schema":{"type":"boolean","description":"Live games only (commence_time at or before now). Equivalent to passing commenceTimeTo=<now>. No extra cost.","default":false,"title":"Live"},"description":"Live games only (commence_time at or before now). Equivalent to passing commenceTimeTo=<now>. No extra cost."},{"name":"include_live","in":"query","required":false,"schema":{"type":"boolean","description":"Include in-progress games in the response (off by default; /odds is for pregame analysis). Set true to get both pregame and live in one call.","default":false,"title":"Include Live"},"description":"Include in-progress games in the response (off by default; /odds is for pregame analysis). Set true to get both pregame and live in one call."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-credit-cost-formula":"len(markets) x len(regions)","x-credit-cost-floor":1,"x-credit-cost-type":"variable","x-credit-cost-example":"?markets=h2h,spreads&regions=us = 2x1 = 2 credits","x-credit-cost-description":"Multi-market multi-region odds","security":[{"apiKeyHeader":[]},{"apiKeyQuery":[]},{"bearerAuth":[]}],"tags":["Sports & Odds"]}},"/v1/sports/{sport_key}/props":{"get":{"summary":"Get Props","description":"Get player prop odds from 10+ sources. 3 credits.\n\nSourced from DraftKings, FanDuel, Caesars, Bovada, Pinnacle, Fliff (real\nAmerican odds), PrizePicks, Underdog, Betr, Pick6, Sleeper, Novig, ProphetX,\nPolymarket (event markets, opt-in via include_event_markets=true).\n\n**Freshness.** Active books are polled every few seconds; the poll cadence\nper source runs ~30-120s depending on the book. This endpoint serves the\nmost recent row per book written within the last 60 minutes, so a quiet\nmarket can return a line several minutes old. Every row (and every entry in\na grouped `books[]`) carries `age_seconds`, the real age of that write. To\nenforce a tighter window pass `?maxAgeSec=` (e.g. `?maxAgeSec=120` for\nprops no older than two minutes); rows past the bound, and rows whose write\ntime can't be parsed, are dropped.\n\n**Supported sports:** NFL, NBA, MLB, NHL, MMA/UFC, Soccer, Tennis, Golf, and more.\n\n**Market keys:** player_points, player_rebounds, player_assists, player_three_pointers,\nplayer_strikeouts, player_hits, player_home_runs, player_total_bases, player_runs,\nplayer_rbis, player_goals, player_shots_on_goal, player_pts_rebs_asts, and 50+ more.\n\n**Example:** `GET /v1/sports/basketball_nba/props?markets=player_points,player_rebounds`\n\n**Filter by player:** `?player=LeBron`\n\n**Filter by book:** `?bookmakers=fliff,pinnacle`\n\n**Polymarket / Kalshi event markets:** `?bookmakers=polymarket` (auto-includes\nevent markets) or `?include_event_markets=true` (any source). For a dedicated\nprediction-market endpoint with question text + event_url, use\n`/v1/prediction-markets/{sport_key}`.\n\n**Pagination headers** (sleep_iter_7 #508): every response carries\n`x-result-page-size`, `x-result-row-count`, `x-result-limit`,\n`x-result-offset`, `x-result-has-more`. When `x-result-has-more: true`, the\nresponse also carries `x-next-offset: <int>` showing the offset to request\nfor the next page. Use these to walk a large result set without truncating\nsilently. The default limit of 5000 is fine for most queries; a\nfull bet365 baseball_mlb response needs ~3 pages of 5000 to retrieve\nall available rows.\n\n**Pagination with `grouped=true`:** `limit` and `offset` count ROWS (one row\nper book) in both modes, so `x-next-offset` means the same thing either way.\nA grouped page therefore returns FEWER entries than `limit` - several rows\ncollapse into one prop - and `x-result-page-size` (props returned) will sit\nbelow `x-result-row-count` (rows behind them). Trust `x-result-has-more`,\nnot the entry count, to decide whether to ask for another page. A prop whose\nbooks straddle a page boundary arrives with a partial `books[]` on each\npage; merge pages on (event_id, player, market_key, line). To avoid that\nentirely, narrow with `?markets=` / `?bookmakers=` so the whole result fits\nin one page.\n\nReturns over/under lines with American odds from each source.","operationId":"get_props_v1_sports__sport_key__props_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated prop market keys (e.g. player_pass_yds,player_points)","title":"Markets"},"description":"Comma-separated prop market keys (e.g. player_pass_yds,player_points)"},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Comma-separated bookmaker keys","title":"Bookmakers"},"description":"Comma-separated bookmaker keys"},{"name":"player","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by player name (partial match)","title":"Player"},"description":"Filter by player name (partial match)"},{"name":"eventId","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Filter by event ID","title":"Eventid"},"description":"Filter by event ID"},{"name":"oddsFormat","in":"query","required":false,"schema":{"type":"string","default":"american","title":"Oddsformat"}},{"name":"dfsOdds","in":"query","required":false,"schema":{"type":"string","description":"DFS normalization: 'midpoint' = +100/-100 (default, zero-vig), 'effective' = per-book implied (PrizePicks/Underdog = -137/-137)","default":"midpoint","title":"Dfsodds"},"description":"DFS normalization: 'midpoint' = +100/-100 (default, zero-vig), 'effective' = per-book implied (PrizePicks/Underdog = -137/-137)"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":1,"description":"Max rows returned (default 5000, max 10000)","default":5000,"title":"Limit"},"description":"Max rows returned (default 5000, max 10000)"},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"description":"Page offset within the result set. Combine with limit for pagination. For results past 10000 rows, narrow via ?markets= or ?bookmakers= filters instead.","default":0,"title":"Offset"},"description":"Page offset within the result set. Combine with limit for pagination. For results past 10000 rows, narrow via ?markets= or ?bookmakers= filters instead."},{"name":"grouped","in":"query","required":false,"schema":{"type":"boolean","description":"Return one entry per prop with a books[] array (recommended) instead of one row per book","default":false,"title":"Grouped"},"description":"Return one entry per prop with a books[] array (recommended) instead of one row per book"},{"name":"include_event_markets","in":"query","required":false,"schema":{"type":"boolean","description":"Include futures and prediction-market rows that lack a single home/away_team (Polymarket yes/no questions, Underdog season-longs, etc). Auto-enabled when bookmakers includes 'polymarket'.","default":false,"title":"Include Event Markets"},"description":"Include futures and prediction-market rows that lack a single home/away_team (Polymarket yes/no questions, Underdog season-longs, etc). Auto-enabled when bookmakers includes 'polymarket'."},{"name":"maxAgeSec","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":3600,"minimum":1},{"type":"null"}],"description":"Only return prop rows written within this many seconds. Every row already carries age_seconds; this drops any older than the bound (and any whose write time can't be parsed). Omit to serve the full recency window (up to 3600s / 60 min).","title":"Maxagesec"},"description":"Only return prop rows written within this many seconds. Every row already carries age_seconds; this drops any older than the bound (and any whose write time can't be parsed). Omit to serve the full recency window (up to 3600s / 60 min)."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-credit-cost":3,"x-credit-cost-type":"fixed","x-credit-cost-description":"Player props for one sport","security":[{"apiKeyHeader":[]},{"apiKeyQuery":[]},{"bearerAuth":[]}],"tags":["Sports & Odds"]}},"/v1/status":{"get":{"summary":"Status Json","description":"**Public, no-auth.** Live endpoint health: per-source freshness,\nrequest-rate-log p50/p95, total request count last hour.\n\nSame data the /status page renders. Refreshes every 90 seconds.\n\nCached 90s with stale-while-revalidate plus background refresh, so a\nrequest never blocks on the heavy per-source aggregate. Warm cache is\nserved directly; a stale cache is served immediately while one worker\nrecomputes off the request path; only a truly cold cache computes inline,\nsingle-flighted and time-bounded so a slow aggregate returns a quick 503\ninstead of hanging into a proxy 500. (Pre-fix, concurrent cold polls each\nran the same ~3s aggregate at once; I/O contention inflated every one to\n~18s and tripped the worker timeout into 500s.)","operationId":"status_json_v1_status_get","responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}}},"tags":["Metadata"]}},"/v1/historical/closing-lines.json":{"get":{"summary":"Closing Lines Json","description":"JSON variant of /v1/historical/closing-lines.csv. Same data,\nsame 6-hour server cache, structured as JSON for programmatic\nconsumers that prefer JSON parsing over CSV.\n\nMetered: 1 credit per 1,000 rows delivered, minimum 1 credit.\nThe response carries X-Export-Rows, X-Export-Credits and\nX-Export-Rate so the cost of a call is always visible. Rows are\ncounted as DELIVERED, not as requested, so a wide limit on a thin\nslate costs only what it returns.\n\nResponse shape:\n  {\n    \"as_of\": \"...\",\n    \"date\": \"YYYY-MM-DD\",\n    \"filters\": {\"sport_key\": \"...\", \"source\": \"...\", \"limit\": N},\n    \"row_count\": N,\n    \"rows\": [\n      {\"game_date\":\"...\",\"sport_key\":\"...\",\"commence_time\":\"...\",\n       \"home_team\":\"...\",\"away_team\":\"...\",\"source\":\"...\",\n       \"player_name\":\"...\",\"market_key\":\"...\",\"market_label\":\"...\",\n       \"line\":N,\"over_price\":N,\"under_price\":N,\n       \"over_implied_prob\":N,\"under_implied_prob\":N,\n       \"snapshot_time\":\"...\"},\n      ...\n    ]\n  }\n\nMemory rule compliance: every row comes from prop_closing_lines,\nwhich only carries observed closing snapshots. We never synthesize\na closing price.","operationId":"closing_lines_json_v1_historical_closing_lines_json_get","parameters":[{"name":"date","in":"query","required":true,"schema":{"type":"string","description":"YYYY-MM-DD (UTC). One day per request.","title":"Date"},"description":"YYYY-MM-DD (UTC). One day per request."},{"name":"sport_key","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional sport_key filter.","title":"Sport Key"},"description":"Optional sport_key filter."},{"name":"source","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"Optional source filter (e.g. pinnacle, draftkings).","title":"Source"},"description":"Optional source filter (e.g. pinnacle, draftkings)."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500000,"minimum":1,"default":50000,"title":"Limit"}}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-credit-cost":1,"x-credit-cost-type":"fixed","x-credit-cost-description":"Daily closing-line JSON download (cached 6h)","security":[{"apiKeyHeader":[]},{"apiKeyQuery":[]},{"bearerAuth":[]}],"tags":["Historical"]}},"/v1/sports/{sport_key}/live":{"get":{"summary":"Get Live Odds","description":"In-play odds only. 3 credits.\n\nReturns events whose `commence_time` is within the last `max_age_hours`\nhours AND is at or before now (i.e. started and presumably still in play).\nPulls from the same `odds_snapshots` table that powers\n/v1/sports/{key}/odds. Game lines (h2h/spreads/totals) come from there;\n\"live\"-tagged player props that some books emit are merged in too. The\ndistinction from /odds is the time filter, not the data source.\n\nsleep_iter_6 #507 (pass-10 finding 6 from the public audit): the prior\nfilter was `commence_time <= now` with no lower bound, which returned\ngames that started 13+ hours ago and had been over for many hours.\nThe default 6-hour window now matches typical game lengths and the\n`max_age_hours` knob lets callers tune it for their sport.\n\n**Example:** `GET /v1/sports/baseball_mlb/live?bookmakers=pinnacle&markets=h2h&max_age_hours=4`","operationId":"get_live_odds_v1_sports__sport_key__live_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"title":"Bookmakers"}},{"name":"markets","in":"query","required":false,"schema":{"type":"string","description":"h2h, spreads, totals (comma-separated)","default":"h2h","title":"Markets"},"description":"h2h, spreads, totals (comma-separated)"},{"name":"regions","in":"query","required":false,"schema":{"type":"string","description":"us, eu, uk, au (comma-separated)","default":"us","title":"Regions"},"description":"us, eu, uk, au (comma-separated)"},{"name":"oddsFormat","in":"query","required":false,"schema":{"type":"string","default":"american","title":"Oddsformat"}},{"name":"include","in":"query","required":false,"schema":{"type":"string","description":"Comma-separated. 'verification' adds verified_at / line_changed_at / is_current per bookmaker.","default":"normalized","title":"Include"},"description":"Comma-separated. 'verification' adds verified_at / line_changed_at / is_current per bookmaker."},{"name":"max_age_hours","in":"query","required":false,"schema":{"type":"number","maximum":24.0,"minimum":0.5,"description":"Only include events whose commence_time is within the last N hours. Default 6 covers a typical MLB or NBA game plus extra innings / overtime. Raise to 12 for events with longer windows (long tennis matches, soccer with stoppage time), or lower to 3 for stricter in-play filtering.","default":6.0,"title":"Max Age Hours"},"description":"Only include events whose commence_time is within the last N hours. Default 6 covers a typical MLB or NBA game plus extra innings / overtime. Raise to 12 for events with longer windows (long tennis matches, soccer with stoppage time), or lower to 3 for stricter in-play filtering."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-credit-cost":3,"x-credit-cost-type":"fixed","x-credit-cost-description":"In-play odds for currently live games","security":[{"apiKeyHeader":[]},{"apiKeyQuery":[]},{"bearerAuth":[]}],"tags":["Sports & Odds"]}},"/v1/sports/{sport_key}/arbitrage":{"get":{"summary":"Find Arbitrage","description":"Find arbitrage opportunities across bookmakers. 10 credits.\n\nEXCLUSIVE. Scans all events across all bookmakers and identifies games\nwhere the combined implied probability is less than 100%, meaning a\nguaranteed profit is possible by betting both sides at different books.\n\n**Example:** `GET /v1/sports/baseball_mlb/arbitrage?minProfit=1`\n\nReturns events with arb opportunities, the optimal bet split, and expected profit %.\n\n**Pagination headers** (sleep_iter_14): every response carries\n`x-result-page-size`, `x-result-limit`, `x-result-offset`,\n`x-result-has-more`, `x-result-total-available`, and (when has-more)\n`x-next-offset`. Body shape unchanged.","operationId":"find_arbitrage_v1_sports__sport_key__arbitrage_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"include_live","in":"query","required":false,"schema":{"type":"boolean","description":"Include games that have already started or finished. Off by default: these endpoints rank BETS, and a bet on a finished game is not actionable. Set true to see commenced games too.","default":false,"title":"Include Live"},"description":"Include games that have already started or finished. Off by default: these endpoints rank BETS, and a bet on a finished game is not actionable. Set true to see commenced games too."},{"name":"minProfit","in":"query","required":false,"schema":{"type":"number","description":"Minimum profit % to include (e.g. 1.5)","default":0,"title":"Minprofit"},"description":"Minimum profit % to include (e.g. 1.5)"},{"name":"exclude_exchanges","in":"query","required":false,"schema":{"type":"boolean","description":"Exclude arbs where either side is anchored on an exchange (novig, prophetx). Exchange asks can be no-volume 'shill' orders that aren't actually takeable. iter_062 #473. When false (default), arbs still surface with an `is_exchange_anchored` flag so callers can choose.","default":false,"title":"Exclude Exchanges"},"description":"Exclude arbs where either side is anchored on an exchange (novig, prophetx). Exchange asks can be no-volume 'shill' orders that aren't actually takeable. iter_062 #473. When false (default), arbs still surface with an `is_exchange_anchored` flag so callers can choose."},{"name":"exclude_books","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of book keys to exclude from EITHER side of every arb. iter_064 #481. Example: `exclude_books=prophetx,novig` to drop arbs anchored on exchanges. Cleaner than exclude_exchanges when you want to drop additional books.","title":"Exclude Books"},"description":"CSV of book keys to exclude from EITHER side of every arb. iter_064 #481. Example: `exclude_books=prophetx,novig` to drop arbs anchored on exchanges. Cleaner than exclude_exchanges when you want to drop additional books."},{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of market_keys to limit the arb scan to. iter_064 #481. Example: `markets=h2h,spreads,totals` for game lines only; `markets=player_points` to focus on points props.","title":"Markets"},"description":"CSV of market_keys to limit the arb scan to. iter_064 #481. Example: `markets=h2h,spreads,totals` for game lines only; `markets=player_points` to focus on points props."},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":1,"description":"sleep_iter_14 #513: max arb opportunities returned per call. Default 10000 effectively returns all (preserves prior behavior since typical query yields <500). Lower it (e.g. limit=20) for a top-N quick view.","default":10000,"title":"Limit"},"description":"sleep_iter_14 #513: max arb opportunities returned per call. Default 10000 effectively returns all (preserves prior behavior since typical query yields <500). Lower it (e.g. limit=20) for a top-N quick view."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":10000,"minimum":0,"description":"Page offset within the sorted (best profit first) arb list. Combine with limit for pagination.","default":0,"title":"Offset"},"description":"Page offset within the sorted (best profit first) arb list. Combine with limit for pagination."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-credit-cost":10,"x-credit-cost-type":"fixed","x-credit-cost-description":"Pre-match arbitrage finder","security":[{"apiKeyHeader":[]},{"apiKeyQuery":[]},{"bearerAuth":[]}],"tags":["Sports & Odds"]}},"/v1/sports/{sport_key}/ev":{"get":{"summary":"Find Positive Ev","description":"Find +EV bets by comparing sharp vs soft book lines. 10 credits.\n\nEXCLUSIVE. Compares Pinnacle (or another sharp book) lines against\nsoft books (DraftKings, FanDuel, Caesars, Bovada). When a soft book's\nodds imply a lower probability than the sharp book, that's +EV.\n\n**Example:** `GET /v1/sports/baseball_mlb/ev?sharpBook=pinnacle&minEdge=3`\n\nA 5% edge means the soft book is offering odds that are 5% better than\nthe sharp book's assessment of true probability.\n\nParameter aliases (iter_056 #453): `min_edge_pct` is an alias for\n`minEdge` (snake-case). Filters honored:\n  - `markets=`  CSV market_key filter (e.g. player_points,player_assists)\n  - `min_books=` minimum number of books in the comparison\n  - `minEdge` / `min_edge_pct`  minimum edge % to include\nEarlier these were silently dropped per iter_056 tester report.","operationId":"find_positive_ev_v1_sports__sport_key__ev_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"include_live","in":"query","required":false,"schema":{"type":"boolean","description":"Include games that have already started or finished. Off by default: these endpoints rank BETS, and a bet on a finished game is not actionable. Set true to see commenced games too.","default":false,"title":"Include Live"},"description":"Include games that have already started or finished. Off by default: these endpoints rank BETS, and a bet on a finished game is not actionable. Set true to see commenced games too."},{"name":"sharpBook","in":"query","required":false,"schema":{"type":"string","description":"Sharp book to use as true odds baseline","default":"pinnacle","title":"Sharpbook"},"description":"Sharp book to use as true odds baseline"},{"name":"minEdge","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Minimum edge % to include (default 2.0)","title":"Minedge"},"description":"Minimum edge % to include (default 2.0)"},{"name":"min_edge_pct","in":"query","required":false,"schema":{"anyOf":[{"type":"number"},{"type":"null"}],"description":"Snake-case alias for minEdge","title":"Min Edge Pct"},"description":"Snake-case alias for minEdge"},{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of market_keys to include (e.g. player_points,player_assists)","title":"Markets"},"description":"CSV of market_keys to include (e.g. player_points,player_assists)"},{"name":"min_books","in":"query","required":false,"schema":{"anyOf":[{"type":"integer","maximum":50,"minimum":1},{"type":"null"}],"description":"Minimum books_compared per row","title":"Min Books"},"description":"Minimum books_compared per row"},{"name":"limit","in":"query","required":false,"schema":{"type":"integer","maximum":500,"minimum":1,"description":"Max EV picks returned (default 200, max 500). sleep_iter_13 #512: prior hardcoded 200 cap is now caller-controllable. Combined with offset for pagination.","default":200,"title":"Limit"},"description":"Max EV picks returned (default 200, max 500). sleep_iter_13 #512: prior hardcoded 200 cap is now caller-controllable. Combined with offset for pagination."},{"name":"offset","in":"query","required":false,"schema":{"type":"integer","maximum":2000,"minimum":0,"description":"Page offset within the sorted EV pick list. Combine with limit for pagination.","default":0,"title":"Offset"},"description":"Page offset within the sorted EV pick list. Combine with limit for pagination."}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-credit-cost":10,"x-credit-cost-type":"fixed","x-credit-cost-description":"Positive expected-value picks","security":[{"apiKeyHeader":[]},{"apiKeyQuery":[]},{"bearerAuth":[]}],"tags":["Sports & Odds"]}},"/v1/sports/{sport_key}/consensus":{"get":{"summary":"Get Consensus","description":"Get consensus (average) odds across all bookmakers. 3 credits.\n\nReturns the average odds, best odds, worst odds, and hold/vig\nfor each market across all bookmakers. Useful for identifying\nwhere your book stands vs the market.\n\nThe average is taken in probability space: each book's American\nprice is converted to its implied probability, those are averaged\ninto `consensus_prob`, and `consensus_odds` is that probability\nconverted back. Averaging American prices directly is invalid\nbecause the scale jumps across zero, so +150 and -150 would\naverage to 0 rather than to even money.\n\niter_058 #462: prediction markets are excluded from the rollup by\ndefault. Kalshi was producing wildly off prices like +3233 for\n'Phillies win' that turned out to reference a different market\n(e.g. series winner, not next game ML) and skewed consensus_odds\nto nonsense values. Use `?include_prediction_markets=true` to\nrestore the prior behavior of mixing them in.","operationId":"get_consensus_v1_sports__sport_key__consensus_get","parameters":[{"name":"sport_key","in":"path","required":true,"schema":{"type":"string","title":"Sport Key"}},{"name":"include_prediction_markets","in":"query","required":false,"schema":{"type":"boolean","description":"Include Kalshi/Polymarket prices in the consensus rollup. Defaults to False because prediction markets often price derivative questions (e.g. 'Phillies win series') that look like h2h ML but skew the average. Set true if you specifically want their prices mixed in.","default":false,"title":"Include Prediction Markets"},"description":"Include Kalshi/Polymarket prices in the consensus rollup. Defaults to False because prediction markets often price derivative questions (e.g. 'Phillies win series') that look like h2h ML but skew the average. Set true if you specifically want their prices mixed in."},{"name":"bookmakers","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of bookmaker keys to include in the rollup. Omit to include all books for the sport. iter_060 #465.","title":"Bookmakers"},"description":"CSV of bookmaker keys to include in the rollup. Omit to include all books for the sport. iter_060 #465."},{"name":"markets","in":"query","required":false,"schema":{"anyOf":[{"type":"string"},{"type":"null"}],"description":"CSV of market_keys to filter to (e.g. h2h,player_points)","title":"Markets"},"description":"CSV of market_keys to filter to (e.g. h2h,player_points)"}],"responses":{"200":{"description":"Successful Response","content":{"application/json":{"schema":{}}}},"422":{"description":"Validation Error","content":{"application/json":{"schema":{"$ref":"#/components/schemas/HTTPValidationError"}}}}},"x-credit-cost":3,"x-credit-cost-type":"fixed","x-credit-cost-description":"Consensus / no-vig fair line","security":[{"apiKeyHeader":[]},{"apiKeyQuery":[]},{"bearerAuth":[]}],"tags":["Sports & Odds"]}}},"x-full-spec":"https://parlay-api.com/openapi.json","x-mcp-server":"pip install parlayapi-mcp"}