/** * Response shape definitions for PropLine API. * * These mirror the JSON returned by api.prop-line.com and are intentionally * loose — every interface allows extra fields so adding a column server-side * never breaks consumers. Use them as guides, not contracts. */ interface Sport { key: string; title: string; active: boolean; [k: string]: unknown; } interface Event { id: number | string; sport_key: string; home_team: string; away_team: string; commence_time: string; /** * Stable per-team join key ("st_mirren", "chiefs"). Every bookmaker * spelling of a club resolves to the same key, and a published key is * never renamed — key stored per-team data on this, not on the display * name. `null` when the team cannot be identified with certainty * (individual sports like tennis/golf have no team; a small tail of * team sports lacks coverage) — fall back to the name there. */ home_team_key?: string | null; /** Away-side counterpart of `home_team_key`. */ away_team_key?: string | null; /** * The league's own permanent team id, namespaced by source ("mlb:147", * "espn.soccer:363", "espn.nfl:12"). Use `home_team_key` to key data * inside PropLine; use this to join PropLine rows against external * datasets keyed on the same league ids. Sourced from the stats feeds * PropLine grades against, never guessed — `null` where no confirmed id * exists. */ home_team_id?: string | null; /** Away-side counterpart of `home_team_id`. */ away_team_id?: string | null; /** Public team logo image (league CDN), built from `home_team_id`. `null` when the id is null. */ home_team_logo_url?: string | null; /** Away-side counterpart of `home_team_logo_url`. */ away_team_logo_url?: string | null; /** * Event ids that were merged INTO this event when duplicate fixtures * from different bookmakers were folded into one. Always present (there * is no flag); `null`/absent for the large majority of events, which * have never been merged. * * Use it to reconcile a stored id from a response you were already * fetching. The alternative — re-requesting each saved id to see where * it now resolves — costs one request per stored event. */ merged_from_event_ids?: string[] | null; [k: string]: unknown; } interface Outcome { name: string; description?: string | null; price: number; point?: number | null; /** * DFS payout multiplier for boosted/discounted picks (Underdog Fantasy). * Populated on EVERY Underdog outcome; `null`/absent means the book is * not Underdog. `1.0` is a standard pick whose `price` carries the full * payout; e.g. `1.5` (boost) or `0.75` (discount) scales the effective * payout. Keep only `payout_multiplier === 1.0` when comparing DFS lines * to sportsbook consensus so a scaled payout doesn't read as a mispriced * edge — filtering on non-null would drop every Underdog line. */ payout_multiplier?: number | null; /** * PrizePicks projection flavor: `"standard"` (the true market line), * `"goblin"` (easier line / lower payout) or `"demon"` (harder line / * higher payout). `null`/absent for every traditional sportsbook. Filter * to `"standard"` to get PrizePicks's market line — goblin/demon arrive as * their own per-line markets (e.g. `"Points (demon 27.5)"`) so they never * overwrite it. PrizePicks publishes no numeric multiplier for these. */ dfs_odds_type?: "standard" | "goblin" | "demon" | null; /** * PropLine's observed timestamp (ISO datetime) of the last time this * outcome's price actually changed. Distinct from `book_updated_at` (the * book's own publish-time, which only Bovada exposes): `last_change_at` is * derived by PropLine and is populated for every book, including Pinnacle * and PrizePicks. Compare it across books in a single `getOdds` call to * detect repricing lag without a separate `getOddsHistory` call per event. */ last_change_at?: string | null; /** * The last delivery this outcome appeared in (ISO datetime) — the book * still had it on the board at that poll, whether or not the price moved. * Equals the market's `last_update` when the outcome was in the market's * latest delivery; an older value means the book has stopped sending this * selection while still sending the market (a withdrawal in progress). * `last_change_at` = when the price moved; `last_seen_at` = when it was * last offered. `/odds` only; null on rows predating 2026-08-26. */ last_seen_at?: string | null; /** * This book's OWN identifier for the priced selection / contract, for * joining onto its native feed. Kalshi ships the per-contract market * ticker (e.g. `"KXMLBGAME-26AUG08NYYBOS-NYY"`). Only set when the * request passed `includeBookIds: true`; null for books that don't * publish a stable per-selection id. * * NB a two-sided market can share ONE id across both legs — a Kalshi * contract is binary, so Over and Under are its YES and NO sides. The * id identifies the contract; `name` says which side. */ book_outcome_id?: string | null; /** * PropLine's own stable id for this selection (`outcomes.id`), present when * `includeBookIds=true`. Shared with `/odds/history`, `/odds/closing`, the * resolved-props CSV and webhook payloads — the join key across all of them. */ outcome_id?: number | null; /** * Dollars a bettor can actually stake at the quoted `price` — exchange * books that publish resting-offer size (ProphetX, Novig) and Pinnacle, * where it is the book's posted max risk stake on the market. `null`/ * absent for every other book, and for an exchange quote whose size the * feed omitted (never coerced to 0). On a P2P exchange the best price is * often a thin dangling offer with only a few dollars behind it — filter * or discount small values before treating the price as bettable. * Refreshed every poll cycle independently of price movement. Exchange * size changes never appear in `getOddsHistory`; a Pinnacle limit change * does (its own snapshot row, price unchanged), and `getOddsClosing` * carries `opening_liquidity` beside `liquidity`. Neither fires * `line_movement` webhooks. */ liquidity?: number | null; /** * Signed line-difficulty delta for a PrizePicks goblin/demon outcome: * `point - standard_point` for the same player+stat. Positive on a harder * (demon) line, negative on an easier (goblin) line. `null`/absent when the * outcome isn't a PrizePicks goblin/demon, or when no `standard` line exists * for that player+stat (PrizePicks often posts a player goblin/demon-only). * PrizePicks publishes no numeric per-pick multiplier — the flavor plus this * line gap are the modelable signals for fitting per-pick payout adjustments. * Returned on `getOdds`; only ever set on PrizePicks goblin/demon outcomes. */ line_gap?: number | null; /** * Stable, cross-referenceable league player id for joining the SAME player * across books WITHOUT name matching — `"{source}:{league_id}"`: * `"mlb:592450"` (MLBAM person id), `"nba:"`/`"wnba:"` (CDN personId, separate * id spaces), `"nhl:"` (api-web playerId), `"espn:8439"` (ESPN athlete id, for * soccer/NFL/NCAAF). A real league id rather than a name-hash, so it * distinguishes two players with the same name, is stable across seasons, and * cross-references to the league's own API. * * Present on player-prop markets only (always `null` on game lines and * futures), unconditional (no query param), on `getOdds` and `getEventResults`. * `null`/absent whenever we lack a CONFIRMED, unambiguous id — and never * guessed, because a wrong join is worse than a missed one: a sport with no * stable-id stats feed (tennis/golf/UFC/… — null forever), a player who has * never graded, a book spelling that diverges from the league's (`"Elmer * Rodríguez"` gets the id, `"Elmer Rodriguez Cruz"` stays null), or a name two * players share. Coverage warms as games grade after launch. */ player_id?: string | null; [k: string]: unknown; } interface ResolvedOutcome extends Outcome { resolution: "won" | "lost" | "push" | "void" | null; actual_value: number | null; resolved_at: string | null; redacted?: boolean; } interface Market { key: string; /** * The book's OWN name for this market row, and the only thing that separates * a TEAM total from the game total — both ride the `totals` key (e.g. * `"Total"` at 2.5 alongside `"Team Total - Arsenal"` at 1.5). Wording is per * book, so prefer the `team` field below rather than parsing this string. * Present on odds, odds history, closing lines and movement. */ description?: string; /** Game-period bucket (q1..q4, h1/h2, p1..p3, i1..i9, f3/f5/f7). Null for full-game markets. */ period?: string | null; /** * Set when this book has taken the market off the board pregame (the * pull-side twin of the `market_suspended` webhook). Null = on the board. * The outcomes are then the last quoted legs, not a live price. */ suspended_at?: string | null; /** * Canonical event team name when this market is scoped to ONE team — i.e. * a team total — and null for the game total. Both ride the `totals` key, * so this is the machine-readable form of `description`: it matches the * event's `home_team` / `away_team` exactly, so you never parse a book's * wording. Always null outside `totals`. */ team?: string | null; outcomes: Outcome[]; [k: string]: unknown; } interface Bookmaker { key: string; title: string; /** * Public event-page URL at this book. Only set when the request * passed `includeLinks: true` and the book has a verified URL * template (Bovada / DraftKings / FanDuel / BetMGM / Kalshi / * Polymarket / Smarkets); null or absent otherwise. */ link?: string | null; /** * This book's OWN event identifier (Kalshi event ticker, DraftKings / * BetMGM numeric event id, Pinnacle matchup id, ...). Only set when the * request passed `includeBookIds: true` and this book publishes a * stable id; null otherwise. */ book_event_id?: string | null; /** * True when the event is LIVE and this book does not price it in play. * Its prices below are the last PREGAME quote and will not move again * until the game ends — they are not a live price. * * This is the one staleness class `Market.suspended_at` cannot show: * that flag is set when a book pulls a market from a poll, and a book * with no in-play feed is never polled for the fixture once it starts, * so nothing goes missing and nothing is flagged. Always false before * kickoff. * * The rows are still returned rather than withheld, because on the DFS * books the frozen pregame line is the number the bet settles against. * Filter these out yourself if you are pricing in play. */ pregame_only?: boolean; markets: Market[]; [k: string]: unknown; } interface OddsResponse { id: number | string; sport_key: string; home_team: string; away_team: string; commence_time: string; /** * Stable per-team join key ("st_mirren", "chiefs"). Every bookmaker * spelling of a club resolves to the same key, and a published key is * never renamed — key stored per-team data on this, not on the display * name. `null` when the team cannot be identified with certainty * (individual sports like tennis/golf have no team; a small tail of * team sports lacks coverage) — fall back to the name there. */ home_team_key?: string | null; /** Away-side counterpart of `home_team_key`. */ away_team_key?: string | null; /** * The league's own permanent team id, namespaced by source ("mlb:147", * "espn.soccer:363", "espn.nfl:12"). Use `home_team_key` to key data * inside PropLine; use this to join PropLine rows against external * datasets keyed on the same league ids. Sourced from the stats feeds * PropLine grades against, never guessed — `null` where no confirmed id * exists. */ home_team_id?: string | null; /** Away-side counterpart of `home_team_id`. */ away_team_id?: string | null; /** Public team logo image (league CDN), built from `home_team_id`. `null` when the id is null. */ home_team_logo_url?: string | null; /** Away-side counterpart of `home_team_logo_url`. */ away_team_logo_url?: string | null; /** * Event ids that were merged INTO this event when duplicate fixtures * from different bookmakers were folded into one. Always present (there * is no flag); `null`/absent for the large majority of events, which * have never been merged. * * Use it to reconcile a stored id from a response you were already * fetching. The alternative — re-requesting each saved id to see where * it now resolves — costs one request per stored event. */ merged_from_event_ids?: string[] | null; bookmakers: Bookmaker[]; [k: string]: unknown; } interface MarketSummary { key: string; outcomes_count: number; [k: string]: unknown; } interface OutcomeSnapshot { recorded_at: string; price: number; point?: number | null; /** * Stake limit / resting size in force at this snapshot (Pinnacle: its * max risk stake). A Pinnacle limit change with no price move is its own * snapshot row — price/point repeat, this moves — and survives * `changes_only`. Null for other books and on rows before 2026-09-10. */ liquidity?: number | null; [k: string]: unknown; } interface OddsHistoryOutcome { name: string; description?: string | null; /** PropLine's stable id for this selection — shared with `/odds?includeBookIds=true`, `/odds/closing`, the resolved-props CSV and webhook payloads. */ outcome_id?: number | null; snapshots: OutcomeSnapshot[]; snapshots_available?: number; redacted?: boolean; [k: string]: unknown; } interface OddsHistoryMarket { key: string; /** Game-period bucket. Null for full-game markets. */ period?: string | null; /** * Canonical event team name when this market is scoped to ONE team — i.e. * a team total — and null for the game total. Both ride the `totals` key, * so this is the machine-readable form of `description`: it matches the * event's `home_team` / `away_team` exactly, so you never parse a book's * wording. Always null outside `totals`. */ team?: string | null; outcomes: OddsHistoryOutcome[]; [k: string]: unknown; } interface OddsHistoryBookmaker { key: string; title: string; markets: OddsHistoryMarket[]; [k: string]: unknown; } interface OddsHistoryResponse { id: number | string; sport_key: string; home_team: string; away_team: string; commence_time: string; bookmakers: OddsHistoryBookmaker[]; upgrade_url?: string; [k: string]: unknown; } interface ClosingOutcome { name: string; description?: string | null; /** PropLine's stable id for this selection — shared with `/odds?includeBookIds=true`, `/odds/history`, the resolved-props CSV and webhook payloads. */ outcome_id?: number | null; price: number | null; point: number | null; /** recorded_at of the snapshot we picked as "closing" (last at-or-before commence_time). */ closing_at?: string | null; /** Seconds between `closing_at` and kickoff. Large = the book stopped quoting early. */ closing_age_seconds?: number | null; /** True when `closing_age_seconds` > 600 — advisory, not a hard filter. */ is_stale?: boolean; /** American price of the first snapshot in the 14 days before kickoff. */ opening_price?: number | null; /** * Line that went with `opening_price`. On spreads and totals the point * moves as much as the price (-3 -110 -> -3.5 -105), so compare this to * `point` (the closing line), not just the two prices. */ opening_point?: number | null; /** recorded_at of the chosen opening snapshot. */ opening_at?: string | null; /** * Seconds between `opening_at` and kickoff. The archive starts 2026-04, * so for a book/sport PropLine began polling after the line was posted, * "opening" means first-observed-by-us rather than the book's true open — * a value in minutes rather than hours is the tell. */ opening_age_seconds?: number | null; book_updated_at?: string | null; book_version?: number | null; /** Stake limit / resting size at the closing snapshot (Pinnacle: max risk stake). */ liquidity?: number | null; /** Stake limit / resting size at the opening snapshot — compare with `liquidity` to see whether the book raised its limit as the line moved. */ opening_liquidity?: number | null; redacted?: boolean; /** PrizePicks projection tier (standard/goblin/demon); null for sportsbooks. */ dfs_odds_type?: string | null; [k: string]: unknown; } interface ClosingMarket { key: string; description?: string; /** Game-period bucket. Null for full-game markets. */ period?: string | null; /** * Canonical event team name when this market is scoped to ONE team — i.e. * a team total — and null for the game total. Both ride the `totals` key, * so this is the machine-readable form of `description`: it matches the * event's `home_team` / `away_team` exactly, so you never parse a book's * wording. Always null outside `totals`. */ team?: string | null; outcomes: ClosingOutcome[]; [k: string]: unknown; } interface ClosingBookmaker { key: string; title: string; markets: ClosingMarket[]; [k: string]: unknown; } interface OddsClosingResponse { id: number | string; sport_key: string; home_team: string; away_team: string; commence_time: string; bookmakers: ClosingBookmaker[]; upgrade_url?: string; [k: string]: unknown; } interface ScoreEvent { id: number | string; sport_key: string; home_team: string; away_team: string; commence_time: string; /** * Stable per-team join key ("st_mirren", "chiefs"). Every bookmaker * spelling of a club resolves to the same key, and a published key is * never renamed — key stored per-team data on this, not on the display * name. `null` when the team cannot be identified with certainty * (individual sports like tennis/golf have no team; a small tail of * team sports lacks coverage) — fall back to the name there. */ home_team_key?: string | null; /** Away-side counterpart of `home_team_key`. */ away_team_key?: string | null; /** * The league's own permanent team id, namespaced by source ("mlb:147", * "espn.soccer:363", "espn.nfl:12"). Use `home_team_key` to key data * inside PropLine; use this to join PropLine rows against external * datasets keyed on the same league ids. Sourced from the stats feeds * PropLine grades against, never guessed — `null` where no confirmed id * exists. */ home_team_id?: string | null; /** Away-side counterpart of `home_team_id`. */ away_team_id?: string | null; /** Public team logo image (league CDN), built from `home_team_id`. `null` when the id is null. */ home_team_logo_url?: string | null; /** Away-side counterpart of `home_team_logo_url`. */ away_team_logo_url?: string | null; status: "upcoming" | "in_progress" | "final" | string; home_score: number | null; away_score: number | null; [k: string]: unknown; } interface MlbGrandSalamiBook { key: string; title: string; /** Number of games on the slate for which this book quoted a primary game total. */ games_priced: number; /** Sum of each priced game's primary O/U line. */ line: number; /** "over" / "under" / "push" once the slate is final; null until then. */ result: "over" | "under" | "push" | null; } interface MlbGrandSalamiResponse { sport_key: "baseball_mlb"; /** YYYY-MM-DD (UTC). */ date: string; games_total: number; games_completed: number; games_in_progress: number; games_upcoming: number; /** Sum of (home_score + away_score) across completed games. Null until at least one completes. */ actual_total_runs: number | null; bookmakers: MlbGrandSalamiBook[]; } interface NhlDailyGoalsTotalBook { key: string; title: string; /** Number of NHL games on the slate for which this book quoted a primary game total. */ games_priced: number; /** Sum of each priced game's primary O/U line — the implied Daily Goals Total. */ line: number; /** "over" / "under" / "push" once the slate is final; null until then. */ result: "over" | "under" | "push" | null; } interface NhlDailyGoalsTotalResponse { sport_key: "hockey_nhl"; /** YYYY-MM-DD (UTC). */ date: string; games_total: number; games_completed: number; games_in_progress: number; games_upcoming: number; /** Sum of (home_score + away_score) across completed games (incl. OT/SO). Null until at least one completes. */ actual_total_goals: number | null; bookmakers: NhlDailyGoalsTotalBook[]; } interface ResolutionSummarySport { sport_key: string; title: string; graded: number; events: number; } interface ResolutionSummaryMarket { market_key: string; graded: number; } interface ResolutionSummary { days: number; /** Resolution set incl. void. */ total_graded: number; /** won/lost/push only (excl. void). */ total_settled: number; events_graded: number; sports_covered: number; by_sport: ResolutionSummarySport[]; /** Top 12 markets by graded volume. */ top_markets: ResolutionSummaryMarket[]; [k: string]: unknown; } interface PlayerStat { player_name: string; team_abbr: string; stat_type: string; stat_value: number; [k: string]: unknown; } interface StatsResponse { id: number | string; sport_key: string; home_team: string; away_team: string; status: string; home_score: number | null; away_score: number | null; stats: PlayerStat[]; [k: string]: unknown; } interface ResultsMarket { key: string; outcomes: ResolvedOutcome[]; [k: string]: unknown; } interface WeatherInfo { temperature_f: number | null; humidity_pct: number | null; precip_probability_pct: number | null; precip_in: number | null; wind_speed_mph: number | null; wind_gust_mph: number | null; wind_direction_deg: number | null; wind_direction: string | null; conditions: string | null; observed_for: string | null; [k: string]: unknown; } interface ContextResponse { event_id: number | string; sport_key: string; home_team: string; away_team: string; commence_time: string; venue: string | null; roof_type: string | null; is_indoor: boolean; home_probable_pitcher: string | null; away_probable_pitcher: string | null; /** Throwing hand of the probable starter: "L", "R", or "S" (switch). MLB only. */ home_probable_pitcher_hand: string | null; away_probable_pitcher_hand: string | null; lineup_confirmed: boolean; home_plate_umpire: string | null; weather: WeatherInfo | null; updated_at: string | null; [k: string]: unknown; } interface MovementOutcome { name: string; description: string | null; open_price: number | null; open_point: number | null; open_at: string | null; latest_price: number | null; latest_point: number | null; latest_at: string | null; prob_shift: number | null; point_shift: number | null; direction: string | null; num_snapshots: number; redacted: boolean; [k: string]: unknown; } interface MovementMarket { key: string; period: string | null; /** * Canonical event team name when this market is scoped to ONE team — i.e. * a team total — and null for the game total. Both ride the `totals` key, * so this is the machine-readable form of `description`: it matches the * event's `home_team` / `away_team` exactly, so you never parse a book's * wording. Always null outside `totals`. */ team?: string | null; outcomes: MovementOutcome[]; [k: string]: unknown; } interface MovementBookmaker { key: string; title: string; markets: MovementMarket[]; [k: string]: unknown; } interface SteamMove { market: string; period: string | null; name: string; description: string | null; books_quoting: number; books_moved: number; consensus_direction: string; avg_prob_shift: number; consensus_point_shift: number | null; steam_score: number; [k: string]: unknown; } interface MovementResponse { id: number | string; sport_key: string; home_team: string; away_team: string; commence_time: string; bookmakers: MovementBookmaker[]; steam: SteamMove[]; redacted?: boolean; upgrade_url?: string; [k: string]: unknown; } interface ResultsResponse { id: number | string; sport_key: string; home_team: string; away_team: string; status: string; home_score: number | null; away_score: number | null; markets: ResultsMarket[]; context?: ContextResponse | null; upgrade_url?: string; [k: string]: unknown; } interface PlayerHistoryEntry { event_id: number | string; commence_time: string; home_team: string; away_team: string; bookmaker: string; bookmaker_title: string; line: number | null; over_price: number | null; under_price: number | null; actual_value: number | null; over_result: "won" | "lost" | "push" | "void" | null; under_result: "won" | "lost" | "push" | "void" | null; resolved_at: string | null; redacted?: boolean; [k: string]: unknown; } interface PlayerHistoryResponse { player_name: string; sport_key: string; market: string; entries: PlayerHistoryEntry[]; upgrade_url?: string; [k: string]: unknown; } /** Over/under/push tally over a rolling window of recent graded games. */ interface HitRateSplit { window: number; games: number; over: number; under: number; push: number; over_pct: number | null; [k: string]: unknown; } /** Current run of consecutive identical results. */ interface TrendStreak { result: "over" | "under" | "push" | string; length: number; [k: string]: unknown; } /** The most recent graded game for a player on a market. */ interface TrendLastGame { event_id: number | string; commence_time: string; line: number | null; actual_value: number | null; result: "over" | "under" | "push" | string; [k: string]: unknown; } /** Trend summary for a single market. */ interface PlayerMarketTrend { market: string; games_graded: number; reference_bookmaker: string | null; reference_bookmaker_title: string | null; recent_line: number | null; avg_actual: number | null; last_5: HitRateSplit | null; last_10: HitRateSplit | null; last_20: HitRateSplit | null; last_50: HitRateSplit | null; current_streak: TrendStreak | null; last_game: TrendLastGame | null; redacted?: boolean; [k: string]: unknown; } interface PlayerTrends { player_name: string; sport_key: string; /** * Echo of the `dfs_odds_type` filter that scoped these trends (PrizePicks * flavor: `"standard"`/`"goblin"`/`"demon"`). `null` = cross-book, * flavor-agnostic. */ dfs_odds_type?: string | null; markets: PlayerMarketTrend[]; upgrade_url?: string | null; [k: string]: unknown; } interface PlayerGame { event_id: string; commence_time: string; status: string; home_team: string; away_team: string; home_score: number | null; away_score: number | null; /** The box score's own team abbreviation for this player. */ team_abbr: string | null; /** * Null when the player's side can't be identified from `team_abbr`, and * always for individual sports (tennis, golf, UFC) which have no home side. * Left null rather than guessed — a wrong home/away flag would corrupt * every split built on it. */ player_team: string | null; opponent: string | null; is_home: boolean | null; /** Flat map of stat name to value. Vocabulary is per-sport. */ stats: Record; [k: string]: unknown; } interface PlayerGameLog { player_name: string; sport_key: string; /** Echo of the `opponent` filter, or null. */ opponent: string | null; games: PlayerGame[]; [k: string]: unknown; } interface BestPrice { book: string; book_title: string; /** * American odds at this book. Null only on free-tier redacted * responses — book identity and ranking stay visible, the price * is paid. */ price: number | null; /** * When this book last refreshed the market carrying this price — * use it to discount stale quotes. ISO timestamp; null when the * book has no update signal. */ last_update?: string | null; /** * Public event-page URL at this book — the click-out for "go bet * this". Only set when the request passed `includeLinks: true` and * the book has a verified URL template; null or absent otherwise. * Present on free-tier redacted rows too. */ link?: string | null; /** * Dollars bettable at this price — exchange books publishing resting * size only (ProphetX today), null elsewhere. A thin exchange quote * often wins the "best" slot on price alone, so discount rows whose * liquidity is a few dollars. Nulled on free-tier redacted responses. */ liquidity?: number | null; [k: string]: unknown; } interface BestLineSide { /** Highest American price across all books for this side. */ best: BestPrice; /** Every book's price, sorted best-first (descending price). */ all_prices: BestPrice[]; [k: string]: unknown; } interface BestLine { market_key: string; /** Player name (props) or empty string (game lines). */ description: string; /** The line — null for moneyline / 1X2. */ point: number | null; /** * Map of side name → best price + alternatives. Sides are * typically `"Over"`/`"Under"` for player props and totals; * team names for moneylines and spreads. */ sides: Record; [k: string]: unknown; } interface EventBestLineResponse { id: string; sport_key: string; home_team: string; away_team: string; commence_time: string; /** * Books that quoted at least one line on this event. DFS pick'em * books (PrizePicks, Sleeper, Dabble) are always excluded from * best-line responses — their quotes aren't independently bettable * payouts; Underdog is included only at its clean two-way lines * (payout_multiplier == 1.0). */ books_considered: string[]; lines: BestLine[]; /** * True on free-tier responses: structure, book identities, and the * best-first ranking are visible but every price is null. */ redacted?: boolean; /** Set on redacted responses — where to upgrade for full prices. */ upgrade_url?: string | null; [k: string]: unknown; } interface EvOutcome { book: string; book_title: string; /** Outcome label — e.g. `"Over"`, `"Under"`, team name. */ name: string; /** American odds. */ price: number; /** Expected value as a percent on a unit stake. Positive = +EV. */ ev_pct: number; is_plus_ev: boolean; [k: string]: unknown; } interface EvLine { market_key: string; /** Player name (props) or empty string (game lines). */ description: string; /** The line — null for moneyline / 1X2. */ point: number | null; /** Which book anchored the no-vig fair calc (typically `"pinnacle"`). */ fair_source: string; /** Map of outcome name → normalized fair probability. */ fair_probs: Record; outcomes: EvOutcome[]; [k: string]: unknown; } interface EventEvResponse { id: string; sport_key: string; home_team: string; away_team: string; commence_time: string; /** Documents the priority order used for the fair anchor. */ fair_source_default: string; /** Echo of the `devig` option: how the anchor's vig was removed. */ devig_method: "multiplicative" | "shin" | string; lines: EvLine[]; [k: string]: unknown; } interface ProjectionRow { market_key: string; player: string; /** * Stable cross-book player id from the graded-name registry * ("mlb:592450" / "espn:8439" style); null until the player has graded * at least once, or on an ambiguous name. Same semantics as the * per-outcome player_id on /odds. */ player_id: string | null; /** * The market-implied statistical value — the line where the no-vig * P(over) crosses 50%, median across contributing books. Null on the * free tier (redacted teaser). */ projected_value: number | null; consensus_over_prob: number | null; books_contributing: number; last_update: string | null; [k: string]: unknown; } interface EventProjectionsResponse { id: string; sport_key: string; home_team: string; away_team: string; commence_time: string; /** Describes the market-implied method; never a forecast. */ method: string; projections: ProjectionRow[]; redacted: boolean; upgrade_url: string | null; [k: string]: unknown; } interface EventEvCalcResponse { market: string; name: string; point: number | null; description: string; price: number; /** Which book the no-vig fair anchor came from (pinnacle, bovada, ...). */ fair_source: string; /** No-vig fair win probability for `name` at `point`. */ fair_prob: number; /** Win probability implied by the user's `price`. */ implied_prob: number; ev_pct: number; is_plus_ev: boolean; [k: string]: unknown; } interface FuturesOutcome { /** Team or player name. */ name: string; /** American odds. */ price: number | null; price_decimal: number | null; /** * Settlement, for the season-long team futures a final regular-season * table decides — win totals, division winners and the conference #1 * seed on NFL / NBA / MLB. Null on everything else: a championship, a * pennant or a conference title is not in any standings table, and * awards (MVP, Coach of the Year) are published by no free feed, so * those stay honestly unsettled rather than guessed. */ resolution?: "won" | "lost" | "push" | "void" | null; /** * The figure settled against — a team's season wins for a win total, * 1/0 for a yes-style outright. Null until settled. */ actual_value?: number | null; settled_at?: string | null; [k: string]: unknown; } interface FuturesMarket { /** Slugified description, e.g. "world_series_winner". */ key: string; /** Original book label, e.g. "World Series Winner". */ description: string; bookmaker: string; bookmaker_title: string; last_update: string; /** When the book itself reports this market was last updated; null when the book doesn't expose a publish-time signal. */ book_updated_at: string | null; outcomes: FuturesOutcome[]; [k: string]: unknown; } interface FuturesEvent { id: string; sport_key: string; /** The futures title from the book, e.g. "World Series 2026". */ title: string; /** Season-end / target resolution time. */ commence_time: string; markets: FuturesMarket[]; [k: string]: unknown; } interface Webhook { id: number; url: string; secret: string; active: boolean; events: string[]; filter_sport_key: string | null; filter_event_id: number | null; filter_market_key: string | null; filter_player_name: string | null; filter_bookmaker_key: string | null; min_price_change_pct: number | null; min_steam_score: number | null; min_books_agreeing: number | null; /** Batched delivery: up to N events per POST (null = per-event). */ batch_max: number | null; created_at: string; [k: string]: unknown; } interface WebhookDelivery { id: number; webhook_id: number; status: "pending" | "success" | "failed" | string; response_code: number | null; attempts: number; delivered_at: string | null; payload: Record; /** * This subscription's own event counter — the value sent as the * `X-PropLine-Sequence` header. Null on deliveries enqueued before the * sequence shipped; those cannot be replayed. */ seq: number | null; [k: string]: unknown; } /** One event from `replayWebhookEvents`. */ interface ReplayEvent { /** Cursor position. Monotonic within this subscription. */ seq: number; delivery_id: number; event_type: string; created_at: string; /** The canonical payload that was (or would have been) POSTed. */ data: Record; } interface ReplayPage { webhook_id: number; since_seq: number; /** Oldest first, so you can replay them forward. */ events: ReplayEvent[]; /** * Cursor for the next call. Equals the `since_seq` you sent when the page * is empty, so a paging loop needs no special case. */ next_seq: number; has_more: boolean; /** Bounds of what is still retained. Null when nothing is. */ oldest_available_seq: number | null; newest_available_seq: number | null; /** * The most recent sequence ever issued to this subscription. NOT subject to * retention, so `latest_seq - next_seq` is an honest "how far behind am I" * even after the rows themselves are pruned. */ latest_seq: number; /** * TRUE when events after your cursor have already aged out and are gone. * Check this: without it a short `events` array is indistinguishable from * "nothing to catch up on". */ truncated: boolean; retention_note: string | null; } interface DfsPayoutTier { correct: number; multiplier: number; } interface DfsPlayPayout { play_type: "power" | "flex" | string; legs: number; all_correct_multiplier: number; payouts: DfsPayoutTier[]; /** Per-leg win probability needed to break even (independent legs). */ breakeven_leg_win_prob: number; /** Only present when leg_win_prob was supplied in the request. */ expected_return?: number | null; is_plus_ev?: boolean | null; [k: string]: unknown; } interface DfsPayoutsResponse { platform: string; leg_win_prob: number | null; disclaimer: string; plays: DfsPlayPayout[]; [k: string]: unknown; } /** * One placed bet submitted to `gradeClv`. * * `selection` is the subject: player name for a prop, team name for a * game line. Set `side` ("Over" / "Under") for two-way markets; omit it * for YES-only props where the player IS the outcome. */ interface ClvBetInput { /** Echoed back untouched, so rows can be aligned without relying on order. */ ref?: string | null; sport_key: string; event_id: number; market: string; bookmaker: string; selection: string; side?: string | null; point?: number | null; /** Canonical period code (q1, h1, p1, f5). Omit for full-game markets. */ period?: string | null; /** American odds you took. */ price: number; /** Defaults to 1 unit when computing `profit_units`. */ stake?: number | null; } /** One graded bet returned by `gradeClv`. */ interface ClvGradedBet extends ClvBetInput { /** * False whenever the bet could not be pinned to EXACTLY one stored * outcome. Matching is fail-closed: a confident wrong match would report * a real-looking CLV for a different bet, so ambiguity is refused. */ matched: boolean; unmatched_reason?: "event_not_found" | "no_market_for_key" | "no_outcome_for_selection" | "ambiguous_selection" | "no_closing_snapshot" | null; closing_price?: number | null; closing_point?: number | null; closing_at?: string | null; /** Book stopped quoting well before kickoff — advisory, not a hard filter. */ closing_is_stale: boolean; /** * False when the event had not started, i.e. the "closing" snapshot is * just the latest price. These rows are excluded from summary averages. */ closing_is_final: boolean; /** Which book's closing pair the de-vig came from — NOT necessarily yours. */ fair_source?: string | null; closing_fair_prob?: number | null; /** Price-vs-price. Familiar and quotable, but vig-blind. */ clv_pct?: number | null; /** Price vs the DE-VIGGED close. The honest number. */ ev_vs_close_pct?: number | null; beat_close?: boolean | null; resolution?: "won" | "lost" | "push" | "void" | null; actual_value?: number | null; } interface ClvSummary { bets: number; matched: number; unmatched: number; graded: number; /** Matched bets whose event has not started; excluded from the averages. */ pending: number; avg_clv_pct?: number | null; avg_ev_vs_close_pct?: number | null; beat_close_pct?: number | null; profit_units?: number | null; } interface ClvGradeResponse { summary: ClvSummary; bets: ClvGradedBet[]; redacted?: boolean; upgrade_url?: string | null; /** Echo of the `devig` option: how the closing anchor's vig was removed. */ devig_method?: "multiplicative" | "shin" | string; } /** * One leg of a same-game parlay submitted to `priceSgp`, named exactly as * `/odds` names an outcome. Or pass `book_outcome_id` (from * `includeBookIds: true`), which overrides the other fields. */ interface SgpLegInput { /** Market key as served by /odds, e.g. "h2h", "totals", "batter_1plus_hits". */ market?: string | null; /** Outcome name: team name, "Over"/"Under", or the player for YES-only props. */ name?: string | null; /** Outcome description (the player on a two-way prop); "" for game lines. */ description?: string; /** Line exactly as served by /odds. Omit for h2h and YES-only props. */ point?: number | null; /** Canonical period code (q1, h1, f5). Omit for full game. */ period?: string | null; /** * For a TEAM total: the team, as /odds serves it in the market's `team` * field. Omit for the game total — a totals leg with no team matches the * team-less market only. */ team?: string | null; /** * The book's own id from includeBookIds. Overrides the other fields. On * betonlineag / lowvig this is Sportcast's settlement id (MatchWinner_Home). */ book_outcome_id?: string | null; } /** One leg as the book saw it, returned by `priceSgp`. */ interface SgpLegQuote { index: number; market: string; name: string; description: string; point: number | null; period: string | null; /** The team a team total is scoped to, as /odds serves it; null for the game total. */ team: string | null; book_outcome_id: string | null; /** The last price PropLine stored for this leg (American). */ price: number | null; /** The single-leg price the book quoted in the same call — the live number. */ book_price: number | null; accepted: boolean | null; /** The book's own refusal code when it would not take this leg in the slip. */ failure_code?: string | null; } interface SgpQuoteResponse { id: string; sport_key: string; home_team: string; away_team: string; commence_time: string; bookmaker: string; bookmaker_title: string; legs: SgpLegQuote[]; /** True when the book priced the FULL combination; null on the free tier. */ quoted: boolean | null; /** The book's correlated parlay price (American). */ sgp_price: number | null; sgp_price_decimal: number | null; /** Product of the live single-leg prices, as American odds. */ independent_price: number | null; independent_price_decimal: number | null; /** sgp_price_decimal / independent_price_decimal. */ correlation_factor: number | null; priced_at: string | null; redacted?: boolean; upgrade_url?: string | null; } /** One book that could not quote the slip under `bookmaker: "all"`. */ interface SgpBookError { bookmaker: string; bookmaker_title: string; /** The HTTP status the single-book call would have returned. */ status: number; /** The error code from that call's body (e.g. "event_not_at_book"), if any. */ error: string | null; detail: unknown; } /** * `priceSgp(..., "all")`: every supported book quoted on the same legs. * `best_bookmaker` is the quoted book paying the most — on identical legs, * the one charging the smallest correlation reduction. */ interface SgpMultiQuoteResponse { id: string; sport_key: string; home_team: string; away_team: string; commence_time: string; bookmaker: "all"; quotes: SgpQuoteResponse[]; errors: SgpBookError[]; best_bookmaker: string | null; redacted?: boolean; upgrade_url?: string | null; } /** Options for {@link PropLineClient.getDfsPayouts}. */ interface GetDfsPayoutsOptions { /** DFS platform. Only "prizepicks" today. */ platform?: string; /** * Assumed per-leg win probability in [0, 1]. When supplied, each play * also carries `expected_return` (per $1) and `is_plus_ev` at that rate. */ legWinProb?: number; } /** * Structured error body returned by gated/throttled endpoints * (see https://prop-line.com/docs#errors). Branch on `error` — the * codes are stable — and follow the URLs instead of parsing prose. */ interface PropLineErrorInfo { /** Stable machine-readable code, e.g. "upgrade_required", "daily_limit_exceeded". */ error?: string; /** Human-readable sentence. */ message?: string; /** Cheapest tier that unlocks a gated feature (403s). */ required_tier?: string; /** Where to unlock it — pre-filled one-click URL on daily-cap 429s. */ upgrade_url?: string; docs_url?: string; signup_url?: string; backfill_url?: string; /** Burst-limit backoff hint (429s). */ retry_after_seconds?: number; /** Daily-cap 429s: recommended next plan incl. its own upgrade_url. */ recommended?: { plan?: string; upgrade_url?: string; [key: string]: unknown; }; [key: string]: unknown; } /** Base error for all PropLine API failures. */ declare class PropLineError extends Error { readonly statusCode: number; /** Human-readable detail message. */ readonly detail: string; /** Structured error body, when the API returned one. */ readonly info?: PropLineErrorInfo; constructor(statusCode: number, detail: string, info?: PropLineErrorInfo); /** Stable machine-readable code (e.g. "upgrade_required"), if present. */ get errorCode(): string | undefined; /** The URL that unlocks a gated feature or lifts a cap, if present. */ get upgradeUrl(): string | undefined; } /** Thrown when the API key is missing or invalid (HTTP 401). */ declare class AuthError extends PropLineError { constructor(detail?: string, info?: PropLineErrorInfo); } /** Thrown when the daily request limit is exceeded (HTTP 429). */ declare class RateLimitError extends PropLineError { constructor(detail?: string, info?: PropLineErrorInfo); } interface PropLineOptions { /** API base URL. Default: `https://api.prop-line.com/v1`. */ baseUrl?: string; /** Request timeout in milliseconds. Default: 15000. */ timeoutMs?: number; /** Custom fetch implementation. Defaults to global `fetch` (Node 18+). */ fetch?: typeof fetch; } /** * Game-period filter. String of canonical codes, optionally * comma-separated, or the sentinel `"all"`. Omitted = full-game * markets only (backwards-compatible default). * * "q1" — 1st quarter * "q1,q2" — 1st and 2nd quarters * ["q1","q2"] — same, as an array * "h1" — 1st half * "p1"|"p2"|"p3" — hockey periods * "i6" — 6th inning * "f3"|"f5"|"f7" — first N innings * "all" — every period including full game */ type PeriodFilter = string | string[]; interface GetOddsOptions { /** Specific event ID to get odds (with player props) for. Omit for bulk odds. */ eventId?: number | string; /** * Market keys to filter by. If omitted, the bulk `/odds` endpoint * defaults to `h2h` and the per-event `/odds` endpoint defaults to * `h2h,spreads,totals` — game-line markets every book carries across * every sport. Pass an explicit list to fetch player props (e.g. * `["pitcher_strikeouts", "batter_home_runs"]` for MLB, * `["player_points", "player_rebounds"]` for NBA). */ markets?: string[]; /** Game-period filter — see `PeriodFilter`. */ period?: PeriodFilter; /** * Bookmaker key(s) to restrict the response to (e.g. `"draftkings"` or * `["draftkings", "fanduel"]`). Omitted = all books. Same parameter name * as the-odds-api. */ bookmakers?: string | string[]; /** * When true, each bookmaker block carries a `link` — that book's * public event-page URL (plain navigation, no affiliate tagging), * so your UI can click out from a line to the book. Links ship for * Bovada, DraftKings, FanDuel, BetMGM, Kalshi, Polymarket and * Smarkets; other books return null. Maps to the * the-odds-api-compatible `includeLinks=true` query param. */ includeLinks?: boolean; /** * When true, each bookmaker block carries a `book_event_id` and each * outcome a `book_outcome_id` — that book's OWN identifiers for the * event and the priced selection. Use these to join PropLine rows onto * a book's native feed by id instead of matching on team names, * players and lines. Kalshi ships both (the event ticker and the * per-contract market ticker, e.g. `KXMLBGAME-26AUG08NYYBOS-NYY`); * most other books ship an event id. Books without a stable id return * null. * * NB a two-sided market can share ONE `book_outcome_id` across both * legs — a Kalshi contract is binary, so Over and Under are its YES * and NO sides. The id identifies the contract; the outcome's `name` * says which side. * * PropLine-specific (`includeBookIds=true`); the-odds-api has no * equivalent. */ includeBookIds?: boolean; } interface GetOddsHistoryOptions { markets?: string[]; /** ISO timestamp; only include snapshots at or after this time. Mutually exclusive with `relativeFrom`. */ from?: string; /** ISO timestamp; only include snapshots at or before this time. Mutually exclusive with `relativeTo`. */ to?: string; /** Offset relative to commence_time, e.g. "-3h", "-30m", "-90s". Mutually exclusive with `from`. */ relativeFrom?: string; /** Offset relative to commence_time, e.g. "-1m" or "0" for commence_time itself. Mutually exclusive with `to`. */ relativeTo?: string; /** Downsample to one snapshot per bucket. Latest snapshot in each bucket wins. */ interval?: "30s" | "1m" | "5m" | "15m" | "30m" | "1h"; /** When true, drop snapshots whose (price, point) match the previous one. Opening line is always kept. */ changesOnly?: boolean; /** Game-period filter — see `PeriodFilter`. */ period?: PeriodFilter; /** Bookmaker key(s) to restrict the response to. Omitted = all books. */ bookmakers?: string | string[]; } interface GetOddsClosingOptions { markets?: string[]; /** Game-period filter — see `PeriodFilter`. */ period?: PeriodFilter; /** Bookmaker key(s) to restrict the response to. Omitted = all books. */ bookmakers?: string | string[]; } interface GetMovementOptions { markets?: string[]; /** Game-period filter — see `PeriodFilter`. */ period?: PeriodFilter; /** Bookmaker key(s) to restrict the response to. Omitted = all books. */ bookmakers?: string | string[]; } interface GetScoresOptions { /** Days back to include (default 3). */ daysFrom?: number; } interface GetMlbGrandSalamiOptions { /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */ date?: string; } interface GetNhlDailyGoalsTotalOptions { /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */ date?: string; } interface GetStatsOptions { /** Stat types to filter by (e.g. `["strikeouts", "hits"]`). */ statType?: string[]; } interface GetResultsOptions { markets?: string[]; } interface GetPlayerHistoryOptions { /** Market key (e.g. `"pitcher_strikeouts"`). Required. */ market: string; /** Restrict to a single bookmaker (e.g. `"draftkings"`). */ bookmaker?: string; /** Max entries (1-100). Default 20. */ limit?: number; } interface GetPlayerGamesOptions { /** Games to return, 1-100. Default 20. */ limit?: number; /** * Head-to-head filter. Accepts a full name, nickname or abbreviation * ("Boston Red Sox", "Red Sox", "BOS"). The limit applies AFTER this * filter, so `{ opponent: "BOS", limit: 10 }` is the last 10 MEETINGS, * not the Boston games among the last 10 games. Not capped to the * current season. */ opponent?: string; /** * Stat name(s) to return; omit for all. Vocabulary is per-sport — * see https://prop-line.com/docs#stats */ statType?: string | string[]; } interface GetPlayerTrendsOptions { /** Market key (e.g. `"batter_total_bases"`). Omit for all markets. */ market?: string; /** * PrizePicks pick-em flavor to compute trends against: `"standard"` * (default market line), `"goblin"`, or `"demon"`. When set, the trend is * computed against that flavor's PrizePicks line only. Omit for the default * cross-book behavior. Flavor tagging began 2026-06-16. */ dfsOddsType?: "standard" | "goblin" | "demon"; } interface GetEventProjectionsOptions { /** Optional market-key filter (comma-separated string or array). */ markets?: string | string[]; } interface GetEventEvOptions { /** * Optional market filter. Pass a single comma-separated string or an * array of market keys (e.g. `["pitcher_strikeouts", "batter_hits"]`). * Omit to evaluate every market on the event. */ markets?: string | string[]; /** * Optional bookmaker filter (the-odds-api-compatible). Pass a * comma-separated string or an array of book keys (e.g. * `["draftkings", "fanduel"]`) to price only the books you hold * accounts at. Omit for every book. * * This narrows the PRICES, never the fair-line anchor: * `bookmakers: ["draftkings"]` still returns DraftKings EV% measured * against Pinnacle. Lines where none of your books quote a price are * omitted. */ bookmakers?: string | string[]; /** * How the anchor's vig is removed before the fair line is derived. * `"multiplicative"` (the default when omitted) divides each implied * probability by the booksum; `"shin"` solves Shin's insider-trading * model, which loads the overround onto the longshot and corrects the * favourite-longshot bias — negligible on a -110/-110 total, material * on a +600 anytime scorer. The response echoes it as `devig_method`. */ devig?: "multiplicative" | "shin"; } interface GetEventBestLineOptions { /** * Optional market filter. Pass a single comma-separated string or an * array of market keys (e.g. `["pitcher_strikeouts", "h2h"]`). Omit * to include every market on the event. */ markets?: string | string[]; /** * Optional bookmaker filter (the-odds-api-compatible). Pass a * comma-separated string or an array of book keys (e.g. * `["draftkings", "fanduel"]`) to shop only the books you hold * accounts at. Omit for all comparable books. */ bookmakers?: string | string[]; /** * When true, every price row carries a `link` — that book's public * event-page URL, the click-out for "go bet this". Books without a * verified URL template return null. Links appear on free-tier * redacted responses too (navigation isn't the paid data). */ includeLinks?: boolean; } interface CalcEventEvOptions { /** Market key — h2h / spreads / totals / pitcher_strikeouts / etc. */ market: string; /** Outcome name. Team for h2h/spreads; "Over" or "Under" for totals/props. */ name: string; /** American odds at your book, e.g. -118 or 145. */ price: number; /** Line/point for spreads, totals, player props. Sign matters for spreads (-1.5 favorite). Omit for h2h. */ point?: number; /** Player name for player-prop markets. Omit for game-line markets. */ description?: string; } interface ExportResolvedPropsOptions { /** Sport key (e.g. `"baseball_mlb"`). Required. */ sport: string; /** Optional market filter. */ market?: string; /** Optional bookmaker filter. */ bookmaker?: string; /** ISO datetime lower bound on `resolved_at`. */ since?: string; /** ISO datetime upper bound on `resolved_at`. */ until?: string; /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */ outPath?: string; } interface ExportOddsHistoryOptions { /** Sport key (e.g. `"baseball_mlb"`). Required. */ sport: string; /** Optional market filter. */ market?: string; /** Optional bookmaker filter. */ bookmaker?: string; /** ISO datetime lower bound on `recorded_at`. */ since?: string; /** ISO datetime upper bound on `recorded_at`. */ until?: string; /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */ outPath?: string; } /** * Webhook event types. `steam` = cross-book sharp-money alert. * `market_suspended` = a book took a market off the board pregame (one * delivery per (book, event, player) withdrawal, with `books_agreeing`). */ type WebhookEventType = "line_movement" | "resolution" | "steam" | "market_suspended"; interface CreateWebhookOptions { /** HTTPS endpoint to receive POSTed events. Required. */ url: string; /** Event types to subscribe to. Default: all. */ events?: WebhookEventType[]; filterSportKey?: string; filterEventId?: number; filterMarketKey?: string; filterPlayerName?: string; /** * Comma-separated book keys, same vocabulary as the `?bookmakers=` * query param (e.g. "draftkings,fanduel"). Unset = all books; unknown * keys match nothing. Applies to line_movement, resolution and * market_suspended; steam is cross-book and unaffected. */ filterBookmakerKey?: string; /** Minimum % change in American odds to fire a line_movement. Point-only shifts always pass. */ minPriceChangePct?: number; /** Minimum 0-100 steam score to fire a `steam` event. Null = detector's global floor. */ minSteamScore?: number; /** * `market_suspended` only: how many books must have pulled the same * player/market on the same event before you are told. Unset/1 = every * drop (right if you price off one book); 3+ = corroborated late * scratches only. Every payload carries `books_agreeing` regardless. */ minBooksAgreeing?: number; /** * Batched delivery opt-in (1-500): up to N events per POST as a signed * envelope `{"batch": true, "event_type": ..., "count": N, "events": * [{"delivery_id": ..., "data": }, ...]}` with an * `X-PropLine-Batch` header. Strongly recommended for high-volume * subscriptions — one POST per event caps your delivery rate at your * endpoint's response time. 0 reverts to per-event. JSON format only. */ batchMax?: number; } interface UpdateWebhookOptions { url?: string; events?: WebhookEventType[]; filterSportKey?: string; filterEventId?: number; filterMarketKey?: string; filterPlayerName?: string; /** * Comma-separated book keys, same vocabulary as the `?bookmakers=` * query param (e.g. "draftkings,fanduel"). Unset = all books; unknown * keys match nothing. Applies to line_movement, resolution and * market_suspended; steam is cross-book and unaffected. */ filterBookmakerKey?: string; minPriceChangePct?: number; minSteamScore?: number; minBooksAgreeing?: number; /** Batched delivery (see CreateWebhookOptions.batchMax). 0 = per-event. */ batchMax?: number; active?: boolean; } interface ListWebhookDeliveriesOptions { /** Max deliveries to return. Default 50, max 200. */ limit?: number; /** * Page backwards: pass the smallest `id` from the previous page to get * the next-older page. Pages are newest-first; a page shorter than * `limit` is the last one. */ beforeId?: number; } interface ReplayWebhookEventsOptions { /** * Read events after this cursor — the highest `X-PropLine-Sequence` you * have processed. Defaults to 0 (from the oldest retained event). */ sinceSeq?: number; /** Max events per page. Default 100, max 500. */ limit?: number; } interface StreamOptions { /** Subscription to stream. Must be transport="websocket". */ webhookId: number; /** Resume point — the last `seq` you processed. Default 0. */ sinceSeq?: number; /** Auto-reconnect and resume from the last seq. Default true. */ reconnect?: boolean; /** Override the websocket origin (default: derived from baseUrl). */ wsUrl?: string; /** Called on every successful handshake with the `ready` frame. */ onReady?: (ready: ReplayPage) => void; /** * Called when the server reports events after your cursor have aged out of * retention. This is the one case the stream cannot make you whole — * resync from the REST endpoints. */ onTruncated?: (ready: ReplayPage) => void; } interface VerifySignatureOptions { /** Webhook signing secret (returned once from `createWebhook`). */ secret: string; /** Value of the `X-PropLine-Timestamp` header. */ timestamp: string; /** Raw request body. */ body: Uint8Array | Buffer | string; /** Value of the `X-PropLine-Signature` header. */ signature: string; } /** * Live daily-quota state, parsed from the `X-Daily-*` headers the API * returns on every authenticated response. */ interface QuotaStatus { /** Your tier's daily request cap. */ limit: number; /** Requests used today (including the request that produced this). */ used: number; /** Requests left before the cap. */ remaining: number; /** Unix seconds when the quota resets (00:00 UTC — a hard reset, not a rolling window). */ resetEpoch: number; /** Quota reset time as a `Date`. */ resetAt: Date; } /** * Client for the PropLine player props API. * * @example * ```ts * import { PropLine } from "propline"; * * const client = new PropLine("your_api_key"); * const sports = await client.getSports(); * ``` */ declare class PropLine { readonly apiKey: string; readonly baseUrl: string; readonly timeoutMs: number; /** * Daily-quota state from the most recent API response, or `null` before * the first request. Updated on every call (including 429s): * * ```ts * await client.getSports(); * console.log(client.lastQuota?.remaining); // 999 * ``` */ lastQuota: QuotaStatus | null; private readonly _fetch; constructor(apiKey: string, options?: PropLineOptions); private _buildUrl; /** * Record the X-Daily-* quota headers when present (absent on * unauthenticated errors, e.g. an invalid key's 401). */ private _captureQuota; private _request; /** List all available sports. */ getSports(): Promise; /** List upcoming events for a sport. */ getEvents(sport: string): Promise; /** * Get current odds. With `eventId`, returns single-event odds (including * player props). Without, returns bulk odds for all upcoming events. * * Each response carries a `bookmakers` array — iterate it to compare * lines across Bovada, DraftKings, FanDuel, Pinnacle, Unibet, and * PrizePicks (coverage varies by sport). */ getOdds(sport: string, options: GetOddsOptions & { eventId: number | string; }): Promise; getOdds(sport: string, options?: Omit & { eventId?: undefined; }): Promise; /** List the available market types for an event. */ getMarkets(sport: string, eventId: number | string): Promise; /** * Get historical odds movement for an event. * * Hobby+: full snapshots. Free tier: redacted (snapshot counts only). * * Supports period-historical filters: * - `from` / `to` — absolute ISO timestamps * - `relativeFrom` / `relativeTo` — offsets to commence_time ("-3h", "-30m", "0") * - `interval` — downsample to a fixed bucket size * - `changesOnly` — drop unchanged adjacent snapshots */ getOddsHistory(sport: string, eventId: number | string, options?: GetOddsHistoryOptions): Promise; /** * Get the opening AND closing line per `(book, market, outcome)` for an * event. Closing is the last snapshot at or before commence_time * (`price` / `point` / `closing_at`); opening is the first snapshot in * the same 14-day pre-kickoff window (`opening_price` / `opening_point` * / `opening_at`). Canonical CLV helper: replaces "fetch full history → * find the first and last pre-game rows" with one call. * * Compare the *points* as well as the prices — on spreads and totals * the number moves as much as the price, so a price-only comparison * mis-measures those markets. * * Hobby+: full data. Free tier: redacted. */ getOddsClosing(sport: string, eventId: number | string, options?: GetOddsClosingOptions): Promise; /** Get game scores and status (free tier). */ getScores(sport: string, options?: GetScoresOptions): Promise; /** * PrizePicks Power/Flex entry payout schedule (2-6 legs) plus the per-leg * breakeven win probability for each play. Pass `legWinProb` to also get * `expected_return` (per $1) and `is_plus_ev` per play — turning a slip * into the hit rate it actually needs to clear. * * These are PrizePicks's *standard* published payouts; demon/goblin per-pick * modifiers aren't in PrizePicks's feed, so they're not reflected. Breakeven * assumes independent legs. See the `disclaimer` field on the response. */ getDfsPayouts(options?: GetDfsPayoutsOptions): Promise; /** * Synthetic MLB Grand Salami for a given UTC date — total runs scored * across every MLB game on the slate, plus each book's implied Grand * Salami line (median of per-game primary totals across our MLB books). * * No retail sportsbook quotes this as a single market, so historical * cross-book Grand Salami data isn't available elsewhere. Free tier; * defaults to today (UTC). */ getMlbGrandSalami(options?: GetMlbGrandSalamiOptions): Promise; /** * Synthetic NHL Daily Goals Total for a given UTC date — total goals * scored (incl. OT/SO) across every NHL game on the slate, plus each * book's implied Daily Goals Total line (median of per-game primary * totals across our NHL books). * * Hockey's equivalent of the MLB Grand Salami. No retail sportsbook * quotes this as a single market. Free tier; defaults to today (UTC). */ getNhlDailyGoalsTotal(options?: GetNhlDailyGoalsTotalOptions): Promise; /** * Factual volume of graded player props over the last N days (free tier). * * Aggregated counts only — a coverage proof (every outcome counted was * graded against the real box score), never a profitability claim. * * @param days Look-back window, 1-90 (default 30). */ getResolutionSummary(days?: number): Promise; /** * Get raw player/team box-score stats (book-agnostic, free tier). * * Returns actual stat values decoupled from any bookmaker's lines. * * Live during games for major US sports (MLB + WNBA now; NFL, NCAAF, * NBA, NHL at season start): while the event's status is "in_progress", * stats refresh roughly every 90 seconds with cumulative in-game values — * treat them as partial until status flips to "final". Other sports * populate stats at game completion. */ getStats(sport: string, eventId: number | string, options?: GetStatsOptions): Promise; /** * Get game context — the conditions a prop settles under. * * For MLB: probable starting pitchers and their throwing hand * (`home_probable_pitcher_hand` / `away_probable_pitcher_hand`, "L"/"R"/"S" * — platoon-split context for every batter prop), a confirmed-lineup flag, * the home-plate umpire, and first-pitch weather (outdoor / open-roof * venues; indoor venues return `weather: null` with `is_indoor: true`). * For NFL & NCAAF: the venue and kickoff weather (pitcher/umpire/lineup * fields are null for football). The same block is embedded in * {@link getResults}, so every graded prop carries its conditions — unique * to PropLine. Free tier. Rejects with a 404 when no context is on file * for the event yet. */ getContext(sport: string, eventId: number | string): Promise; /** * Get line movement + steam detection from the snapshot tick history. * * Per (book, market, outcome): opening line, latest line, signed * implied-probability shift, point shift, direction. The `steam` array * flags outcomes multiple books moved the same direction — the * sharp-money signal across every book PropLine polls. When a book moves * the line itself, that outcome's `prob_shift` is null and `direction` is * `"line_moved"` (excluded from the steam signal). Unique to PropLine. * Hobby+ full; free tier redacted. */ getMovement(sport: string, eventId: number | string, options?: GetMovementOptions): Promise; /** * Get resolved prop outcomes with actual player stats. * * Pro tier: full data. Free tier: redacted (resolution + actual nulled). */ getResults(sport: string, eventId: number | string, options?: GetResultsOptions): Promise; /** * Recent resolved prop history for a player on a market. * * One entry per (event, bookmaker) pair. Pro: full. Free: redacted. */ getPlayerHistory(sport: string, playerName: string, options: GetPlayerHistoryOptions): Promise; /** * A player's game log — recent games with every raw box-score stat. * * One call replaces one request per event, so L5/L10/L20, season splits, * charts and head-to-head can all be built from the raw rows. Free tier. * * Reads the RAW-STATS archive, not graded-prop history: it covers every * game with a box score on file, including games no sportsbook priced, so * a "last 10 games" window here really is the last 10 games — unlike one * built from {@link getPlayerHistory} or {@link getPlayerTrends}. Carries * no line, price or grade. * * @example * ```ts * const log = await client.getPlayerGames("baseball_mlb", "Aaron Judge", { limit: 10 }); * const hits = log.games.reduce((n, g) => n + (g.stats.hits ?? 0), 0); * * // Last 5 meetings with Boston — not the Boston games among his last 5. * const h2h = await client.getPlayerGames("baseball_mlb", "Aaron Judge", { * limit: 5, * opponent: "BOS", * }); * ``` */ getPlayerGames(sportKey: string, playerName: string, options?: GetPlayerGamesOptions): Promise; /** * Rolling hit-rate trends for a player across one or all markets. * * Returns over/under/push splits over the last 5/10/20/50 graded games, * the current streak, and the most recent game per market. Pro: full. * Free: redacted. */ getPlayerTrends(sportKey: string, playerName: string, options?: GetPlayerTrendsOptions): Promise; /** * Cross-book +EV analysis for a single event (Pro+ tier). * * Groups every outcome by (market, player, line) across the books we * carry, derives a no-vig fair line from a sharp anchor (Pinnacle * preferred, Bovada fallback), and returns EV% per book at the same * line. Outcomes are sorted with +EV plays floated to the top. * * PrizePicks is excluded — its synthetic +100/+100 prices aren't * payout odds. Lines without sharp-anchor coverage are dropped. * * @example * ```ts * const ev = await client.getEventEv("baseball_mlb", 12345); * for (const line of ev.lines) { * const plus = line.outcomes.filter(o => o.is_plus_ev); * if (plus.length) console.log(line.market_key, line.description, plus); * } * ``` */ /** * List futures markets for a sport — championship winner, MVP, * division winner, season win totals, etc. Each row is one (futures * event, book, market) with every team or player priced. Free tier; * aggregated across each book's futures feed (Bovada, FanDuel, * DraftKings, and Pinnacle). * * @example * ```ts * const futures = await client.getFutures("baseball_mlb"); * for (const event of futures) { * console.log(`${event.title} @ ${event.commence_time}`); * for (const m of event.markets) { * const top3 = [...m.outcomes].sort((a, b) => a.price - b.price).slice(0, 3); * for (const o of top3) console.log(` ${o.name}: ${o.price}`); * } * } * ``` * * @param options.bookmakers Optional book key(s) to restrict the per-book * market rows (the-odds-api-compatible; omitted = all books, unknown keys * match nothing). A futures event left with no matching market is dropped. */ getFutures(sport: string, options?: { bookmakers?: string | string[]; }): Promise; /** * Market-implied consensus projections for a single event. * * One row per (market, player): the statistical value the betting * market collectively implies — the line where the no-vig P(over) * crosses 50%, median across contributing sportsbooks. Built for * validating your own statistical/fantasy projections against the * live market. Market-implied arithmetic over sportsbook prices, * never a forecast. DFS pick'em pricing is excluded. * * Paid tier required (Hobby+); free tier receives the structure with * projected values nulled and `redacted: true`. * * @example * ```ts * const proj = await client.getEventProjections("football_nfl", 25070); * for (const row of proj.projections) { * console.log(row.player, row.market_key, row.projected_value); * } * ``` */ getEventProjections(sport: string, eventId: number | string, options?: GetEventProjectionsOptions): Promise; getEventEv(sport: string, eventId: number | string, options?: GetEventEvOptions): Promise; /** * Cross-book best-line lookup for a single event. * * For each (market, player, line) tuple, returns the single best * American price across every book we carry, with the book name * attached. Companion to `getEventEv`: best-line tells you which * book has the highest payout right now; +EV tells you whether * that price beats a sharp no-vig fair line. Most line shoppers * want both. * * PrizePicks is excluded from the comparison — its DFS payout * structure (synthetic +100/+100 quotes) isn't directly comparable * to traditional sportsbook odds. * * Hobby tier or higher sees prices. Free tier gets a redacted * teaser: the full structure — every line, side, book identity, and * the best-first ranking — with every price null, plus * `redacted: true` and an `upgrade_url`. * * @example * ```ts * const bl = await client.getEventBestLine("baseball_mlb", 12345); * for (const line of bl.lines) { * for (const [side, info] of Object.entries(line.sides)) { * console.log( * `${line.description} ${side} ${line.point}: ` + * `${info.best.price} @ ${info.best.book_title}` * ); * } * } * ``` */ getEventBestLine(sport: string, eventId: number | string, options?: GetEventBestLineOptions): Promise; /** * Calculate EV% for a user-supplied price against the event's * no-vig fair anchor. Useful for books PropLine doesn't carry — * Caesars, BetMGM, Fanatics, BetUS, Hard Rock — where you have a * price in hand and want to know if it's +EV against the sharp * consensus we do carry. * * Same fair-line math as `getEventEv` (Pinnacle-preferred anchor, * no-vig devigging) but takes one user price as input. Pro tier. * * @example * ```ts * const r = await client.calcEventEv("baseball_mlb", 12614, { * market: "h2h", * name: "Pittsburgh Pirates", * price: -118, * }); * console.log(`EV ${r.ev_pct}% fair=${r.fair_prob}`); * ``` */ calcEventEv(sport: string, eventId: number | string, options: CalcEventEvOptions): Promise; /** * Bulk CSV export of resolved prop outcomes (Pro+ tier). * * If `outPath` is provided, streams the CSV to disk and resolves to the * path. Otherwise resolves to the full CSV bytes as a `Uint8Array`. * * @example * ```ts * await client.exportResolvedProps({ * sport: "baseball_mlb", * market: "pitcher_strikeouts", * since: "2026-04-01T00:00:00Z", * outPath: "./mlb-strikeouts.csv", * }); * ``` */ exportResolvedProps(options: ExportResolvedPropsOptions & { outPath: string; }): Promise; exportResolvedProps(options: ExportResolvedPropsOptions & { outPath?: undefined; }): Promise; /** * Bulk CSV export of the full line-movement time-series. * * One row per (outcome, snapshot): every recorded odds snapshot (price + * line, per book, including period markets), not just the closing line. * This is the raw tick history no subscription tier can pull in bulk — * Pro/Streaming get per-event {@link getOddsHistory} only; this bulk * firehose is exclusive to the one-time Historical Backfill pass and * Enterprise. * * A full archive runs to gigabytes per sport — page month by month with * `since`/`until`. If `outPath` is provided, streams to disk and resolves * to the path; otherwise resolves to the CSV bytes as a `Uint8Array`. * * @example * ```ts * await client.exportOddsHistory({ * sport: "baseball_mlb", * since: "2026-04-01T00:00:00Z", * until: "2026-05-01T00:00:00Z", * outPath: "./mlb-line-history-apr.csv", * }); * ``` */ exportOddsHistory(options: ExportOddsHistoryOptions & { outPath: string; }): Promise; exportOddsHistory(options: ExportOddsHistoryOptions & { outPath?: undefined; }): Promise; /** * Register a webhook subscription. Streaming tier only. * * The returned object includes the full signing `secret` — this is the * ONLY time it's revealed. Store it securely. */ createWebhook(options: CreateWebhookOptions): Promise; /** List your webhook subscriptions. Secrets are masked. */ listWebhooks(): Promise; /** Get a single webhook subscription. Secret is masked. */ getWebhook(webhookId: number): Promise; /** Update fields on a webhook. Only supplied fields are changed. */ updateWebhook(webhookId: number, options: UpdateWebhookOptions): Promise; /** Delete a webhook (cascades its delivery history). */ deleteWebhook(webhookId: number): Promise<{ ok: boolean; } | unknown>; /** Queue a sample `test` payload to the webhook's URL. */ testWebhook(webhookId: number): Promise; /** Last 50 (default) delivery attempts for a webhook. */ listWebhookDeliveries(webhookId: number, options?: ListWebhookDeliveriesOptions): Promise; /** * Re-read this subscription's events in order, from a cursor. * * Answers "my endpoint was down — what did I miss?". Every delivery carries * an `X-PropLine-Sequence` header: a counter monotonic *within your * subscription*. Store the highest one you processed and pass it as * `sinceSeq`. * * Do NOT use `X-PropLine-Delivery` as the cursor — that id is global across * every subscription, so its gaps are other customers' traffic. * * Events come back oldest-first (the opposite of `listWebhookDeliveries`, * which is a newest-first debugging log). Page by passing `next_seq` back * as `sinceSeq` while `has_more` is true. * * **Check `truncated`.** True means events after your cursor have aged out * of retention (2 days, max 5,000 deliveries per subscription) and are gone * — resync from the REST endpoints instead of assuming you are current. * * Does not count against your daily request quota. */ replayWebhookEvents(webhookId: number, options?: ReplayWebhookEventsOptions): Promise; /** * Stream a websocket subscription as an async iterable. * * ```ts * for await (const ev of client.stream({ webhookId: 12, sinceSeq: 4180 })) { * console.log(ev.seq, ev.event_type, ev.data); * } * ``` * * The subscription must have been created with `transport: "websocket"`. * Same events, same filters, same `seq` as an HTTP webhook — one * subscription, different transport. * * **Reconnects automatically and resumes from the last `seq` it saw**, which * is the whole point of the sequence: a dropped connection does not become a * gap in your data. Set `reconnect: false` to get a single connection that * ends when the socket closes. * * If the server reports `truncated` — events after your cursor aged out of * retention and are gone — `onTruncated` fires. Handle it: that is the one * case where the stream cannot make you whole and you should resync from the * REST endpoints. */ stream(options: StreamOptions): AsyncGenerator; /** * Grade placed bets against their closing lines (CLV). * * Closing line value is the only durable proxy for whether a bettor has * edge: did the price you took beat the number the market settled on? * Send the bets you actually placed; each comes back with its closing * price, the de-vigged closing fair probability, CLV, and — once the * game settles — the graded result and actual stat value. * * Stateless: nothing is stored server-side. * * **Two CLV numbers are returned deliberately.** `clv_pct` is * price-vs-price — familiar and quotable, but vig-blind, so it flatters * a bet taken on the juicy side of a wide market. `ev_vs_close_pct` * scores your price against the DE-VIGGED close and is the honest one; * on a real bet the two came out +6.52% and +0.08%. * * The de-vig anchors to the **sharpest book quoting that line at close** * (`fair_source`), not the book you bet at — de-vigging your own book * always returns a negative number, because you paid its hold. * * Bets whose event has not started carry `closing_is_final: false`, land * in `summary.pending`, and are excluded from the summary averages: * before kickoff the "closing" price is just the latest price. * * Matching is fail-closed — a bet that cannot be pinned to exactly one * stored outcome returns `matched: false` with an `unmatched_reason` * rather than a confident wrong match. Max 500 bets per request. * * Hobby+ required; free tier receives the structure with numbers nulled. * * @example * const res = await client.gradeClv([{ * ref: "b1", * sport_key: "baseball_mlb", * event_id: 150791, * market: "batter_hits_runs_rbis", * bookmaker: "lowvig", * selection: "Drake Baldwin", * side: "Under", * point: 0.5, * price: 145, * stake: 1, * }]); * console.log(res.summary.avg_ev_vs_close_pct); */ gradeClv(bets: ClvBetInput[], options?: { devig?: "multiplicative" | "shin"; }): Promise; /** * Price a same-game parlay at the book's own correlated odds. * * Send two to ten legs from ONE event and get back the book's own price * for that exact slip — what a FanDuel customer would be offered for it * at that moment, not a model of it — beside `independent_price` (the * product of the live single-leg prices) and `correlation_factor` * (their ratio: the correlation the book is charging, below 1, or * paying, above 1, for). Measured live: Cardinals ML +205 × Freddie * Freeman to record a hit -260 → SGP +592 against an independent +322. * * Book-native. `bookmaker` is "fanduel" (its own betslip pricer) or * "betonlineag" / "lowvig" (the Sportcast engine both Chico brands embed, * same builder price); an unsupported value is a 422. * * Legs are named exactly as `/odds` names an outcome (market, name, * description, point, period), or by `book_outcome_id` from * `includeBookIds: true`. Matching is fail-closed — a leg that does not * pin to exactly one stored outcome is a 422 `leg_unmatched` naming the * leg. `quoted: false` means the book will not offer that combination as * a same-game parlay; refused legs carry the book's own `failure_code`. * Quotes for an identical slip are shared for 15 seconds. * * Hobby+ required; free tier receives the matched legs with every price * nulled and never triggers a book call. * * @example * const q = await client.priceSgp("baseball_mlb", 150791, [ * { market: "h2h", name: "St. Louis Cardinals" }, * { market: "batter_1plus_hits", name: "Freddie Freeman", description: "Freddie Freeman" }, * ]); * console.log(q.sgp_price, q.independent_price, q.correlation_factor); * * // Every book on the same legs, side by side — best_bookmaker is the * // one charging the smallest correlation reduction: * const all = await client.priceSgp("baseball_mlb", 150791, legs, "all"); * console.log(all.best_bookmaker, all.quotes.map((x) => [x.bookmaker, x.correlation_factor])); */ priceSgp(sportKey: string, eventId: number | string, legs: SgpLegInput[], bookmaker: "all"): Promise; priceSgp(sportKey: string, eventId: number | string, legs: SgpLegInput[], bookmaker?: string): Promise; /** * Verify that an inbound webhook delivery was signed by PropLine. * * Compares HMAC-SHA256(secret, `${timestamp}.` + body) against the * `X-PropLine-Signature` header in constant time. * * @example * ```ts * import { PropLine } from "propline"; * * app.post("/hooks/propline", express.raw({ type: "*\/*" }), (req, res) => { * const ok = PropLine.verifySignature({ * secret: process.env.WEBHOOK_SECRET!, * timestamp: req.header("X-PropLine-Timestamp")!, * body: req.body, // raw Buffer * signature: req.header("X-PropLine-Signature")!, * }); * if (!ok) return res.status(401).end(); * // ... * }); * ``` */ static verifySignature(options: VerifySignatureOptions): boolean; } /** * PropLine — Node/TypeScript SDK for the PropLine player props API. * * @example * ```ts * import { PropLine } from "propline"; * * const client = new PropLine("your_api_key"); * const events = await client.getEvents("basketball_nba"); * const odds = await client.getOdds("basketball_nba", { * eventId: events[0].id, * markets: ["player_points", "player_rebounds"], * }); * ``` */ /** String constants for bookmaker keys in odds responses. */ declare const Bookmakers: { readonly BOVADA: "bovada"; readonly DRAFTKINGS: "draftkings"; readonly FANDUEL: "fanduel"; readonly PINNACLE: "pinnacle"; readonly UNIBET: "unibet"; readonly UNDERDOG: "underdog"; readonly KALSHI: "kalshi"; readonly POLYMARKET: "polymarket"; readonly PRIZEPICKS: "prizepicks"; }; type BookmakerKey = (typeof Bookmakers)[keyof typeof Bookmakers]; declare const VERSION = "0.52.0"; export { AuthError, type BestLine, type BestLineSide, type BestPrice, type Bookmaker, type BookmakerKey, Bookmakers, type CalcEventEvOptions, type ClosingBookmaker, type ClosingMarket, type ClosingOutcome, type ClvBetInput, type ClvGradeResponse, type ClvGradedBet, type ClvSummary, type ContextResponse, type CreateWebhookOptions, type DfsPayoutTier, type DfsPayoutsResponse, type DfsPlayPayout, type EvLine, type EvOutcome, type Event, type EventBestLineResponse, type EventEvCalcResponse, type EventEvResponse, type EventProjectionsResponse, type ExportOddsHistoryOptions, type ExportResolvedPropsOptions, type FuturesEvent, type FuturesMarket, type FuturesOutcome, type GetDfsPayoutsOptions, type GetEventBestLineOptions, type GetEventEvOptions, type GetEventProjectionsOptions, type GetMlbGrandSalamiOptions, type GetNhlDailyGoalsTotalOptions, type GetOddsClosingOptions, type GetOddsHistoryOptions, type GetOddsOptions, type GetPlayerGamesOptions, type GetPlayerHistoryOptions, type GetPlayerTrendsOptions, type GetResultsOptions, type GetScoresOptions, type GetStatsOptions, type HitRateSplit, type ListWebhookDeliveriesOptions, type Market, type MarketSummary, type MlbGrandSalamiBook, type MlbGrandSalamiResponse, type MovementBookmaker, type MovementMarket, type MovementOutcome, type MovementResponse, type NhlDailyGoalsTotalBook, type NhlDailyGoalsTotalResponse, type OddsClosingResponse, type OddsHistoryBookmaker, type OddsHistoryMarket, type OddsHistoryOutcome, type OddsHistoryResponse, type OddsResponse, type Outcome, type OutcomeSnapshot, type PeriodFilter, type PlayerGame, type PlayerGameLog, type PlayerHistoryEntry, type PlayerHistoryResponse, type PlayerMarketTrend, type PlayerStat, type PlayerTrends, type ProjectionRow, PropLine, PropLineError, type PropLineErrorInfo, type PropLineOptions, type QuotaStatus, RateLimitError, type ReplayEvent, type ReplayPage, type ReplayWebhookEventsOptions, type ResolutionSummary, type ResolutionSummaryMarket, type ResolutionSummarySport, type ResolvedOutcome, type ResultsMarket, type ResultsResponse, type ScoreEvent, type SgpBookError, type SgpLegInput, type SgpLegQuote, type SgpMultiQuoteResponse, type SgpQuoteResponse, type Sport, type StatsResponse, type SteamMove, type StreamOptions, type TrendLastGame, type TrendStreak, type UpdateWebhookOptions, VERSION, type VerifySignatureOptions, type WeatherInfo, type Webhook, type WebhookDelivery, type WebhookEventType };