propline 0.48.2 → 0.48.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -1132,7 +1132,7 @@ var Bookmakers = {
1132
1132
  POLYMARKET: "polymarket",
1133
1133
  PRIZEPICKS: "prizepicks"
1134
1134
  };
1135
- var VERSION = "0.48.2";
1135
+ var VERSION = "0.48.3";
1136
1136
  // Annotate the CommonJS export names for ESM import in node:
1137
1137
  0 && (module.exports = {
1138
1138
  AuthError,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["/**\n * PropLine — Node/TypeScript SDK for the PropLine player props API.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * const client = new PropLine(\"your_api_key\");\n * const events = await client.getEvents(\"basketball_nba\");\n * const odds = await client.getOdds(\"basketball_nba\", {\n * eventId: events[0].id,\n * markets: [\"player_points\", \"player_rebounds\"],\n * });\n * ```\n */\n\nexport { PropLine } from \"./client.js\";\nexport {\n PropLineError,\n AuthError,\n RateLimitError,\n} from \"./client.js\";\nexport type {\n PropLineErrorInfo,\n PropLineOptions,\n QuotaStatus,\n GetOddsOptions,\n GetOddsHistoryOptions,\n GetOddsClosingOptions,\n PeriodFilter,\n GetScoresOptions,\n GetDfsPayoutsOptions,\n GetMlbGrandSalamiOptions,\n GetNhlDailyGoalsTotalOptions,\n GetStatsOptions,\n GetResultsOptions,\n GetPlayerHistoryOptions,\n GetPlayerGamesOptions,\n GetPlayerTrendsOptions,\n GetEventEvOptions,\n GetEventProjectionsOptions,\n GetEventBestLineOptions,\n CalcEventEvOptions,\n ExportResolvedPropsOptions,\n ExportOddsHistoryOptions,\n WebhookEventType,\n CreateWebhookOptions,\n UpdateWebhookOptions,\n ListWebhookDeliveriesOptions,\n ReplayWebhookEventsOptions,\n StreamOptions,\n VerifySignatureOptions,\n} from \"./client.js\";\n\nexport type {\n ClvBetInput,\n ClvGradedBet,\n ClvSummary,\n ClvGradeResponse,\n SgpLegInput,\n SgpLegQuote,\n SgpQuoteResponse,\n Sport,\n Event,\n Outcome,\n ResolvedOutcome,\n Market,\n Bookmaker,\n OddsResponse,\n MarketSummary,\n OutcomeSnapshot,\n OddsHistoryOutcome,\n OddsHistoryMarket,\n OddsHistoryBookmaker,\n OddsHistoryResponse,\n ClosingOutcome,\n ClosingMarket,\n ClosingBookmaker,\n OddsClosingResponse,\n ScoreEvent,\n MlbGrandSalamiBook,\n MlbGrandSalamiResponse,\n NhlDailyGoalsTotalBook,\n NhlDailyGoalsTotalResponse,\n ResolutionSummary,\n ResolutionSummarySport,\n ResolutionSummaryMarket,\n PlayerStat,\n StatsResponse,\n WeatherInfo,\n ContextResponse,\n MovementOutcome,\n MovementMarket,\n MovementBookmaker,\n SteamMove,\n MovementResponse,\n ResultsMarket,\n ResultsResponse,\n PlayerHistoryEntry,\n PlayerHistoryResponse,\n HitRateSplit,\n TrendStreak,\n TrendLastGame,\n PlayerMarketTrend,\n PlayerGame,\n PlayerGameLog,\n PlayerTrends,\n EvOutcome,\n EvLine,\n EventEvResponse,\n EventProjectionsResponse,\n ProjectionRow,\n EventEvCalcResponse,\n BestPrice,\n BestLineSide,\n BestLine,\n EventBestLineResponse,\n FuturesOutcome,\n FuturesMarket,\n FuturesEvent,\n Webhook,\n WebhookDelivery,\n ReplayEvent,\n ReplayPage,\n DfsPayoutTier,\n DfsPlayPayout,\n DfsPayoutsResponse,\n} from \"./types.js\";\n\n/** String constants for bookmaker keys in odds responses. */\nexport const Bookmakers = {\n BOVADA: \"bovada\",\n DRAFTKINGS: \"draftkings\",\n FANDUEL: \"fanduel\",\n PINNACLE: \"pinnacle\",\n UNIBET: \"unibet\",\n UNDERDOG: \"underdog\",\n KALSHI: \"kalshi\",\n POLYMARKET: \"polymarket\",\n PRIZEPICKS: \"prizepicks\",\n} as const;\n\nexport type BookmakerKey = (typeof Bookmakers)[keyof typeof Bookmakers];\n\nexport const VERSION = \"0.48.2\";\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport type {\n ClvBetInput,\n ClvGradeResponse,\n SgpLegInput,\n SgpQuoteResponse,\n Sport,\n Event as PropLineEvent,\n OddsResponse,\n MarketSummary,\n OddsHistoryResponse,\n OddsClosingResponse,\n ScoreEvent,\n MlbGrandSalamiResponse,\n NhlDailyGoalsTotalResponse,\n ResolutionSummary,\n StatsResponse,\n ContextResponse,\n MovementResponse,\n ResultsResponse,\n PlayerGameLog,\n PlayerHistoryResponse,\n PlayerTrends,\n EventEvResponse,\n EventProjectionsResponse,\n EventEvCalcResponse,\n EventBestLineResponse,\n FuturesEvent,\n Webhook,\n WebhookDelivery,\n ReplayEvent,\n ReplayPage,\n DfsPayoutsResponse,\n} from \"./types.js\";\n\n/** Options for {@link PropLineClient.getDfsPayouts}. */\nexport interface GetDfsPayoutsOptions {\n /** DFS platform. Only \"prizepicks\" today. */\n platform?: string;\n /**\n * Assumed per-leg win probability in [0, 1]. When supplied, each play\n * also carries `expected_return` (per $1) and `is_plus_ev` at that rate.\n */\n legWinProb?: number;\n}\n\n/**\n * Structured error body returned by gated/throttled endpoints\n * (see https://prop-line.com/docs#errors). Branch on `error` — the\n * codes are stable — and follow the URLs instead of parsing prose.\n */\nexport interface PropLineErrorInfo {\n /** Stable machine-readable code, e.g. \"upgrade_required\", \"daily_limit_exceeded\". */\n error?: string;\n /** Human-readable sentence. */\n message?: string;\n /** Cheapest tier that unlocks a gated feature (403s). */\n required_tier?: string;\n /** Where to unlock it — pre-filled one-click URL on daily-cap 429s. */\n upgrade_url?: string;\n docs_url?: string;\n signup_url?: string;\n backfill_url?: string;\n /** Burst-limit backoff hint (429s). */\n retry_after_seconds?: number;\n /** Daily-cap 429s: recommended next plan incl. its own upgrade_url. */\n recommended?: { plan?: string; upgrade_url?: string; [key: string]: unknown };\n [key: string]: unknown;\n}\n\n/** Base error for all PropLine API failures. */\nexport class PropLineError extends Error {\n readonly statusCode: number;\n /** Human-readable detail message. */\n readonly detail: string;\n /** Structured error body, when the API returned one. */\n readonly info?: PropLineErrorInfo;\n\n constructor(statusCode: number, detail: string, info?: PropLineErrorInfo) {\n super(`[${statusCode}] ${detail}`);\n this.name = \"PropLineError\";\n this.statusCode = statusCode;\n this.detail = detail;\n this.info = info;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Stable machine-readable code (e.g. \"upgrade_required\"), if present. */\n get errorCode(): string | undefined {\n return this.info?.error;\n }\n\n /** The URL that unlocks a gated feature or lifts a cap, if present. */\n get upgradeUrl(): string | undefined {\n return this.info?.upgrade_url ?? this.info?.recommended?.upgrade_url;\n }\n}\n\n/** Thrown when the API key is missing or invalid (HTTP 401). */\nexport class AuthError extends PropLineError {\n constructor(detail = \"Invalid API key\", info?: PropLineErrorInfo) {\n super(401, detail, info);\n this.name = \"AuthError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when the daily request limit is exceeded (HTTP 429). */\nexport class RateLimitError extends PropLineError {\n constructor(detail = \"Rate limit exceeded\", info?: PropLineErrorInfo) {\n super(429, detail, info);\n this.name = \"RateLimitError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport interface PropLineOptions {\n /** API base URL. Default: `https://api.prop-line.com/v1`. */\n baseUrl?: string;\n /** Request timeout in milliseconds. Default: 15000. */\n timeoutMs?: number;\n /** Custom fetch implementation. Defaults to global `fetch` (Node 18+). */\n fetch?: typeof fetch;\n}\n\n/**\n * Game-period filter. String of canonical codes, optionally\n * comma-separated, or the sentinel `\"all\"`. Omitted = full-game\n * markets only (backwards-compatible default).\n *\n * \"q1\" — 1st quarter\n * \"q1,q2\" — 1st and 2nd quarters\n * [\"q1\",\"q2\"] — same, as an array\n * \"h1\" — 1st half\n * \"p1\"|\"p2\"|\"p3\" — hockey periods\n * \"i6\" — 6th inning\n * \"f3\"|\"f5\"|\"f7\" — first N innings\n * \"all\" — every period including full game\n */\nexport type PeriodFilter = string | string[];\n\nexport interface GetOddsOptions {\n /** Specific event ID to get odds (with player props) for. Omit for bulk odds. */\n eventId?: number | string;\n /**\n * Market keys to filter by. If omitted, the bulk `/odds` endpoint\n * defaults to `h2h` and the per-event `/odds` endpoint defaults to\n * `h2h,spreads,totals` — game-line markets every book carries across\n * every sport. Pass an explicit list to fetch player props (e.g.\n * `[\"pitcher_strikeouts\", \"batter_home_runs\"]` for MLB,\n * `[\"player_points\", \"player_rebounds\"]` for NBA).\n */\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /**\n * Bookmaker key(s) to restrict the response to (e.g. `\"draftkings\"` or\n * `[\"draftkings\", \"fanduel\"]`). Omitted = all books. Same parameter name\n * as the-odds-api.\n */\n bookmakers?: string | string[];\n /**\n * When true, each bookmaker block carries a `link` — that book's\n * public event-page URL (plain navigation, no affiliate tagging),\n * so your UI can click out from a line to the book. Links ship for\n * Bovada, DraftKings, FanDuel, BetMGM, Kalshi, Polymarket and\n * Smarkets; other books return null. Maps to the\n * the-odds-api-compatible `includeLinks=true` query param.\n */\n includeLinks?: boolean;\n /**\n * When true, each bookmaker block carries a `book_event_id` and each\n * outcome a `book_outcome_id` — that book's OWN identifiers for the\n * event and the priced selection. Use these to join PropLine rows onto\n * a book's native feed by id instead of matching on team names,\n * players and lines. Kalshi ships both (the event ticker and the\n * per-contract market ticker, e.g. `KXMLBGAME-26AUG08NYYBOS-NYY`);\n * most other books ship an event id. Books without a stable id return\n * null.\n *\n * NB a two-sided market can share ONE `book_outcome_id` across both\n * legs — a Kalshi contract is binary, so Over and Under are its YES\n * and NO sides. The id identifies the contract; the outcome's `name`\n * says which side.\n *\n * PropLine-specific (`includeBookIds=true`); the-odds-api has no\n * equivalent.\n */\n includeBookIds?: boolean;\n}\n\nexport interface GetOddsHistoryOptions {\n markets?: string[];\n /** ISO timestamp; only include snapshots at or after this time. Mutually exclusive with `relativeFrom`. */\n from?: string;\n /** ISO timestamp; only include snapshots at or before this time. Mutually exclusive with `relativeTo`. */\n to?: string;\n /** Offset relative to commence_time, e.g. \"-3h\", \"-30m\", \"-90s\". Mutually exclusive with `from`. */\n relativeFrom?: string;\n /** Offset relative to commence_time, e.g. \"-1m\" or \"0\" for commence_time itself. Mutually exclusive with `to`. */\n relativeTo?: string;\n /** Downsample to one snapshot per bucket. Latest snapshot in each bucket wins. */\n interval?: \"30s\" | \"1m\" | \"5m\" | \"15m\" | \"30m\" | \"1h\";\n /** When true, drop snapshots whose (price, point) match the previous one. Opening line is always kept. */\n changesOnly?: boolean;\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nexport interface GetOddsClosingOptions {\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nexport interface GetMovementOptions {\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nfunction _periodParam(p: PeriodFilter | undefined): string | undefined {\n if (p === undefined) return undefined;\n return typeof p === \"string\" ? p : p.join(\",\");\n}\n\nfunction _bookmakersParam(b: string | string[] | undefined): string | undefined {\n if (b === undefined || b.length === 0) return undefined;\n return typeof b === \"string\" ? b : b.join(\",\");\n}\n\nexport interface GetScoresOptions {\n /** Days back to include (default 3). */\n daysFrom?: number;\n}\n\nexport interface GetMlbGrandSalamiOptions {\n /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */\n date?: string;\n}\n\nexport interface GetNhlDailyGoalsTotalOptions {\n /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */\n date?: string;\n}\n\nexport interface GetStatsOptions {\n /** Stat types to filter by (e.g. `[\"strikeouts\", \"hits\"]`). */\n statType?: string[];\n}\n\nexport interface GetResultsOptions {\n markets?: string[];\n}\n\nexport interface GetPlayerHistoryOptions {\n /** Market key (e.g. `\"pitcher_strikeouts\"`). Required. */\n market: string;\n /** Restrict to a single bookmaker (e.g. `\"draftkings\"`). */\n bookmaker?: string;\n /** Max entries (1-100). Default 20. */\n limit?: number;\n}\n\nexport interface GetPlayerGamesOptions {\n /** Games to return, 1-100. Default 20. */\n limit?: number;\n /**\n * Head-to-head filter. Accepts a full name, nickname or abbreviation\n * (\"Boston Red Sox\", \"Red Sox\", \"BOS\"). The limit applies AFTER this\n * filter, so `{ opponent: \"BOS\", limit: 10 }` is the last 10 MEETINGS,\n * not the Boston games among the last 10 games. Not capped to the\n * current season.\n */\n opponent?: string;\n /**\n * Stat name(s) to return; omit for all. Vocabulary is per-sport —\n * see https://prop-line.com/docs#stats\n */\n statType?: string | string[];\n}\n\nexport interface GetPlayerTrendsOptions {\n /** Market key (e.g. `\"batter_total_bases\"`). Omit for all markets. */\n market?: string;\n /**\n * PrizePicks pick-em flavor to compute trends against: `\"standard\"`\n * (default market line), `\"goblin\"`, or `\"demon\"`. When set, the trend is\n * computed against that flavor's PrizePicks line only. Omit for the default\n * cross-book behavior. Flavor tagging began 2026-06-16.\n */\n dfsOddsType?: \"standard\" | \"goblin\" | \"demon\";\n}\n\nexport interface GetEventProjectionsOptions {\n /** Optional market-key filter (comma-separated string or array). */\n markets?: string | string[];\n}\n\nexport interface GetEventEvOptions {\n /**\n * Optional market filter. Pass a single comma-separated string or an\n * array of market keys (e.g. `[\"pitcher_strikeouts\", \"batter_hits\"]`).\n * Omit to evaluate every market on the event.\n */\n markets?: string | string[];\n /**\n * Optional bookmaker filter (the-odds-api-compatible). Pass a\n * comma-separated string or an array of book keys (e.g.\n * `[\"draftkings\", \"fanduel\"]`) to price only the books you hold\n * accounts at. Omit for every book.\n *\n * This narrows the PRICES, never the fair-line anchor:\n * `bookmakers: [\"draftkings\"]` still returns DraftKings EV% measured\n * against Pinnacle. Lines where none of your books quote a price are\n * omitted.\n */\n bookmakers?: string | string[];\n}\n\nexport interface GetEventBestLineOptions {\n /**\n * Optional market filter. Pass a single comma-separated string or an\n * array of market keys (e.g. `[\"pitcher_strikeouts\", \"h2h\"]`). Omit\n * to include every market on the event.\n */\n markets?: string | string[];\n /**\n * Optional bookmaker filter (the-odds-api-compatible). Pass a\n * comma-separated string or an array of book keys (e.g.\n * `[\"draftkings\", \"fanduel\"]`) to shop only the books you hold\n * accounts at. Omit for all comparable books.\n */\n bookmakers?: string | string[];\n /**\n * When true, every price row carries a `link` — that book's public\n * event-page URL, the click-out for \"go bet this\". Books without a\n * verified URL template return null. Links appear on free-tier\n * redacted responses too (navigation isn't the paid data).\n */\n includeLinks?: boolean;\n}\n\nexport interface CalcEventEvOptions {\n /** Market key — h2h / spreads / totals / pitcher_strikeouts / etc. */\n market: string;\n /** Outcome name. Team for h2h/spreads; \"Over\" or \"Under\" for totals/props. */\n name: string;\n /** American odds at your book, e.g. -118 or 145. */\n price: number;\n /** Line/point for spreads, totals, player props. Sign matters for spreads (-1.5 favorite). Omit for h2h. */\n point?: number;\n /** Player name for player-prop markets. Omit for game-line markets. */\n description?: string;\n}\n\nexport interface ExportResolvedPropsOptions {\n /** Sport key (e.g. `\"baseball_mlb\"`). Required. */\n sport: string;\n /** Optional market filter. */\n market?: string;\n /** Optional bookmaker filter. */\n bookmaker?: string;\n /** ISO datetime lower bound on `resolved_at`. */\n since?: string;\n /** ISO datetime upper bound on `resolved_at`. */\n until?: string;\n /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */\n outPath?: string;\n}\n\nexport interface ExportOddsHistoryOptions {\n /** Sport key (e.g. `\"baseball_mlb\"`). Required. */\n sport: string;\n /** Optional market filter. */\n market?: string;\n /** Optional bookmaker filter. */\n bookmaker?: string;\n /** ISO datetime lower bound on `recorded_at`. */\n since?: string;\n /** ISO datetime upper bound on `recorded_at`. */\n until?: string;\n /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */\n outPath?: string;\n}\n\n/**\n * Webhook event types. `steam` = cross-book sharp-money alert.\n * `market_suspended` = a book took a market off the board pregame (one\n * delivery per (book, event, player) withdrawal, with `books_agreeing`).\n */\nexport type WebhookEventType = \"line_movement\" | \"resolution\" | \"steam\" | \"market_suspended\";\n\nexport interface CreateWebhookOptions {\n /** HTTPS endpoint to receive POSTed events. Required. */\n url: string;\n /** Event types to subscribe to. Default: all. */\n events?: WebhookEventType[];\n filterSportKey?: string;\n filterEventId?: number;\n filterMarketKey?: string;\n filterPlayerName?: string;\n /**\n * Comma-separated book keys, same vocabulary as the `?bookmakers=`\n * query param (e.g. \"draftkings,fanduel\"). Unset = all books; unknown\n * keys match nothing. Applies to line_movement, resolution and\n * market_suspended; steam is cross-book and unaffected.\n */\n filterBookmakerKey?: string;\n /** Minimum % change in American odds to fire a line_movement. Point-only shifts always pass. */\n minPriceChangePct?: number;\n /** Minimum 0-100 steam score to fire a `steam` event. Null = detector's global floor. */\n minSteamScore?: number;\n /**\n * `market_suspended` only: how many books must have pulled the same\n * player/market on the same event before you are told. Unset/1 = every\n * drop (right if you price off one book); 3+ = corroborated late\n * scratches only. Every payload carries `books_agreeing` regardless.\n */\n minBooksAgreeing?: number;\n /**\n * Batched delivery opt-in (1-500): up to N events per POST as a signed\n * envelope `{\"batch\": true, \"event_type\": ..., \"count\": N, \"events\":\n * [{\"delivery_id\": ..., \"data\": <per-event payload>}, ...]}` with an\n * `X-PropLine-Batch` header. Strongly recommended for high-volume\n * subscriptions — one POST per event caps your delivery rate at your\n * endpoint's response time. 0 reverts to per-event. JSON format only.\n */\n batchMax?: number;\n}\n\nexport interface UpdateWebhookOptions {\n url?: string;\n events?: WebhookEventType[];\n filterSportKey?: string;\n filterEventId?: number;\n filterMarketKey?: string;\n filterPlayerName?: string;\n /**\n * Comma-separated book keys, same vocabulary as the `?bookmakers=`\n * query param (e.g. \"draftkings,fanduel\"). Unset = all books; unknown\n * keys match nothing. Applies to line_movement, resolution and\n * market_suspended; steam is cross-book and unaffected.\n */\n filterBookmakerKey?: string;\n minPriceChangePct?: number;\n minSteamScore?: number;\n minBooksAgreeing?: number;\n /** Batched delivery (see CreateWebhookOptions.batchMax). 0 = per-event. */\n batchMax?: number;\n active?: boolean;\n}\n\nexport interface ListWebhookDeliveriesOptions {\n /** Max deliveries to return. Default 50, max 200. */\n limit?: number;\n /**\n * Page backwards: pass the smallest `id` from the previous page to get\n * the next-older page. Pages are newest-first; a page shorter than\n * `limit` is the last one.\n */\n beforeId?: number;\n}\n\nexport interface ReplayWebhookEventsOptions {\n /**\n * Read events after this cursor — the highest `X-PropLine-Sequence` you\n * have processed. Defaults to 0 (from the oldest retained event).\n */\n sinceSeq?: number;\n /** Max events per page. Default 100, max 500. */\n limit?: number;\n}\n\nexport interface StreamOptions {\n /** Subscription to stream. Must be transport=\"websocket\". */\n webhookId: number;\n /** Resume point — the last `seq` you processed. Default 0. */\n sinceSeq?: number;\n /** Auto-reconnect and resume from the last seq. Default true. */\n reconnect?: boolean;\n /** Override the websocket origin (default: derived from baseUrl). */\n wsUrl?: string;\n /** Called on every successful handshake with the `ready` frame. */\n onReady?: (ready: ReplayPage) => void;\n /**\n * Called when the server reports events after your cursor have aged out of\n * retention. This is the one case the stream cannot make you whole —\n * resync from the REST endpoints.\n */\n onTruncated?: (ready: ReplayPage) => void;\n}\n\nexport interface VerifySignatureOptions {\n /** Webhook signing secret (returned once from `createWebhook`). */\n secret: string;\n /** Value of the `X-PropLine-Timestamp` header. */\n timestamp: string;\n /** Raw request body. */\n body: Uint8Array | Buffer | string;\n /** Value of the `X-PropLine-Signature` header. */\n signature: string;\n}\n\n/**\n * Live daily-quota state, parsed from the `X-Daily-*` headers the API\n * returns on every authenticated response.\n */\nexport interface QuotaStatus {\n /** Your tier's daily request cap. */\n limit: number;\n /** Requests used today (including the request that produced this). */\n used: number;\n /** Requests left before the cap. */\n remaining: number;\n /** Unix seconds when the quota resets (00:00 UTC — a hard reset, not a rolling window). */\n resetEpoch: number;\n /** Quota reset time as a `Date`. */\n resetAt: Date;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.prop-line.com/v1\";\n\n// Streaming origin. Separate Fly app, separate machines — see stream().\nconst DEFAULT_WS_URL = \"wss://ws.prop-line.com\";\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Client for the PropLine player props API.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * const client = new PropLine(\"your_api_key\");\n * const sports = await client.getSports();\n * ```\n */\nexport class PropLine {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n /**\n * Daily-quota state from the most recent API response, or `null` before\n * the first request. Updated on every call (including 429s):\n *\n * ```ts\n * await client.getSports();\n * console.log(client.lastQuota?.remaining); // 999\n * ```\n */\n lastQuota: QuotaStatus | null = null;\n private readonly _fetch: typeof fetch;\n\n constructor(apiKey: string, options: PropLineOptions = {}) {\n if (!apiKey) {\n throw new Error(\"PropLine: apiKey is required\");\n }\n this.apiKey = apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this._fetch = options.fetch ?? globalThis.fetch;\n if (!this._fetch) {\n throw new Error(\n \"PropLine: global fetch is unavailable. Use Node 18+ or pass options.fetch.\"\n );\n }\n }\n\n // ------------------------------------------------------------------\n // Internals\n // ------------------------------------------------------------------\n\n private _buildUrl(path: string, params?: Record<string, string | number | undefined>): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null) {\n url.searchParams.set(k, String(v));\n }\n }\n }\n return url.toString();\n }\n\n /**\n * Record the X-Daily-* quota headers when present (absent on\n * unauthenticated errors, e.g. an invalid key's 401).\n */\n private _captureQuota(resp: Response): void {\n const limit = Number(resp.headers.get(\"X-Daily-Limit\"));\n const used = Number(resp.headers.get(\"X-Daily-Used\"));\n const remaining = Number(resp.headers.get(\"X-Daily-Remaining\"));\n const resetEpoch = Number(resp.headers.get(\"X-Daily-Reset\"));\n if (\n resp.headers.has(\"X-Daily-Limit\") &&\n Number.isFinite(limit) &&\n Number.isFinite(used) &&\n Number.isFinite(remaining) &&\n Number.isFinite(resetEpoch)\n ) {\n this.lastQuota = {\n limit,\n used,\n remaining,\n resetEpoch,\n resetAt: new Date(resetEpoch * 1000),\n };\n }\n }\n\n private async _request<T>(\n method: string,\n path: string,\n init: { params?: Record<string, string | number | undefined>; body?: unknown } = {}\n ): Promise<T> {\n const url = this._buildUrl(path, init.params);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method,\n headers: {\n \"X-API-Key\": this.apiKey,\n ...(init.body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n const d = await readDetail(resp, \"Invalid API key\");\n throw new AuthError(d.message, d.info);\n }\n if (resp.status === 429) {\n const d = await readDetail(resp, \"Rate limit exceeded\");\n throw new RateLimitError(d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n if (resp.status === 204) {\n return undefined as T;\n }\n return (await resp.json()) as T;\n }\n\n // ------------------------------------------------------------------\n // Public API\n // ------------------------------------------------------------------\n\n /** List all available sports. */\n getSports(): Promise<Sport[]> {\n return this._request<Sport[]>(\"GET\", \"/sports\");\n }\n\n /** List upcoming events for a sport. */\n getEvents(sport: string): Promise<PropLineEvent[]> {\n return this._request<PropLineEvent[]>(\"GET\", `/sports/${encodeURIComponent(sport)}/events`);\n }\n\n /**\n * Get current odds. With `eventId`, returns single-event odds (including\n * player props). Without, returns bulk odds for all upcoming events.\n *\n * Each response carries a `bookmakers` array — iterate it to compare\n * lines across Bovada, DraftKings, FanDuel, Pinnacle, Unibet, and\n * PrizePicks (coverage varies by sport).\n */\n getOdds(\n sport: string,\n options: GetOddsOptions & { eventId: number | string }\n ): Promise<OddsResponse>;\n getOdds(\n sport: string,\n options?: Omit<GetOddsOptions, \"eventId\"> & { eventId?: undefined }\n ): Promise<OddsResponse[]>;\n getOdds(\n sport: string,\n options: GetOddsOptions = {}\n ): Promise<OddsResponse | OddsResponse[]> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n const periodParam = _periodParam(options.period);\n if (periodParam !== undefined) params.period = periodParam;\n const bookmakersParam = _bookmakersParam(options.bookmakers);\n if (bookmakersParam !== undefined) params.bookmakers = bookmakersParam;\n if (options.includeLinks) params.includeLinks = \"true\";\n if (options.includeBookIds) params.includeBookIds = \"true\";\n const sp = encodeURIComponent(sport);\n if (options.eventId !== undefined) {\n return this._request<OddsResponse>(\n \"GET\",\n `/sports/${sp}/events/${encodeURIComponent(String(options.eventId))}/odds`,\n { params }\n );\n }\n return this._request<OddsResponse[]>(\"GET\", `/sports/${sp}/odds`, { params });\n }\n\n /** List the available market types for an event. */\n getMarkets(sport: string, eventId: number | string): Promise<MarketSummary[]> {\n return this._request<MarketSummary[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/markets`\n );\n }\n\n /**\n * Get historical odds movement for an event.\n *\n * Hobby+: full snapshots. Free tier: redacted (snapshot counts only).\n *\n * Supports period-historical filters:\n * - `from` / `to` — absolute ISO timestamps\n * - `relativeFrom` / `relativeTo` — offsets to commence_time (\"-3h\", \"-30m\", \"0\")\n * - `interval` — downsample to a fixed bucket size\n * - `changesOnly` — drop unchanged adjacent snapshots\n */\n getOddsHistory(\n sport: string,\n eventId: number | string,\n options: GetOddsHistoryOptions = {}\n ): Promise<OddsHistoryResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n if (options.from !== undefined) params.from = options.from;\n if (options.to !== undefined) params.to = options.to;\n if (options.relativeFrom !== undefined) params.relative_from = options.relativeFrom;\n if (options.relativeTo !== undefined) params.relative_to = options.relativeTo;\n if (options.interval !== undefined) params.interval = options.interval;\n if (options.changesOnly) params.changes_only = \"true\";\n const periodParam2 = _periodParam(options.period);\n if (periodParam2 !== undefined) params.period = periodParam2;\n const bookmakersParam2 = _bookmakersParam(options.bookmakers);\n if (bookmakersParam2 !== undefined) params.bookmakers = bookmakersParam2;\n return this._request<OddsHistoryResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/odds/history`,\n { params }\n );\n }\n\n /**\n * Get the opening AND closing line per `(book, market, outcome)` for an\n * event. Closing is the last snapshot at or before commence_time\n * (`price` / `point` / `closing_at`); opening is the first snapshot in\n * the same 14-day pre-kickoff window (`opening_price` / `opening_point`\n * / `opening_at`). Canonical CLV helper: replaces \"fetch full history →\n * find the first and last pre-game rows\" with one call.\n *\n * Compare the *points* as well as the prices — on spreads and totals\n * the number moves as much as the price, so a price-only comparison\n * mis-measures those markets.\n *\n * Hobby+: full data. Free tier: redacted.\n */\n getOddsClosing(\n sport: string,\n eventId: number | string,\n options: GetOddsClosingOptions = {}\n ): Promise<OddsClosingResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n const periodParam3 = _periodParam(options.period);\n if (periodParam3 !== undefined) params.period = periodParam3;\n const bookmakersParam3 = _bookmakersParam(options.bookmakers);\n if (bookmakersParam3 !== undefined) params.bookmakers = bookmakersParam3;\n return this._request<OddsClosingResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/odds/closing`,\n { params }\n );\n }\n\n /** Get game scores and status (free tier). */\n getScores(sport: string, options: GetScoresOptions = {}): Promise<ScoreEvent[]> {\n return this._request<ScoreEvent[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/scores`,\n { params: { days_from: options.daysFrom ?? 3 } }\n );\n }\n\n /**\n * PrizePicks Power/Flex entry payout schedule (2-6 legs) plus the per-leg\n * breakeven win probability for each play. Pass `legWinProb` to also get\n * `expected_return` (per $1) and `is_plus_ev` per play — turning a slip\n * into the hit rate it actually needs to clear.\n *\n * These are PrizePicks's *standard* published payouts; demon/goblin per-pick\n * modifiers aren't in PrizePicks's feed, so they're not reflected. Breakeven\n * assumes independent legs. See the `disclaimer` field on the response.\n */\n getDfsPayouts(\n options: GetDfsPayoutsOptions = {}\n ): Promise<DfsPayoutsResponse> {\n const params: Record<string, string | number> = {\n platform: options.platform ?? \"prizepicks\",\n };\n if (options.legWinProb !== undefined) params.leg_win_prob = options.legWinProb;\n return this._request<DfsPayoutsResponse>(\"GET\", \"/dfs/payouts\", { params });\n }\n\n /**\n * Synthetic MLB Grand Salami for a given UTC date — total runs scored\n * across every MLB game on the slate, plus each book's implied Grand\n * Salami line (median of per-game primary totals across our MLB books).\n *\n * No retail sportsbook quotes this as a single market, so historical\n * cross-book Grand Salami data isn't available elsewhere. Free tier;\n * defaults to today (UTC).\n */\n getMlbGrandSalami(\n options: GetMlbGrandSalamiOptions = {}\n ): Promise<MlbGrandSalamiResponse> {\n const params: Record<string, string> = {};\n if (options.date) params.date = options.date;\n return this._request<MlbGrandSalamiResponse>(\n \"GET\",\n \"/sports/baseball_mlb/grand-salami\",\n { params }\n );\n }\n\n /**\n * Synthetic NHL Daily Goals Total for a given UTC date — total goals\n * scored (incl. OT/SO) across every NHL game on the slate, plus each\n * book's implied Daily Goals Total line (median of per-game primary\n * totals across our NHL books).\n *\n * Hockey's equivalent of the MLB Grand Salami. No retail sportsbook\n * quotes this as a single market. Free tier; defaults to today (UTC).\n */\n getNhlDailyGoalsTotal(\n options: GetNhlDailyGoalsTotalOptions = {}\n ): Promise<NhlDailyGoalsTotalResponse> {\n const params: Record<string, string> = {};\n if (options.date) params.date = options.date;\n return this._request<NhlDailyGoalsTotalResponse>(\n \"GET\",\n \"/sports/hockey_nhl/daily-goals-total\",\n { params }\n );\n }\n\n /**\n * Factual volume of graded player props over the last N days (free tier).\n *\n * Aggregated counts only — a coverage proof (every outcome counted was\n * graded against the real box score), never a profitability claim.\n *\n * @param days Look-back window, 1-90 (default 30).\n */\n getResolutionSummary(days = 30): Promise<ResolutionSummary> {\n return this._request<ResolutionSummary>(\n \"GET\",\n \"/markets/resolution-summary\",\n { params: { days: String(days) } }\n );\n }\n\n /**\n * Get raw player/team box-score stats (book-agnostic, free tier).\n *\n * Returns actual stat values decoupled from any bookmaker's lines.\n *\n * Live during games for major US sports (MLB + WNBA now; NFL, NCAAF,\n * NBA, NHL at season start): while the event's status is \"in_progress\",\n * stats refresh roughly every 90 seconds with cumulative in-game values —\n * treat them as partial until status flips to \"final\". Other sports\n * populate stats at game completion.\n */\n getStats(\n sport: string,\n eventId: number | string,\n options: GetStatsOptions = {}\n ): Promise<StatsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.statType?.length) {\n params.stat_type = options.statType.join(\",\");\n }\n return this._request<StatsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/stats`,\n { params }\n );\n }\n\n /**\n * Get game context — the conditions a prop settles under.\n *\n * For MLB: probable starting pitchers and their throwing hand\n * (`home_probable_pitcher_hand` / `away_probable_pitcher_hand`, \"L\"/\"R\"/\"S\"\n * — platoon-split context for every batter prop), a confirmed-lineup flag,\n * the home-plate umpire, and first-pitch weather (outdoor / open-roof\n * venues; indoor venues return `weather: null` with `is_indoor: true`).\n * For NFL & NCAAF: the venue and kickoff weather (pitcher/umpire/lineup\n * fields are null for football). The same block is embedded in\n * {@link getResults}, so every graded prop carries its conditions — unique\n * to PropLine. Free tier. Rejects with a 404 when no context is on file\n * for the event yet.\n */\n getContext(\n sport: string,\n eventId: number | string\n ): Promise<ContextResponse> {\n return this._request<ContextResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/context`\n );\n }\n\n /**\n * Get line movement + steam detection from the snapshot tick history.\n *\n * Per (book, market, outcome): opening line, latest line, signed\n * implied-probability shift, point shift, direction. The `steam` array\n * flags outcomes multiple books moved the same direction — the\n * sharp-money signal across every book PropLine polls. When a book moves\n * the line itself, that outcome's `prob_shift` is null and `direction` is\n * `\"line_moved\"` (excluded from the steam signal). Unique to PropLine.\n * Hobby+ full; free tier redacted.\n */\n getMovement(\n sport: string,\n eventId: number | string,\n options: GetMovementOptions = {}\n ): Promise<MovementResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n params.period = _periodParam(options.period);\n params.bookmakers = _bookmakersParam(options.bookmakers);\n return this._request<MovementResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/movement`,\n { params }\n );\n }\n\n /**\n * Get resolved prop outcomes with actual player stats.\n *\n * Pro tier: full data. Free tier: redacted (resolution + actual nulled).\n */\n getResults(\n sport: string,\n eventId: number | string,\n options: GetResultsOptions = {}\n ): Promise<ResultsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n return this._request<ResultsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/results`,\n { params }\n );\n }\n\n /**\n * Recent resolved prop history for a player on a market.\n *\n * One entry per (event, bookmaker) pair. Pro: full. Free: redacted.\n */\n getPlayerHistory(\n sport: string,\n playerName: string,\n options: GetPlayerHistoryOptions\n ): Promise<PlayerHistoryResponse> {\n const params: Record<string, string | number | undefined> = {\n market: options.market,\n limit: options.limit ?? 20,\n };\n if (options.bookmaker) {\n params.bookmaker = options.bookmaker;\n }\n return this._request<PlayerHistoryResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/players/${encodeURIComponent(playerName)}/history`,\n { params }\n );\n }\n\n /**\n * A player's game log — recent games with every raw box-score stat.\n *\n * One call replaces one request per event, so L5/L10/L20, season splits,\n * charts and head-to-head can all be built from the raw rows. Free tier.\n *\n * Reads the RAW-STATS archive, not graded-prop history: it covers every\n * game with a box score on file, including games no sportsbook priced, so\n * a \"last 10 games\" window here really is the last 10 games — unlike one\n * built from {@link getPlayerHistory} or {@link getPlayerTrends}. Carries\n * no line, price or grade.\n *\n * @example\n * ```ts\n * const log = await client.getPlayerGames(\"baseball_mlb\", \"Aaron Judge\", { limit: 10 });\n * const hits = log.games.reduce((n, g) => n + (g.stats.hits ?? 0), 0);\n *\n * // Last 5 meetings with Boston — not the Boston games among his last 5.\n * const h2h = await client.getPlayerGames(\"baseball_mlb\", \"Aaron Judge\", {\n * limit: 5,\n * opponent: \"BOS\",\n * });\n * ```\n */\n getPlayerGames(\n sportKey: string,\n playerName: string,\n options: GetPlayerGamesOptions = {}\n ): Promise<PlayerGameLog> {\n const params: Record<string, string | number | undefined> = {\n limit: options.limit ?? 20,\n };\n if (options.opponent) {\n params.opponent = options.opponent;\n }\n if (options.statType) {\n params.stat_type = Array.isArray(options.statType)\n ? options.statType.join(\",\")\n : options.statType;\n }\n return this._request<PlayerGameLog>(\n \"GET\",\n `/sports/${encodeURIComponent(sportKey)}/players/${encodeURIComponent(playerName)}/games`,\n { params }\n );\n }\n\n /**\n * Rolling hit-rate trends for a player across one or all markets.\n *\n * Returns over/under/push splits over the last 5/10/20/50 graded games,\n * the current streak, and the most recent game per market. Pro: full.\n * Free: redacted.\n */\n getPlayerTrends(\n sportKey: string,\n playerName: string,\n options: GetPlayerTrendsOptions = {}\n ): Promise<PlayerTrends> {\n const params: Record<string, string | undefined> = {};\n if (options.market) {\n params.market = options.market;\n }\n if (options.dfsOddsType) {\n params.dfs_odds_type = options.dfsOddsType;\n }\n return this._request<PlayerTrends>(\n \"GET\",\n `/sports/${encodeURIComponent(sportKey)}/players/${encodeURIComponent(playerName)}/trends`,\n { params }\n );\n }\n\n /**\n * Cross-book +EV analysis for a single event (Pro+ tier).\n *\n * Groups every outcome by (market, player, line) across the books we\n * carry, derives a no-vig fair line from a sharp anchor (Pinnacle\n * preferred, Bovada fallback), and returns EV% per book at the same\n * line. Outcomes are sorted with +EV plays floated to the top.\n *\n * PrizePicks is excluded — its synthetic +100/+100 prices aren't\n * payout odds. Lines without sharp-anchor coverage are dropped.\n *\n * @example\n * ```ts\n * const ev = await client.getEventEv(\"baseball_mlb\", 12345);\n * for (const line of ev.lines) {\n * const plus = line.outcomes.filter(o => o.is_plus_ev);\n * if (plus.length) console.log(line.market_key, line.description, plus);\n * }\n * ```\n */\n /**\n * List futures markets for a sport — championship winner, MVP,\n * division winner, season win totals, etc. Each row is one (futures\n * event, book, market) with every team or player priced. Free tier;\n * aggregated across each book's futures feed (Bovada, FanDuel,\n * DraftKings, and Pinnacle).\n *\n * @example\n * ```ts\n * const futures = await client.getFutures(\"baseball_mlb\");\n * for (const event of futures) {\n * console.log(`${event.title} @ ${event.commence_time}`);\n * for (const m of event.markets) {\n * const top3 = [...m.outcomes].sort((a, b) => a.price - b.price).slice(0, 3);\n * for (const o of top3) console.log(` ${o.name}: ${o.price}`);\n * }\n * }\n * ```\n *\n * @param options.bookmakers Optional book key(s) to restrict the per-book\n * market rows (the-odds-api-compatible; omitted = all books, unknown keys\n * match nothing). A futures event left with no matching market is dropped.\n */\n getFutures(\n sport: string,\n options: { bookmakers?: string | string[] } = {}\n ): Promise<FuturesEvent[]> {\n const params: Record<string, string | undefined> = {};\n const bookmakersParam = _bookmakersParam(options.bookmakers);\n if (bookmakersParam !== undefined) params.bookmakers = bookmakersParam;\n return this._request<FuturesEvent[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/futures`,\n { params }\n );\n }\n\n /**\n * Market-implied consensus projections for a single event.\n *\n * One row per (market, player): the statistical value the betting\n * market collectively implies — the line where the no-vig P(over)\n * crosses 50%, median across contributing sportsbooks. Built for\n * validating your own statistical/fantasy projections against the\n * live market. Market-implied arithmetic over sportsbook prices,\n * never a forecast. DFS pick'em pricing is excluded.\n *\n * Paid tier required (Hobby+); free tier receives the structure with\n * projected values nulled and `redacted: true`.\n *\n * @example\n * ```ts\n * const proj = await client.getEventProjections(\"football_nfl\", 25070);\n * for (const row of proj.projections) {\n * console.log(row.player, row.market_key, row.projected_value);\n * }\n * ```\n */\n getEventProjections(\n sport: string,\n eventId: number | string,\n options: GetEventProjectionsOptions = {}\n ): Promise<EventProjectionsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n return this._request<EventProjectionsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/projections`,\n { params }\n );\n }\n\n getEventEv(\n sport: string,\n eventId: number | string,\n options: GetEventEvOptions = {}\n ): Promise<EventEvResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n if (options.bookmakers) {\n params.bookmakers = Array.isArray(options.bookmakers)\n ? options.bookmakers.join(\",\")\n : options.bookmakers;\n }\n return this._request<EventEvResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/ev`,\n { params }\n );\n }\n\n /**\n * Cross-book best-line lookup for a single event.\n *\n * For each (market, player, line) tuple, returns the single best\n * American price across every book we carry, with the book name\n * attached. Companion to `getEventEv`: best-line tells you which\n * book has the highest payout right now; +EV tells you whether\n * that price beats a sharp no-vig fair line. Most line shoppers\n * want both.\n *\n * PrizePicks is excluded from the comparison — its DFS payout\n * structure (synthetic +100/+100 quotes) isn't directly comparable\n * to traditional sportsbook odds.\n *\n * Hobby tier or higher sees prices. Free tier gets a redacted\n * teaser: the full structure — every line, side, book identity, and\n * the best-first ranking — with every price null, plus\n * `redacted: true` and an `upgrade_url`.\n *\n * @example\n * ```ts\n * const bl = await client.getEventBestLine(\"baseball_mlb\", 12345);\n * for (const line of bl.lines) {\n * for (const [side, info] of Object.entries(line.sides)) {\n * console.log(\n * `${line.description} ${side} ${line.point}: ` +\n * `${info.best.price} @ ${info.best.book_title}`\n * );\n * }\n * }\n * ```\n */\n getEventBestLine(\n sport: string,\n eventId: number | string,\n options: GetEventBestLineOptions = {}\n ): Promise<EventBestLineResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n if (options.bookmakers) {\n params.bookmakers = Array.isArray(options.bookmakers)\n ? options.bookmakers.join(\",\")\n : options.bookmakers;\n }\n if (options.includeLinks) params.includeLinks = \"true\";\n return this._request<EventBestLineResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/best-line`,\n { params }\n );\n }\n\n /**\n * Calculate EV% for a user-supplied price against the event's\n * no-vig fair anchor. Useful for books PropLine doesn't carry —\n * Caesars, BetMGM, Fanatics, BetUS, Hard Rock — where you have a\n * price in hand and want to know if it's +EV against the sharp\n * consensus we do carry.\n *\n * Same fair-line math as `getEventEv` (Pinnacle-preferred anchor,\n * no-vig devigging) but takes one user price as input. Pro tier.\n *\n * @example\n * ```ts\n * const r = await client.calcEventEv(\"baseball_mlb\", 12614, {\n * market: \"h2h\",\n * name: \"Pittsburgh Pirates\",\n * price: -118,\n * });\n * console.log(`EV ${r.ev_pct}% fair=${r.fair_prob}`);\n * ```\n */\n calcEventEv(\n sport: string,\n eventId: number | string,\n options: CalcEventEvOptions\n ): Promise<EventEvCalcResponse> {\n const params: Record<string, string | number | undefined> = {\n market: options.market,\n name: options.name,\n price: options.price,\n };\n if (options.point !== undefined) params.point = options.point;\n if (options.description) params.description = options.description;\n return this._request<EventEvCalcResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/ev/calc`,\n { params }\n );\n }\n\n /**\n * Bulk CSV export of resolved prop outcomes (Pro+ tier).\n *\n * If `outPath` is provided, streams the CSV to disk and resolves to the\n * path. Otherwise resolves to the full CSV bytes as a `Uint8Array`.\n *\n * @example\n * ```ts\n * await client.exportResolvedProps({\n * sport: \"baseball_mlb\",\n * market: \"pitcher_strikeouts\",\n * since: \"2026-04-01T00:00:00Z\",\n * outPath: \"./mlb-strikeouts.csv\",\n * });\n * ```\n */\n async exportResolvedProps(\n options: ExportResolvedPropsOptions & { outPath: string }\n ): Promise<string>;\n async exportResolvedProps(\n options: ExportResolvedPropsOptions & { outPath?: undefined }\n ): Promise<Uint8Array>;\n async exportResolvedProps(\n options: ExportResolvedPropsOptions\n ): Promise<string | Uint8Array> {\n const params: Record<string, string | undefined> = { sport: options.sport };\n if (options.market) params.market = options.market;\n if (options.bookmaker) params.bookmaker = options.bookmaker;\n if (options.since) params.since = options.since;\n if (options.until) params.until = options.until;\n\n const url = this._buildUrl(\"/exports/resolved-props\", params);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method: \"GET\",\n headers: { \"X-API-Key\": this.apiKey },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n throw new AuthError();\n }\n if (resp.status === 403) {\n const d = await readDetail(resp, \"Pro tier required\");\n throw new PropLineError(403, d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n const buf = new Uint8Array(await resp.arrayBuffer());\n if (options.outPath) {\n await writeFile(options.outPath, buf);\n return options.outPath;\n }\n return buf;\n }\n\n /**\n * Bulk CSV export of the full line-movement time-series.\n *\n * One row per (outcome, snapshot): every recorded odds snapshot (price +\n * line, per book, including period markets), not just the closing line.\n * This is the raw tick history no subscription tier can pull in bulk —\n * Pro/Streaming get per-event {@link getOddsHistory} only; this bulk\n * firehose is exclusive to the one-time Historical Backfill pass and\n * Enterprise.\n *\n * A full archive runs to gigabytes per sport — page month by month with\n * `since`/`until`. If `outPath` is provided, streams to disk and resolves\n * to the path; otherwise resolves to the CSV bytes as a `Uint8Array`.\n *\n * @example\n * ```ts\n * await client.exportOddsHistory({\n * sport: \"baseball_mlb\",\n * since: \"2026-04-01T00:00:00Z\",\n * until: \"2026-05-01T00:00:00Z\",\n * outPath: \"./mlb-line-history-apr.csv\",\n * });\n * ```\n */\n async exportOddsHistory(\n options: ExportOddsHistoryOptions & { outPath: string }\n ): Promise<string>;\n async exportOddsHistory(\n options: ExportOddsHistoryOptions & { outPath?: undefined }\n ): Promise<Uint8Array>;\n async exportOddsHistory(\n options: ExportOddsHistoryOptions\n ): Promise<string | Uint8Array> {\n const params: Record<string, string | undefined> = { sport: options.sport };\n if (options.market) params.market = options.market;\n if (options.bookmaker) params.bookmaker = options.bookmaker;\n if (options.since) params.since = options.since;\n if (options.until) params.until = options.until;\n\n const url = this._buildUrl(\"/exports/odds-history\", params);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method: \"GET\",\n headers: { \"X-API-Key\": this.apiKey },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n throw new AuthError();\n }\n if (resp.status === 403) {\n const d = await readDetail(\n resp,\n \"Historical Backfill pass or Enterprise required\"\n );\n throw new PropLineError(403, d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n const buf = new Uint8Array(await resp.arrayBuffer());\n if (options.outPath) {\n await writeFile(options.outPath, buf);\n return options.outPath;\n }\n return buf;\n }\n\n // ------------------------------------------------------------------\n // Webhooks (Streaming tier)\n // ------------------------------------------------------------------\n\n /**\n * Register a webhook subscription. Streaming tier only.\n *\n * The returned object includes the full signing `secret` — this is the\n * ONLY time it's revealed. Store it securely.\n */\n createWebhook(options: CreateWebhookOptions): Promise<Webhook> {\n return this._request<Webhook>(\"POST\", \"/webhooks\", {\n body: webhookBody(options),\n });\n }\n\n /** List your webhook subscriptions. Secrets are masked. */\n listWebhooks(): Promise<Webhook[]> {\n return this._request<Webhook[]>(\"GET\", \"/webhooks\");\n }\n\n /** Get a single webhook subscription. Secret is masked. */\n getWebhook(webhookId: number): Promise<Webhook> {\n return this._request<Webhook>(\"GET\", `/webhooks/${webhookId}`);\n }\n\n /** Update fields on a webhook. Only supplied fields are changed. */\n updateWebhook(webhookId: number, options: UpdateWebhookOptions): Promise<Webhook> {\n return this._request<Webhook>(\"PATCH\", `/webhooks/${webhookId}`, {\n body: webhookBody(options),\n });\n }\n\n /** Delete a webhook (cascades its delivery history). */\n deleteWebhook(webhookId: number): Promise<{ ok: boolean } | unknown> {\n return this._request(\"DELETE\", `/webhooks/${webhookId}`);\n }\n\n /** Queue a sample `test` payload to the webhook's URL. */\n testWebhook(webhookId: number): Promise<unknown> {\n return this._request(\"POST\", `/webhooks/${webhookId}/test`);\n }\n\n /** Last 50 (default) delivery attempts for a webhook. */\n listWebhookDeliveries(\n webhookId: number,\n options: ListWebhookDeliveriesOptions = {}\n ): Promise<WebhookDelivery[]> {\n return this._request<WebhookDelivery[]>(\n \"GET\",\n `/webhooks/${webhookId}/deliveries`,\n { params: { limit: options.limit ?? 50, before_id: options.beforeId } }\n );\n }\n\n /**\n * Re-read this subscription's events in order, from a cursor.\n *\n * Answers \"my endpoint was down — what did I miss?\". Every delivery carries\n * an `X-PropLine-Sequence` header: a counter monotonic *within your\n * subscription*. Store the highest one you processed and pass it as\n * `sinceSeq`.\n *\n * Do NOT use `X-PropLine-Delivery` as the cursor — that id is global across\n * every subscription, so its gaps are other customers' traffic.\n *\n * Events come back oldest-first (the opposite of `listWebhookDeliveries`,\n * which is a newest-first debugging log). Page by passing `next_seq` back\n * as `sinceSeq` while `has_more` is true.\n *\n * **Check `truncated`.** True means events after your cursor have aged out\n * of retention (2 days, max 5,000 deliveries per subscription) and are gone\n * — resync from the REST endpoints instead of assuming you are current.\n *\n * Does not count against your daily request quota.\n */\n replayWebhookEvents(\n webhookId: number,\n options: ReplayWebhookEventsOptions = {}\n ): Promise<ReplayPage> {\n return this._request<ReplayPage>(\n \"GET\",\n `/webhooks/${webhookId}/replay`,\n { params: { since_seq: options.sinceSeq ?? 0, limit: options.limit ?? 100 } }\n );\n }\n\n /**\n * Stream a websocket subscription as an async iterable.\n *\n * ```ts\n * for await (const ev of client.stream({ webhookId: 12, sinceSeq: 4180 })) {\n * console.log(ev.seq, ev.event_type, ev.data);\n * }\n * ```\n *\n * The subscription must have been created with `transport: \"websocket\"`.\n * Same events, same filters, same `seq` as an HTTP webhook — one\n * subscription, different transport.\n *\n * **Reconnects automatically and resumes from the last `seq` it saw**, which\n * is the whole point of the sequence: a dropped connection does not become a\n * gap in your data. Set `reconnect: false` to get a single connection that\n * ends when the socket closes.\n *\n * If the server reports `truncated` — events after your cursor aged out of\n * retention and are gone — `onTruncated` fires. Handle it: that is the one\n * case where the stream cannot make you whole and you should resync from the\n * REST endpoints.\n */\n async *stream(options: StreamOptions): AsyncGenerator<ReplayEvent, void, void> {\n // ⚠️ Streaming has its OWN origin and does NOT derive from baseUrl.\n // Deriving it (the first cut of this method did) sends the socket to\n // api.prop-line.com — which serves /v1/stream too, from the same ASGI\n // app, so it WORKS and nothing complains. It just parks a persistent\n // connection on the REST tier's event loop, which is precisely what the\n // separate websocket tier exists to prevent. Caught 2026-08-31 by reading\n // the machine id in ws_connections and finding an app-tier machine.\n // Override with `wsUrl` only for self-hosted or local development.\n const wsBase = (options.wsUrl ?? DEFAULT_WS_URL)\n .replace(/^http:/, \"ws:\")\n .replace(/^https:/, \"wss:\")\n .replace(/\\/v1\\/?$/, \"\")\n .replace(/\\/$/, \"\");\n const url = `${wsBase}/v1/stream`;\n let cursor = options.sinceSeq ?? 0;\n let attempt = 0;\n\n for (;;) {\n const queue: ReplayEvent[] = [];\n let notify: (() => void) | null = null;\n let closed: Error | null = null;\n let opened = false;\n\n const ws = new WebSocket(url);\n const wake = () => { const n = notify; notify = null; n?.(); };\n\n ws.addEventListener(\"open\", () => {\n opened = true;\n ws.send(JSON.stringify({\n type: \"auth\",\n api_key: this.apiKey,\n webhook_id: options.webhookId,\n since_seq: cursor,\n }));\n });\n ws.addEventListener(\"message\", (e: MessageEvent) => {\n let msg: Record<string, unknown>;\n try { msg = JSON.parse(String(e.data)); } catch { return; }\n if (msg.type === \"ready\") {\n attempt = 0; // a successful handshake resets backoff\n if (msg.truncated) options.onTruncated?.(msg as unknown as ReplayPage);\n options.onReady?.(msg as unknown as ReplayPage);\n } else if (msg.type === \"event\") {\n queue.push(msg as unknown as ReplayEvent);\n wake();\n }\n // \"ping\" needs no reply — it exists to keep idle proxies from closing.\n });\n ws.addEventListener(\"close\", (e: CloseEvent) => {\n // 4401/4403/4404/4400 are terminal: retrying cannot fix a bad key, a\n // tier without access, or a subscription that is not yours. Only\n // transport failures and 4429 are worth reconnecting for.\n const terminal = [4400, 4401, 4403, 4404].includes(e.code);\n closed = new PropLineError(\n e.code,\n `stream closed${e.reason ? `: ${e.reason}` : \"\"}`,\n );\n (closed as PropLineError & { terminal?: boolean }).terminal = terminal;\n wake();\n });\n ws.addEventListener(\"error\", () => {\n if (!closed) closed = new PropLineError(0, \"stream connection error\");\n wake();\n });\n\n try {\n for (;;) {\n while (queue.length) {\n const ev = queue.shift()!;\n cursor = ev.seq; // advance BEFORE yielding, so a\n yield ev; // consumer `break` still resumes right\n }\n if (closed) break;\n await new Promise<void>((r) => { notify = r; });\n }\n } finally {\n try { ws.close(); } catch { /* already closed */ }\n }\n\n const err = closed as (PropLineError & { terminal?: boolean }) | null;\n if (err?.terminal) throw err;\n if (options.reconnect === false) {\n if (err && !opened) throw err;\n return;\n }\n // Capped exponential backoff. Without the cap a long outage would have\n // clients reconnecting hours apart; without backoff they would stampede.\n const delayMs = Math.min(30_000, 500 * 2 ** attempt++);\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n\n /**\n * Grade placed bets against their closing lines (CLV).\n *\n * Closing line value is the only durable proxy for whether a bettor has\n * edge: did the price you took beat the number the market settled on?\n * Send the bets you actually placed; each comes back with its closing\n * price, the de-vigged closing fair probability, CLV, and — once the\n * game settles — the graded result and actual stat value.\n *\n * Stateless: nothing is stored server-side.\n *\n * **Two CLV numbers are returned deliberately.** `clv_pct` is\n * price-vs-price — familiar and quotable, but vig-blind, so it flatters\n * a bet taken on the juicy side of a wide market. `ev_vs_close_pct`\n * scores your price against the DE-VIGGED close and is the honest one;\n * on a real bet the two came out +6.52% and +0.08%.\n *\n * The de-vig anchors to the **sharpest book quoting that line at close**\n * (`fair_source`), not the book you bet at — de-vigging your own book\n * always returns a negative number, because you paid its hold.\n *\n * Bets whose event has not started carry `closing_is_final: false`, land\n * in `summary.pending`, and are excluded from the summary averages:\n * before kickoff the \"closing\" price is just the latest price.\n *\n * Matching is fail-closed — a bet that cannot be pinned to exactly one\n * stored outcome returns `matched: false` with an `unmatched_reason`\n * rather than a confident wrong match. Max 500 bets per request.\n *\n * Hobby+ required; free tier receives the structure with numbers nulled.\n *\n * @example\n * const res = await client.gradeClv([{\n * ref: \"b1\",\n * sport_key: \"baseball_mlb\",\n * event_id: 150791,\n * market: \"batter_hits_runs_rbis\",\n * bookmaker: \"lowvig\",\n * selection: \"Drake Baldwin\",\n * side: \"Under\",\n * point: 0.5,\n * price: 145,\n * stake: 1,\n * }]);\n * console.log(res.summary.avg_ev_vs_close_pct);\n */\n gradeClv(bets: ClvBetInput[]): Promise<ClvGradeResponse> {\n return this._request<ClvGradeResponse>(\"POST\", \"/clv/grade\", { body: bets });\n }\n\n /**\n * Price a same-game parlay at the book's own correlated odds.\n *\n * Send two to ten legs from ONE event and get back the book's own price\n * for that exact slip — what a FanDuel customer would be offered for it\n * at that moment, not a model of it — beside `independent_price` (the\n * product of the live single-leg prices) and `correlation_factor`\n * (their ratio: the correlation the book is charging, below 1, or\n * paying, above 1, for). Measured live: Cardinals ML +205 × Freddie\n * Freeman to record a hit -260 → SGP +592 against an independent +322.\n *\n * Book-native. `bookmaker` is \"fanduel\" (its own betslip pricer) or\n * \"betonlineag\" / \"lowvig\" (the Sportcast engine both Chico brands embed,\n * same builder price); an unsupported value is a 422.\n *\n * Legs are named exactly as `/odds` names an outcome (market, name,\n * description, point, period), or by `book_outcome_id` from\n * `includeBookIds: true`. Matching is fail-closed — a leg that does not\n * pin to exactly one stored outcome is a 422 `leg_unmatched` naming the\n * leg. `quoted: false` means the book will not offer that combination as\n * a same-game parlay; refused legs carry the book's own `failure_code`.\n * Quotes for an identical slip are shared for 15 seconds.\n *\n * Hobby+ required; free tier receives the matched legs with every price\n * nulled and never triggers a book call.\n *\n * @example\n * const q = await client.priceSgp(\"baseball_mlb\", 150791, [\n * { market: \"h2h\", name: \"St. Louis Cardinals\" },\n * { market: \"batter_1plus_hits\", name: \"Freddie Freeman\", description: \"Freddie Freeman\" },\n * ]);\n * console.log(q.sgp_price, q.independent_price, q.correlation_factor);\n */\n priceSgp(\n sportKey: string,\n eventId: number | string,\n legs: SgpLegInput[],\n bookmaker = \"fanduel\",\n ): Promise<SgpQuoteResponse> {\n return this._request<SgpQuoteResponse>(\n \"POST\",\n `/sports/${encodeURIComponent(sportKey)}/events/${encodeURIComponent(String(eventId))}/sgp`,\n { body: { bookmaker, legs } },\n );\n }\n\n /**\n * Verify that an inbound webhook delivery was signed by PropLine.\n *\n * Compares HMAC-SHA256(secret, `${timestamp}.` + body) against the\n * `X-PropLine-Signature` header in constant time.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * app.post(\"/hooks/propline\", express.raw({ type: \"*\\/*\" }), (req, res) => {\n * const ok = PropLine.verifySignature({\n * secret: process.env.WEBHOOK_SECRET!,\n * timestamp: req.header(\"X-PropLine-Timestamp\")!,\n * body: req.body, // raw Buffer\n * signature: req.header(\"X-PropLine-Signature\")!,\n * });\n * if (!ok) return res.status(401).end();\n * // ...\n * });\n * ```\n */\n static verifySignature(options: VerifySignatureOptions): boolean {\n const { secret, timestamp, body, signature } = options;\n const bodyBuf =\n typeof body === \"string\"\n ? Buffer.from(body, \"utf8\")\n : body instanceof Buffer\n ? body\n : Buffer.from(body);\n const message = Buffer.concat([Buffer.from(`${timestamp}.`, \"utf8\"), bodyBuf]);\n const expected = createHmac(\"sha256\", secret).update(message).digest(\"hex\");\n if (expected.length !== signature.length) return false;\n try {\n return timingSafeEqual(Buffer.from(expected, \"hex\"), Buffer.from(signature, \"hex\"));\n } catch {\n return false;\n }\n }\n}\n\nfunction webhookBody(options: CreateWebhookOptions | UpdateWebhookOptions): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n const map: Array<[keyof (CreateWebhookOptions & UpdateWebhookOptions), string]> = [\n [\"url\", \"url\"],\n [\"events\", \"events\"],\n [\"filterSportKey\", \"filter_sport_key\"],\n [\"filterEventId\", \"filter_event_id\"],\n [\"filterMarketKey\", \"filter_market_key\"],\n [\"filterPlayerName\", \"filter_player_name\"],\n [\"filterBookmakerKey\", \"filter_bookmaker_key\"],\n [\"minPriceChangePct\", \"min_price_change_pct\"],\n [\"minSteamScore\", \"min_steam_score\"],\n [\"minBooksAgreeing\", \"min_books_agreeing\"],\n [\"batchMax\", \"batch_max\"],\n [\"active\", \"active\"],\n ];\n for (const [src, dst] of map) {\n const v = (options as Record<string, unknown>)[src as string];\n if (v !== undefined) body[dst] = v;\n }\n return body;\n}\n\ninterface ReadDetailResult {\n message: string;\n info?: PropLineErrorInfo;\n}\n\nasync function readDetail(\n resp: Response,\n fallback: string,\n): Promise<ReadDetailResult> {\n try {\n const text = await resp.text();\n if (!text) return { message: fallback };\n try {\n const json = JSON.parse(text) as { detail?: unknown };\n if (typeof json.detail === \"string\") return { message: json.detail };\n if (json.detail && typeof json.detail === \"object\") {\n const info = json.detail as PropLineErrorInfo;\n return {\n message: typeof info.message === \"string\" ? info.message : text,\n info,\n };\n }\n } catch {\n // not JSON\n }\n return { message: text || fallback };\n } catch {\n return { message: fallback };\n }\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/index.ts","../src/client.ts"],"sourcesContent":["/**\n * PropLine — Node/TypeScript SDK for the PropLine player props API.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * const client = new PropLine(\"your_api_key\");\n * const events = await client.getEvents(\"basketball_nba\");\n * const odds = await client.getOdds(\"basketball_nba\", {\n * eventId: events[0].id,\n * markets: [\"player_points\", \"player_rebounds\"],\n * });\n * ```\n */\n\nexport { PropLine } from \"./client.js\";\nexport {\n PropLineError,\n AuthError,\n RateLimitError,\n} from \"./client.js\";\nexport type {\n PropLineErrorInfo,\n PropLineOptions,\n QuotaStatus,\n GetOddsOptions,\n GetOddsHistoryOptions,\n GetOddsClosingOptions,\n PeriodFilter,\n GetScoresOptions,\n GetDfsPayoutsOptions,\n GetMlbGrandSalamiOptions,\n GetNhlDailyGoalsTotalOptions,\n GetStatsOptions,\n GetResultsOptions,\n GetPlayerHistoryOptions,\n GetPlayerGamesOptions,\n GetPlayerTrendsOptions,\n GetEventEvOptions,\n GetEventProjectionsOptions,\n GetEventBestLineOptions,\n CalcEventEvOptions,\n ExportResolvedPropsOptions,\n ExportOddsHistoryOptions,\n WebhookEventType,\n CreateWebhookOptions,\n UpdateWebhookOptions,\n ListWebhookDeliveriesOptions,\n ReplayWebhookEventsOptions,\n StreamOptions,\n VerifySignatureOptions,\n} from \"./client.js\";\n\nexport type {\n ClvBetInput,\n ClvGradedBet,\n ClvSummary,\n ClvGradeResponse,\n SgpLegInput,\n SgpLegQuote,\n SgpQuoteResponse,\n Sport,\n Event,\n Outcome,\n ResolvedOutcome,\n Market,\n Bookmaker,\n OddsResponse,\n MarketSummary,\n OutcomeSnapshot,\n OddsHistoryOutcome,\n OddsHistoryMarket,\n OddsHistoryBookmaker,\n OddsHistoryResponse,\n ClosingOutcome,\n ClosingMarket,\n ClosingBookmaker,\n OddsClosingResponse,\n ScoreEvent,\n MlbGrandSalamiBook,\n MlbGrandSalamiResponse,\n NhlDailyGoalsTotalBook,\n NhlDailyGoalsTotalResponse,\n ResolutionSummary,\n ResolutionSummarySport,\n ResolutionSummaryMarket,\n PlayerStat,\n StatsResponse,\n WeatherInfo,\n ContextResponse,\n MovementOutcome,\n MovementMarket,\n MovementBookmaker,\n SteamMove,\n MovementResponse,\n ResultsMarket,\n ResultsResponse,\n PlayerHistoryEntry,\n PlayerHistoryResponse,\n HitRateSplit,\n TrendStreak,\n TrendLastGame,\n PlayerMarketTrend,\n PlayerGame,\n PlayerGameLog,\n PlayerTrends,\n EvOutcome,\n EvLine,\n EventEvResponse,\n EventProjectionsResponse,\n ProjectionRow,\n EventEvCalcResponse,\n BestPrice,\n BestLineSide,\n BestLine,\n EventBestLineResponse,\n FuturesOutcome,\n FuturesMarket,\n FuturesEvent,\n Webhook,\n WebhookDelivery,\n ReplayEvent,\n ReplayPage,\n DfsPayoutTier,\n DfsPlayPayout,\n DfsPayoutsResponse,\n} from \"./types.js\";\n\n/** String constants for bookmaker keys in odds responses. */\nexport const Bookmakers = {\n BOVADA: \"bovada\",\n DRAFTKINGS: \"draftkings\",\n FANDUEL: \"fanduel\",\n PINNACLE: \"pinnacle\",\n UNIBET: \"unibet\",\n UNDERDOG: \"underdog\",\n KALSHI: \"kalshi\",\n POLYMARKET: \"polymarket\",\n PRIZEPICKS: \"prizepicks\",\n} as const;\n\nexport type BookmakerKey = (typeof Bookmakers)[keyof typeof Bookmakers];\n\nexport const VERSION = \"0.48.3\";\n","import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport type {\n ClvBetInput,\n ClvGradeResponse,\n SgpLegInput,\n SgpQuoteResponse,\n Sport,\n Event as PropLineEvent,\n OddsResponse,\n MarketSummary,\n OddsHistoryResponse,\n OddsClosingResponse,\n ScoreEvent,\n MlbGrandSalamiResponse,\n NhlDailyGoalsTotalResponse,\n ResolutionSummary,\n StatsResponse,\n ContextResponse,\n MovementResponse,\n ResultsResponse,\n PlayerGameLog,\n PlayerHistoryResponse,\n PlayerTrends,\n EventEvResponse,\n EventProjectionsResponse,\n EventEvCalcResponse,\n EventBestLineResponse,\n FuturesEvent,\n Webhook,\n WebhookDelivery,\n ReplayEvent,\n ReplayPage,\n DfsPayoutsResponse,\n} from \"./types.js\";\n\n/** Options for {@link PropLineClient.getDfsPayouts}. */\nexport interface GetDfsPayoutsOptions {\n /** DFS platform. Only \"prizepicks\" today. */\n platform?: string;\n /**\n * Assumed per-leg win probability in [0, 1]. When supplied, each play\n * also carries `expected_return` (per $1) and `is_plus_ev` at that rate.\n */\n legWinProb?: number;\n}\n\n/**\n * Structured error body returned by gated/throttled endpoints\n * (see https://prop-line.com/docs#errors). Branch on `error` — the\n * codes are stable — and follow the URLs instead of parsing prose.\n */\nexport interface PropLineErrorInfo {\n /** Stable machine-readable code, e.g. \"upgrade_required\", \"daily_limit_exceeded\". */\n error?: string;\n /** Human-readable sentence. */\n message?: string;\n /** Cheapest tier that unlocks a gated feature (403s). */\n required_tier?: string;\n /** Where to unlock it — pre-filled one-click URL on daily-cap 429s. */\n upgrade_url?: string;\n docs_url?: string;\n signup_url?: string;\n backfill_url?: string;\n /** Burst-limit backoff hint (429s). */\n retry_after_seconds?: number;\n /** Daily-cap 429s: recommended next plan incl. its own upgrade_url. */\n recommended?: { plan?: string; upgrade_url?: string; [key: string]: unknown };\n [key: string]: unknown;\n}\n\n/** Base error for all PropLine API failures. */\nexport class PropLineError extends Error {\n readonly statusCode: number;\n /** Human-readable detail message. */\n readonly detail: string;\n /** Structured error body, when the API returned one. */\n readonly info?: PropLineErrorInfo;\n\n constructor(statusCode: number, detail: string, info?: PropLineErrorInfo) {\n super(`[${statusCode}] ${detail}`);\n this.name = \"PropLineError\";\n this.statusCode = statusCode;\n this.detail = detail;\n this.info = info;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Stable machine-readable code (e.g. \"upgrade_required\"), if present. */\n get errorCode(): string | undefined {\n return this.info?.error;\n }\n\n /** The URL that unlocks a gated feature or lifts a cap, if present. */\n get upgradeUrl(): string | undefined {\n return this.info?.upgrade_url ?? this.info?.recommended?.upgrade_url;\n }\n}\n\n/** Thrown when the API key is missing or invalid (HTTP 401). */\nexport class AuthError extends PropLineError {\n constructor(detail = \"Invalid API key\", info?: PropLineErrorInfo) {\n super(401, detail, info);\n this.name = \"AuthError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when the daily request limit is exceeded (HTTP 429). */\nexport class RateLimitError extends PropLineError {\n constructor(detail = \"Rate limit exceeded\", info?: PropLineErrorInfo) {\n super(429, detail, info);\n this.name = \"RateLimitError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport interface PropLineOptions {\n /** API base URL. Default: `https://api.prop-line.com/v1`. */\n baseUrl?: string;\n /** Request timeout in milliseconds. Default: 15000. */\n timeoutMs?: number;\n /** Custom fetch implementation. Defaults to global `fetch` (Node 18+). */\n fetch?: typeof fetch;\n}\n\n/**\n * Game-period filter. String of canonical codes, optionally\n * comma-separated, or the sentinel `\"all\"`. Omitted = full-game\n * markets only (backwards-compatible default).\n *\n * \"q1\" — 1st quarter\n * \"q1,q2\" — 1st and 2nd quarters\n * [\"q1\",\"q2\"] — same, as an array\n * \"h1\" — 1st half\n * \"p1\"|\"p2\"|\"p3\" — hockey periods\n * \"i6\" — 6th inning\n * \"f3\"|\"f5\"|\"f7\" — first N innings\n * \"all\" — every period including full game\n */\nexport type PeriodFilter = string | string[];\n\nexport interface GetOddsOptions {\n /** Specific event ID to get odds (with player props) for. Omit for bulk odds. */\n eventId?: number | string;\n /**\n * Market keys to filter by. If omitted, the bulk `/odds` endpoint\n * defaults to `h2h` and the per-event `/odds` endpoint defaults to\n * `h2h,spreads,totals` — game-line markets every book carries across\n * every sport. Pass an explicit list to fetch player props (e.g.\n * `[\"pitcher_strikeouts\", \"batter_home_runs\"]` for MLB,\n * `[\"player_points\", \"player_rebounds\"]` for NBA).\n */\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /**\n * Bookmaker key(s) to restrict the response to (e.g. `\"draftkings\"` or\n * `[\"draftkings\", \"fanduel\"]`). Omitted = all books. Same parameter name\n * as the-odds-api.\n */\n bookmakers?: string | string[];\n /**\n * When true, each bookmaker block carries a `link` — that book's\n * public event-page URL (plain navigation, no affiliate tagging),\n * so your UI can click out from a line to the book. Links ship for\n * Bovada, DraftKings, FanDuel, BetMGM, Kalshi, Polymarket and\n * Smarkets; other books return null. Maps to the\n * the-odds-api-compatible `includeLinks=true` query param.\n */\n includeLinks?: boolean;\n /**\n * When true, each bookmaker block carries a `book_event_id` and each\n * outcome a `book_outcome_id` — that book's OWN identifiers for the\n * event and the priced selection. Use these to join PropLine rows onto\n * a book's native feed by id instead of matching on team names,\n * players and lines. Kalshi ships both (the event ticker and the\n * per-contract market ticker, e.g. `KXMLBGAME-26AUG08NYYBOS-NYY`);\n * most other books ship an event id. Books without a stable id return\n * null.\n *\n * NB a two-sided market can share ONE `book_outcome_id` across both\n * legs — a Kalshi contract is binary, so Over and Under are its YES\n * and NO sides. The id identifies the contract; the outcome's `name`\n * says which side.\n *\n * PropLine-specific (`includeBookIds=true`); the-odds-api has no\n * equivalent.\n */\n includeBookIds?: boolean;\n}\n\nexport interface GetOddsHistoryOptions {\n markets?: string[];\n /** ISO timestamp; only include snapshots at or after this time. Mutually exclusive with `relativeFrom`. */\n from?: string;\n /** ISO timestamp; only include snapshots at or before this time. Mutually exclusive with `relativeTo`. */\n to?: string;\n /** Offset relative to commence_time, e.g. \"-3h\", \"-30m\", \"-90s\". Mutually exclusive with `from`. */\n relativeFrom?: string;\n /** Offset relative to commence_time, e.g. \"-1m\" or \"0\" for commence_time itself. Mutually exclusive with `to`. */\n relativeTo?: string;\n /** Downsample to one snapshot per bucket. Latest snapshot in each bucket wins. */\n interval?: \"30s\" | \"1m\" | \"5m\" | \"15m\" | \"30m\" | \"1h\";\n /** When true, drop snapshots whose (price, point) match the previous one. Opening line is always kept. */\n changesOnly?: boolean;\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nexport interface GetOddsClosingOptions {\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nexport interface GetMovementOptions {\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nfunction _periodParam(p: PeriodFilter | undefined): string | undefined {\n if (p === undefined) return undefined;\n return typeof p === \"string\" ? p : p.join(\",\");\n}\n\nfunction _bookmakersParam(b: string | string[] | undefined): string | undefined {\n if (b === undefined || b.length === 0) return undefined;\n return typeof b === \"string\" ? b : b.join(\",\");\n}\n\nexport interface GetScoresOptions {\n /** Days back to include (default 3). */\n daysFrom?: number;\n}\n\nexport interface GetMlbGrandSalamiOptions {\n /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */\n date?: string;\n}\n\nexport interface GetNhlDailyGoalsTotalOptions {\n /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */\n date?: string;\n}\n\nexport interface GetStatsOptions {\n /** Stat types to filter by (e.g. `[\"strikeouts\", \"hits\"]`). */\n statType?: string[];\n}\n\nexport interface GetResultsOptions {\n markets?: string[];\n}\n\nexport interface GetPlayerHistoryOptions {\n /** Market key (e.g. `\"pitcher_strikeouts\"`). Required. */\n market: string;\n /** Restrict to a single bookmaker (e.g. `\"draftkings\"`). */\n bookmaker?: string;\n /** Max entries (1-100). Default 20. */\n limit?: number;\n}\n\nexport interface GetPlayerGamesOptions {\n /** Games to return, 1-100. Default 20. */\n limit?: number;\n /**\n * Head-to-head filter. Accepts a full name, nickname or abbreviation\n * (\"Boston Red Sox\", \"Red Sox\", \"BOS\"). The limit applies AFTER this\n * filter, so `{ opponent: \"BOS\", limit: 10 }` is the last 10 MEETINGS,\n * not the Boston games among the last 10 games. Not capped to the\n * current season.\n */\n opponent?: string;\n /**\n * Stat name(s) to return; omit for all. Vocabulary is per-sport —\n * see https://prop-line.com/docs#stats\n */\n statType?: string | string[];\n}\n\nexport interface GetPlayerTrendsOptions {\n /** Market key (e.g. `\"batter_total_bases\"`). Omit for all markets. */\n market?: string;\n /**\n * PrizePicks pick-em flavor to compute trends against: `\"standard\"`\n * (default market line), `\"goblin\"`, or `\"demon\"`. When set, the trend is\n * computed against that flavor's PrizePicks line only. Omit for the default\n * cross-book behavior. Flavor tagging began 2026-06-16.\n */\n dfsOddsType?: \"standard\" | \"goblin\" | \"demon\";\n}\n\nexport interface GetEventProjectionsOptions {\n /** Optional market-key filter (comma-separated string or array). */\n markets?: string | string[];\n}\n\nexport interface GetEventEvOptions {\n /**\n * Optional market filter. Pass a single comma-separated string or an\n * array of market keys (e.g. `[\"pitcher_strikeouts\", \"batter_hits\"]`).\n * Omit to evaluate every market on the event.\n */\n markets?: string | string[];\n /**\n * Optional bookmaker filter (the-odds-api-compatible). Pass a\n * comma-separated string or an array of book keys (e.g.\n * `[\"draftkings\", \"fanduel\"]`) to price only the books you hold\n * accounts at. Omit for every book.\n *\n * This narrows the PRICES, never the fair-line anchor:\n * `bookmakers: [\"draftkings\"]` still returns DraftKings EV% measured\n * against Pinnacle. Lines where none of your books quote a price are\n * omitted.\n */\n bookmakers?: string | string[];\n}\n\nexport interface GetEventBestLineOptions {\n /**\n * Optional market filter. Pass a single comma-separated string or an\n * array of market keys (e.g. `[\"pitcher_strikeouts\", \"h2h\"]`). Omit\n * to include every market on the event.\n */\n markets?: string | string[];\n /**\n * Optional bookmaker filter (the-odds-api-compatible). Pass a\n * comma-separated string or an array of book keys (e.g.\n * `[\"draftkings\", \"fanduel\"]`) to shop only the books you hold\n * accounts at. Omit for all comparable books.\n */\n bookmakers?: string | string[];\n /**\n * When true, every price row carries a `link` — that book's public\n * event-page URL, the click-out for \"go bet this\". Books without a\n * verified URL template return null. Links appear on free-tier\n * redacted responses too (navigation isn't the paid data).\n */\n includeLinks?: boolean;\n}\n\nexport interface CalcEventEvOptions {\n /** Market key — h2h / spreads / totals / pitcher_strikeouts / etc. */\n market: string;\n /** Outcome name. Team for h2h/spreads; \"Over\" or \"Under\" for totals/props. */\n name: string;\n /** American odds at your book, e.g. -118 or 145. */\n price: number;\n /** Line/point for spreads, totals, player props. Sign matters for spreads (-1.5 favorite). Omit for h2h. */\n point?: number;\n /** Player name for player-prop markets. Omit for game-line markets. */\n description?: string;\n}\n\nexport interface ExportResolvedPropsOptions {\n /** Sport key (e.g. `\"baseball_mlb\"`). Required. */\n sport: string;\n /** Optional market filter. */\n market?: string;\n /** Optional bookmaker filter. */\n bookmaker?: string;\n /** ISO datetime lower bound on `resolved_at`. */\n since?: string;\n /** ISO datetime upper bound on `resolved_at`. */\n until?: string;\n /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */\n outPath?: string;\n}\n\nexport interface ExportOddsHistoryOptions {\n /** Sport key (e.g. `\"baseball_mlb\"`). Required. */\n sport: string;\n /** Optional market filter. */\n market?: string;\n /** Optional bookmaker filter. */\n bookmaker?: string;\n /** ISO datetime lower bound on `recorded_at`. */\n since?: string;\n /** ISO datetime upper bound on `recorded_at`. */\n until?: string;\n /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */\n outPath?: string;\n}\n\n/**\n * Webhook event types. `steam` = cross-book sharp-money alert.\n * `market_suspended` = a book took a market off the board pregame (one\n * delivery per (book, event, player) withdrawal, with `books_agreeing`).\n */\nexport type WebhookEventType = \"line_movement\" | \"resolution\" | \"steam\" | \"market_suspended\";\n\nexport interface CreateWebhookOptions {\n /** HTTPS endpoint to receive POSTed events. Required. */\n url: string;\n /** Event types to subscribe to. Default: all. */\n events?: WebhookEventType[];\n filterSportKey?: string;\n filterEventId?: number;\n filterMarketKey?: string;\n filterPlayerName?: string;\n /**\n * Comma-separated book keys, same vocabulary as the `?bookmakers=`\n * query param (e.g. \"draftkings,fanduel\"). Unset = all books; unknown\n * keys match nothing. Applies to line_movement, resolution and\n * market_suspended; steam is cross-book and unaffected.\n */\n filterBookmakerKey?: string;\n /** Minimum % change in American odds to fire a line_movement. Point-only shifts always pass. */\n minPriceChangePct?: number;\n /** Minimum 0-100 steam score to fire a `steam` event. Null = detector's global floor. */\n minSteamScore?: number;\n /**\n * `market_suspended` only: how many books must have pulled the same\n * player/market on the same event before you are told. Unset/1 = every\n * drop (right if you price off one book); 3+ = corroborated late\n * scratches only. Every payload carries `books_agreeing` regardless.\n */\n minBooksAgreeing?: number;\n /**\n * Batched delivery opt-in (1-500): up to N events per POST as a signed\n * envelope `{\"batch\": true, \"event_type\": ..., \"count\": N, \"events\":\n * [{\"delivery_id\": ..., \"data\": <per-event payload>}, ...]}` with an\n * `X-PropLine-Batch` header. Strongly recommended for high-volume\n * subscriptions — one POST per event caps your delivery rate at your\n * endpoint's response time. 0 reverts to per-event. JSON format only.\n */\n batchMax?: number;\n}\n\nexport interface UpdateWebhookOptions {\n url?: string;\n events?: WebhookEventType[];\n filterSportKey?: string;\n filterEventId?: number;\n filterMarketKey?: string;\n filterPlayerName?: string;\n /**\n * Comma-separated book keys, same vocabulary as the `?bookmakers=`\n * query param (e.g. \"draftkings,fanduel\"). Unset = all books; unknown\n * keys match nothing. Applies to line_movement, resolution and\n * market_suspended; steam is cross-book and unaffected.\n */\n filterBookmakerKey?: string;\n minPriceChangePct?: number;\n minSteamScore?: number;\n minBooksAgreeing?: number;\n /** Batched delivery (see CreateWebhookOptions.batchMax). 0 = per-event. */\n batchMax?: number;\n active?: boolean;\n}\n\nexport interface ListWebhookDeliveriesOptions {\n /** Max deliveries to return. Default 50, max 200. */\n limit?: number;\n /**\n * Page backwards: pass the smallest `id` from the previous page to get\n * the next-older page. Pages are newest-first; a page shorter than\n * `limit` is the last one.\n */\n beforeId?: number;\n}\n\nexport interface ReplayWebhookEventsOptions {\n /**\n * Read events after this cursor — the highest `X-PropLine-Sequence` you\n * have processed. Defaults to 0 (from the oldest retained event).\n */\n sinceSeq?: number;\n /** Max events per page. Default 100, max 500. */\n limit?: number;\n}\n\nexport interface StreamOptions {\n /** Subscription to stream. Must be transport=\"websocket\". */\n webhookId: number;\n /** Resume point — the last `seq` you processed. Default 0. */\n sinceSeq?: number;\n /** Auto-reconnect and resume from the last seq. Default true. */\n reconnect?: boolean;\n /** Override the websocket origin (default: derived from baseUrl). */\n wsUrl?: string;\n /** Called on every successful handshake with the `ready` frame. */\n onReady?: (ready: ReplayPage) => void;\n /**\n * Called when the server reports events after your cursor have aged out of\n * retention. This is the one case the stream cannot make you whole —\n * resync from the REST endpoints.\n */\n onTruncated?: (ready: ReplayPage) => void;\n}\n\nexport interface VerifySignatureOptions {\n /** Webhook signing secret (returned once from `createWebhook`). */\n secret: string;\n /** Value of the `X-PropLine-Timestamp` header. */\n timestamp: string;\n /** Raw request body. */\n body: Uint8Array | Buffer | string;\n /** Value of the `X-PropLine-Signature` header. */\n signature: string;\n}\n\n/**\n * Live daily-quota state, parsed from the `X-Daily-*` headers the API\n * returns on every authenticated response.\n */\nexport interface QuotaStatus {\n /** Your tier's daily request cap. */\n limit: number;\n /** Requests used today (including the request that produced this). */\n used: number;\n /** Requests left before the cap. */\n remaining: number;\n /** Unix seconds when the quota resets (00:00 UTC — a hard reset, not a rolling window). */\n resetEpoch: number;\n /** Quota reset time as a `Date`. */\n resetAt: Date;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.prop-line.com/v1\";\n\n// Streaming origin. Separate Fly app, separate machines — see stream().\nconst DEFAULT_WS_URL = \"wss://ws.prop-line.com\";\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Client for the PropLine player props API.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * const client = new PropLine(\"your_api_key\");\n * const sports = await client.getSports();\n * ```\n */\nexport class PropLine {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n /**\n * Daily-quota state from the most recent API response, or `null` before\n * the first request. Updated on every call (including 429s):\n *\n * ```ts\n * await client.getSports();\n * console.log(client.lastQuota?.remaining); // 999\n * ```\n */\n lastQuota: QuotaStatus | null = null;\n private readonly _fetch: typeof fetch;\n\n constructor(apiKey: string, options: PropLineOptions = {}) {\n if (!apiKey) {\n throw new Error(\"PropLine: apiKey is required\");\n }\n this.apiKey = apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this._fetch = options.fetch ?? globalThis.fetch;\n if (!this._fetch) {\n throw new Error(\n \"PropLine: global fetch is unavailable. Use Node 18+ or pass options.fetch.\"\n );\n }\n }\n\n // ------------------------------------------------------------------\n // Internals\n // ------------------------------------------------------------------\n\n private _buildUrl(path: string, params?: Record<string, string | number | undefined>): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null) {\n url.searchParams.set(k, String(v));\n }\n }\n }\n return url.toString();\n }\n\n /**\n * Record the X-Daily-* quota headers when present (absent on\n * unauthenticated errors, e.g. an invalid key's 401).\n */\n private _captureQuota(resp: Response): void {\n const limit = Number(resp.headers.get(\"X-Daily-Limit\"));\n const used = Number(resp.headers.get(\"X-Daily-Used\"));\n const remaining = Number(resp.headers.get(\"X-Daily-Remaining\"));\n const resetEpoch = Number(resp.headers.get(\"X-Daily-Reset\"));\n if (\n resp.headers.has(\"X-Daily-Limit\") &&\n Number.isFinite(limit) &&\n Number.isFinite(used) &&\n Number.isFinite(remaining) &&\n Number.isFinite(resetEpoch)\n ) {\n this.lastQuota = {\n limit,\n used,\n remaining,\n resetEpoch,\n resetAt: new Date(resetEpoch * 1000),\n };\n }\n }\n\n private async _request<T>(\n method: string,\n path: string,\n init: { params?: Record<string, string | number | undefined>; body?: unknown } = {}\n ): Promise<T> {\n const url = this._buildUrl(path, init.params);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method,\n headers: {\n \"X-API-Key\": this.apiKey,\n ...(init.body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n const d = await readDetail(resp, \"Invalid API key\");\n throw new AuthError(d.message, d.info);\n }\n if (resp.status === 429) {\n const d = await readDetail(resp, \"Rate limit exceeded\");\n throw new RateLimitError(d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n if (resp.status === 204) {\n return undefined as T;\n }\n return (await resp.json()) as T;\n }\n\n // ------------------------------------------------------------------\n // Public API\n // ------------------------------------------------------------------\n\n /** List all available sports. */\n getSports(): Promise<Sport[]> {\n return this._request<Sport[]>(\"GET\", \"/sports\");\n }\n\n /** List upcoming events for a sport. */\n getEvents(sport: string): Promise<PropLineEvent[]> {\n return this._request<PropLineEvent[]>(\"GET\", `/sports/${encodeURIComponent(sport)}/events`);\n }\n\n /**\n * Get current odds. With `eventId`, returns single-event odds (including\n * player props). Without, returns bulk odds for all upcoming events.\n *\n * Each response carries a `bookmakers` array — iterate it to compare\n * lines across Bovada, DraftKings, FanDuel, Pinnacle, Unibet, and\n * PrizePicks (coverage varies by sport).\n */\n getOdds(\n sport: string,\n options: GetOddsOptions & { eventId: number | string }\n ): Promise<OddsResponse>;\n getOdds(\n sport: string,\n options?: Omit<GetOddsOptions, \"eventId\"> & { eventId?: undefined }\n ): Promise<OddsResponse[]>;\n getOdds(\n sport: string,\n options: GetOddsOptions = {}\n ): Promise<OddsResponse | OddsResponse[]> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n const periodParam = _periodParam(options.period);\n if (periodParam !== undefined) params.period = periodParam;\n const bookmakersParam = _bookmakersParam(options.bookmakers);\n if (bookmakersParam !== undefined) params.bookmakers = bookmakersParam;\n if (options.includeLinks) params.includeLinks = \"true\";\n if (options.includeBookIds) params.includeBookIds = \"true\";\n const sp = encodeURIComponent(sport);\n if (options.eventId !== undefined) {\n return this._request<OddsResponse>(\n \"GET\",\n `/sports/${sp}/events/${encodeURIComponent(String(options.eventId))}/odds`,\n { params }\n );\n }\n return this._request<OddsResponse[]>(\"GET\", `/sports/${sp}/odds`, { params });\n }\n\n /** List the available market types for an event. */\n getMarkets(sport: string, eventId: number | string): Promise<MarketSummary[]> {\n return this._request<MarketSummary[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/markets`\n );\n }\n\n /**\n * Get historical odds movement for an event.\n *\n * Hobby+: full snapshots. Free tier: redacted (snapshot counts only).\n *\n * Supports period-historical filters:\n * - `from` / `to` — absolute ISO timestamps\n * - `relativeFrom` / `relativeTo` — offsets to commence_time (\"-3h\", \"-30m\", \"0\")\n * - `interval` — downsample to a fixed bucket size\n * - `changesOnly` — drop unchanged adjacent snapshots\n */\n getOddsHistory(\n sport: string,\n eventId: number | string,\n options: GetOddsHistoryOptions = {}\n ): Promise<OddsHistoryResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n if (options.from !== undefined) params.from = options.from;\n if (options.to !== undefined) params.to = options.to;\n if (options.relativeFrom !== undefined) params.relative_from = options.relativeFrom;\n if (options.relativeTo !== undefined) params.relative_to = options.relativeTo;\n if (options.interval !== undefined) params.interval = options.interval;\n if (options.changesOnly) params.changes_only = \"true\";\n const periodParam2 = _periodParam(options.period);\n if (periodParam2 !== undefined) params.period = periodParam2;\n const bookmakersParam2 = _bookmakersParam(options.bookmakers);\n if (bookmakersParam2 !== undefined) params.bookmakers = bookmakersParam2;\n return this._request<OddsHistoryResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/odds/history`,\n { params }\n );\n }\n\n /**\n * Get the opening AND closing line per `(book, market, outcome)` for an\n * event. Closing is the last snapshot at or before commence_time\n * (`price` / `point` / `closing_at`); opening is the first snapshot in\n * the same 14-day pre-kickoff window (`opening_price` / `opening_point`\n * / `opening_at`). Canonical CLV helper: replaces \"fetch full history →\n * find the first and last pre-game rows\" with one call.\n *\n * Compare the *points* as well as the prices — on spreads and totals\n * the number moves as much as the price, so a price-only comparison\n * mis-measures those markets.\n *\n * Hobby+: full data. Free tier: redacted.\n */\n getOddsClosing(\n sport: string,\n eventId: number | string,\n options: GetOddsClosingOptions = {}\n ): Promise<OddsClosingResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n const periodParam3 = _periodParam(options.period);\n if (periodParam3 !== undefined) params.period = periodParam3;\n const bookmakersParam3 = _bookmakersParam(options.bookmakers);\n if (bookmakersParam3 !== undefined) params.bookmakers = bookmakersParam3;\n return this._request<OddsClosingResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/odds/closing`,\n { params }\n );\n }\n\n /** Get game scores and status (free tier). */\n getScores(sport: string, options: GetScoresOptions = {}): Promise<ScoreEvent[]> {\n return this._request<ScoreEvent[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/scores`,\n { params: { days_from: options.daysFrom ?? 3 } }\n );\n }\n\n /**\n * PrizePicks Power/Flex entry payout schedule (2-6 legs) plus the per-leg\n * breakeven win probability for each play. Pass `legWinProb` to also get\n * `expected_return` (per $1) and `is_plus_ev` per play — turning a slip\n * into the hit rate it actually needs to clear.\n *\n * These are PrizePicks's *standard* published payouts; demon/goblin per-pick\n * modifiers aren't in PrizePicks's feed, so they're not reflected. Breakeven\n * assumes independent legs. See the `disclaimer` field on the response.\n */\n getDfsPayouts(\n options: GetDfsPayoutsOptions = {}\n ): Promise<DfsPayoutsResponse> {\n const params: Record<string, string | number> = {\n platform: options.platform ?? \"prizepicks\",\n };\n if (options.legWinProb !== undefined) params.leg_win_prob = options.legWinProb;\n return this._request<DfsPayoutsResponse>(\"GET\", \"/dfs/payouts\", { params });\n }\n\n /**\n * Synthetic MLB Grand Salami for a given UTC date — total runs scored\n * across every MLB game on the slate, plus each book's implied Grand\n * Salami line (median of per-game primary totals across our MLB books).\n *\n * No retail sportsbook quotes this as a single market, so historical\n * cross-book Grand Salami data isn't available elsewhere. Free tier;\n * defaults to today (UTC).\n */\n getMlbGrandSalami(\n options: GetMlbGrandSalamiOptions = {}\n ): Promise<MlbGrandSalamiResponse> {\n const params: Record<string, string> = {};\n if (options.date) params.date = options.date;\n return this._request<MlbGrandSalamiResponse>(\n \"GET\",\n \"/sports/baseball_mlb/grand-salami\",\n { params }\n );\n }\n\n /**\n * Synthetic NHL Daily Goals Total for a given UTC date — total goals\n * scored (incl. OT/SO) across every NHL game on the slate, plus each\n * book's implied Daily Goals Total line (median of per-game primary\n * totals across our NHL books).\n *\n * Hockey's equivalent of the MLB Grand Salami. No retail sportsbook\n * quotes this as a single market. Free tier; defaults to today (UTC).\n */\n getNhlDailyGoalsTotal(\n options: GetNhlDailyGoalsTotalOptions = {}\n ): Promise<NhlDailyGoalsTotalResponse> {\n const params: Record<string, string> = {};\n if (options.date) params.date = options.date;\n return this._request<NhlDailyGoalsTotalResponse>(\n \"GET\",\n \"/sports/hockey_nhl/daily-goals-total\",\n { params }\n );\n }\n\n /**\n * Factual volume of graded player props over the last N days (free tier).\n *\n * Aggregated counts only — a coverage proof (every outcome counted was\n * graded against the real box score), never a profitability claim.\n *\n * @param days Look-back window, 1-90 (default 30).\n */\n getResolutionSummary(days = 30): Promise<ResolutionSummary> {\n return this._request<ResolutionSummary>(\n \"GET\",\n \"/markets/resolution-summary\",\n { params: { days: String(days) } }\n );\n }\n\n /**\n * Get raw player/team box-score stats (book-agnostic, free tier).\n *\n * Returns actual stat values decoupled from any bookmaker's lines.\n *\n * Live during games for major US sports (MLB + WNBA now; NFL, NCAAF,\n * NBA, NHL at season start): while the event's status is \"in_progress\",\n * stats refresh roughly every 90 seconds with cumulative in-game values —\n * treat them as partial until status flips to \"final\". Other sports\n * populate stats at game completion.\n */\n getStats(\n sport: string,\n eventId: number | string,\n options: GetStatsOptions = {}\n ): Promise<StatsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.statType?.length) {\n params.stat_type = options.statType.join(\",\");\n }\n return this._request<StatsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/stats`,\n { params }\n );\n }\n\n /**\n * Get game context — the conditions a prop settles under.\n *\n * For MLB: probable starting pitchers and their throwing hand\n * (`home_probable_pitcher_hand` / `away_probable_pitcher_hand`, \"L\"/\"R\"/\"S\"\n * — platoon-split context for every batter prop), a confirmed-lineup flag,\n * the home-plate umpire, and first-pitch weather (outdoor / open-roof\n * venues; indoor venues return `weather: null` with `is_indoor: true`).\n * For NFL & NCAAF: the venue and kickoff weather (pitcher/umpire/lineup\n * fields are null for football). The same block is embedded in\n * {@link getResults}, so every graded prop carries its conditions — unique\n * to PropLine. Free tier. Rejects with a 404 when no context is on file\n * for the event yet.\n */\n getContext(\n sport: string,\n eventId: number | string\n ): Promise<ContextResponse> {\n return this._request<ContextResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/context`\n );\n }\n\n /**\n * Get line movement + steam detection from the snapshot tick history.\n *\n * Per (book, market, outcome): opening line, latest line, signed\n * implied-probability shift, point shift, direction. The `steam` array\n * flags outcomes multiple books moved the same direction — the\n * sharp-money signal across every book PropLine polls. When a book moves\n * the line itself, that outcome's `prob_shift` is null and `direction` is\n * `\"line_moved\"` (excluded from the steam signal). Unique to PropLine.\n * Hobby+ full; free tier redacted.\n */\n getMovement(\n sport: string,\n eventId: number | string,\n options: GetMovementOptions = {}\n ): Promise<MovementResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n params.period = _periodParam(options.period);\n params.bookmakers = _bookmakersParam(options.bookmakers);\n return this._request<MovementResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/movement`,\n { params }\n );\n }\n\n /**\n * Get resolved prop outcomes with actual player stats.\n *\n * Pro tier: full data. Free tier: redacted (resolution + actual nulled).\n */\n getResults(\n sport: string,\n eventId: number | string,\n options: GetResultsOptions = {}\n ): Promise<ResultsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n return this._request<ResultsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/results`,\n { params }\n );\n }\n\n /**\n * Recent resolved prop history for a player on a market.\n *\n * One entry per (event, bookmaker) pair. Pro: full. Free: redacted.\n */\n getPlayerHistory(\n sport: string,\n playerName: string,\n options: GetPlayerHistoryOptions\n ): Promise<PlayerHistoryResponse> {\n const params: Record<string, string | number | undefined> = {\n market: options.market,\n limit: options.limit ?? 20,\n };\n if (options.bookmaker) {\n params.bookmaker = options.bookmaker;\n }\n return this._request<PlayerHistoryResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/players/${encodeURIComponent(playerName)}/history`,\n { params }\n );\n }\n\n /**\n * A player's game log — recent games with every raw box-score stat.\n *\n * One call replaces one request per event, so L5/L10/L20, season splits,\n * charts and head-to-head can all be built from the raw rows. Free tier.\n *\n * Reads the RAW-STATS archive, not graded-prop history: it covers every\n * game with a box score on file, including games no sportsbook priced, so\n * a \"last 10 games\" window here really is the last 10 games — unlike one\n * built from {@link getPlayerHistory} or {@link getPlayerTrends}. Carries\n * no line, price or grade.\n *\n * @example\n * ```ts\n * const log = await client.getPlayerGames(\"baseball_mlb\", \"Aaron Judge\", { limit: 10 });\n * const hits = log.games.reduce((n, g) => n + (g.stats.hits ?? 0), 0);\n *\n * // Last 5 meetings with Boston — not the Boston games among his last 5.\n * const h2h = await client.getPlayerGames(\"baseball_mlb\", \"Aaron Judge\", {\n * limit: 5,\n * opponent: \"BOS\",\n * });\n * ```\n */\n getPlayerGames(\n sportKey: string,\n playerName: string,\n options: GetPlayerGamesOptions = {}\n ): Promise<PlayerGameLog> {\n const params: Record<string, string | number | undefined> = {\n limit: options.limit ?? 20,\n };\n if (options.opponent) {\n params.opponent = options.opponent;\n }\n if (options.statType) {\n params.stat_type = Array.isArray(options.statType)\n ? options.statType.join(\",\")\n : options.statType;\n }\n return this._request<PlayerGameLog>(\n \"GET\",\n `/sports/${encodeURIComponent(sportKey)}/players/${encodeURIComponent(playerName)}/games`,\n { params }\n );\n }\n\n /**\n * Rolling hit-rate trends for a player across one or all markets.\n *\n * Returns over/under/push splits over the last 5/10/20/50 graded games,\n * the current streak, and the most recent game per market. Pro: full.\n * Free: redacted.\n */\n getPlayerTrends(\n sportKey: string,\n playerName: string,\n options: GetPlayerTrendsOptions = {}\n ): Promise<PlayerTrends> {\n const params: Record<string, string | undefined> = {};\n if (options.market) {\n params.market = options.market;\n }\n if (options.dfsOddsType) {\n params.dfs_odds_type = options.dfsOddsType;\n }\n return this._request<PlayerTrends>(\n \"GET\",\n `/sports/${encodeURIComponent(sportKey)}/players/${encodeURIComponent(playerName)}/trends`,\n { params }\n );\n }\n\n /**\n * Cross-book +EV analysis for a single event (Pro+ tier).\n *\n * Groups every outcome by (market, player, line) across the books we\n * carry, derives a no-vig fair line from a sharp anchor (Pinnacle\n * preferred, Bovada fallback), and returns EV% per book at the same\n * line. Outcomes are sorted with +EV plays floated to the top.\n *\n * PrizePicks is excluded — its synthetic +100/+100 prices aren't\n * payout odds. Lines without sharp-anchor coverage are dropped.\n *\n * @example\n * ```ts\n * const ev = await client.getEventEv(\"baseball_mlb\", 12345);\n * for (const line of ev.lines) {\n * const plus = line.outcomes.filter(o => o.is_plus_ev);\n * if (plus.length) console.log(line.market_key, line.description, plus);\n * }\n * ```\n */\n /**\n * List futures markets for a sport — championship winner, MVP,\n * division winner, season win totals, etc. Each row is one (futures\n * event, book, market) with every team or player priced. Free tier;\n * aggregated across each book's futures feed (Bovada, FanDuel,\n * DraftKings, and Pinnacle).\n *\n * @example\n * ```ts\n * const futures = await client.getFutures(\"baseball_mlb\");\n * for (const event of futures) {\n * console.log(`${event.title} @ ${event.commence_time}`);\n * for (const m of event.markets) {\n * const top3 = [...m.outcomes].sort((a, b) => a.price - b.price).slice(0, 3);\n * for (const o of top3) console.log(` ${o.name}: ${o.price}`);\n * }\n * }\n * ```\n *\n * @param options.bookmakers Optional book key(s) to restrict the per-book\n * market rows (the-odds-api-compatible; omitted = all books, unknown keys\n * match nothing). A futures event left with no matching market is dropped.\n */\n getFutures(\n sport: string,\n options: { bookmakers?: string | string[] } = {}\n ): Promise<FuturesEvent[]> {\n const params: Record<string, string | undefined> = {};\n const bookmakersParam = _bookmakersParam(options.bookmakers);\n if (bookmakersParam !== undefined) params.bookmakers = bookmakersParam;\n return this._request<FuturesEvent[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/futures`,\n { params }\n );\n }\n\n /**\n * Market-implied consensus projections for a single event.\n *\n * One row per (market, player): the statistical value the betting\n * market collectively implies — the line where the no-vig P(over)\n * crosses 50%, median across contributing sportsbooks. Built for\n * validating your own statistical/fantasy projections against the\n * live market. Market-implied arithmetic over sportsbook prices,\n * never a forecast. DFS pick'em pricing is excluded.\n *\n * Paid tier required (Hobby+); free tier receives the structure with\n * projected values nulled and `redacted: true`.\n *\n * @example\n * ```ts\n * const proj = await client.getEventProjections(\"football_nfl\", 25070);\n * for (const row of proj.projections) {\n * console.log(row.player, row.market_key, row.projected_value);\n * }\n * ```\n */\n getEventProjections(\n sport: string,\n eventId: number | string,\n options: GetEventProjectionsOptions = {}\n ): Promise<EventProjectionsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n return this._request<EventProjectionsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/projections`,\n { params }\n );\n }\n\n getEventEv(\n sport: string,\n eventId: number | string,\n options: GetEventEvOptions = {}\n ): Promise<EventEvResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n if (options.bookmakers) {\n params.bookmakers = Array.isArray(options.bookmakers)\n ? options.bookmakers.join(\",\")\n : options.bookmakers;\n }\n return this._request<EventEvResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/ev`,\n { params }\n );\n }\n\n /**\n * Cross-book best-line lookup for a single event.\n *\n * For each (market, player, line) tuple, returns the single best\n * American price across every book we carry, with the book name\n * attached. Companion to `getEventEv`: best-line tells you which\n * book has the highest payout right now; +EV tells you whether\n * that price beats a sharp no-vig fair line. Most line shoppers\n * want both.\n *\n * PrizePicks is excluded from the comparison — its DFS payout\n * structure (synthetic +100/+100 quotes) isn't directly comparable\n * to traditional sportsbook odds.\n *\n * Hobby tier or higher sees prices. Free tier gets a redacted\n * teaser: the full structure — every line, side, book identity, and\n * the best-first ranking — with every price null, plus\n * `redacted: true` and an `upgrade_url`.\n *\n * @example\n * ```ts\n * const bl = await client.getEventBestLine(\"baseball_mlb\", 12345);\n * for (const line of bl.lines) {\n * for (const [side, info] of Object.entries(line.sides)) {\n * console.log(\n * `${line.description} ${side} ${line.point}: ` +\n * `${info.best.price} @ ${info.best.book_title}`\n * );\n * }\n * }\n * ```\n */\n getEventBestLine(\n sport: string,\n eventId: number | string,\n options: GetEventBestLineOptions = {}\n ): Promise<EventBestLineResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n if (options.bookmakers) {\n params.bookmakers = Array.isArray(options.bookmakers)\n ? options.bookmakers.join(\",\")\n : options.bookmakers;\n }\n if (options.includeLinks) params.includeLinks = \"true\";\n return this._request<EventBestLineResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/best-line`,\n { params }\n );\n }\n\n /**\n * Calculate EV% for a user-supplied price against the event's\n * no-vig fair anchor. Useful for books PropLine doesn't carry —\n * Caesars, BetMGM, Fanatics, BetUS, Hard Rock — where you have a\n * price in hand and want to know if it's +EV against the sharp\n * consensus we do carry.\n *\n * Same fair-line math as `getEventEv` (Pinnacle-preferred anchor,\n * no-vig devigging) but takes one user price as input. Pro tier.\n *\n * @example\n * ```ts\n * const r = await client.calcEventEv(\"baseball_mlb\", 12614, {\n * market: \"h2h\",\n * name: \"Pittsburgh Pirates\",\n * price: -118,\n * });\n * console.log(`EV ${r.ev_pct}% fair=${r.fair_prob}`);\n * ```\n */\n calcEventEv(\n sport: string,\n eventId: number | string,\n options: CalcEventEvOptions\n ): Promise<EventEvCalcResponse> {\n const params: Record<string, string | number | undefined> = {\n market: options.market,\n name: options.name,\n price: options.price,\n };\n if (options.point !== undefined) params.point = options.point;\n if (options.description) params.description = options.description;\n return this._request<EventEvCalcResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/ev/calc`,\n { params }\n );\n }\n\n /**\n * Bulk CSV export of resolved prop outcomes (Pro+ tier).\n *\n * If `outPath` is provided, streams the CSV to disk and resolves to the\n * path. Otherwise resolves to the full CSV bytes as a `Uint8Array`.\n *\n * @example\n * ```ts\n * await client.exportResolvedProps({\n * sport: \"baseball_mlb\",\n * market: \"pitcher_strikeouts\",\n * since: \"2026-04-01T00:00:00Z\",\n * outPath: \"./mlb-strikeouts.csv\",\n * });\n * ```\n */\n async exportResolvedProps(\n options: ExportResolvedPropsOptions & { outPath: string }\n ): Promise<string>;\n async exportResolvedProps(\n options: ExportResolvedPropsOptions & { outPath?: undefined }\n ): Promise<Uint8Array>;\n async exportResolvedProps(\n options: ExportResolvedPropsOptions\n ): Promise<string | Uint8Array> {\n const params: Record<string, string | undefined> = { sport: options.sport };\n if (options.market) params.market = options.market;\n if (options.bookmaker) params.bookmaker = options.bookmaker;\n if (options.since) params.since = options.since;\n if (options.until) params.until = options.until;\n\n const url = this._buildUrl(\"/exports/resolved-props\", params);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method: \"GET\",\n headers: { \"X-API-Key\": this.apiKey },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n throw new AuthError();\n }\n if (resp.status === 403) {\n const d = await readDetail(resp, \"Pro tier required\");\n throw new PropLineError(403, d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n const buf = new Uint8Array(await resp.arrayBuffer());\n if (options.outPath) {\n await writeFile(options.outPath, buf);\n return options.outPath;\n }\n return buf;\n }\n\n /**\n * Bulk CSV export of the full line-movement time-series.\n *\n * One row per (outcome, snapshot): every recorded odds snapshot (price +\n * line, per book, including period markets), not just the closing line.\n * This is the raw tick history no subscription tier can pull in bulk —\n * Pro/Streaming get per-event {@link getOddsHistory} only; this bulk\n * firehose is exclusive to the one-time Historical Backfill pass and\n * Enterprise.\n *\n * A full archive runs to gigabytes per sport — page month by month with\n * `since`/`until`. If `outPath` is provided, streams to disk and resolves\n * to the path; otherwise resolves to the CSV bytes as a `Uint8Array`.\n *\n * @example\n * ```ts\n * await client.exportOddsHistory({\n * sport: \"baseball_mlb\",\n * since: \"2026-04-01T00:00:00Z\",\n * until: \"2026-05-01T00:00:00Z\",\n * outPath: \"./mlb-line-history-apr.csv\",\n * });\n * ```\n */\n async exportOddsHistory(\n options: ExportOddsHistoryOptions & { outPath: string }\n ): Promise<string>;\n async exportOddsHistory(\n options: ExportOddsHistoryOptions & { outPath?: undefined }\n ): Promise<Uint8Array>;\n async exportOddsHistory(\n options: ExportOddsHistoryOptions\n ): Promise<string | Uint8Array> {\n const params: Record<string, string | undefined> = { sport: options.sport };\n if (options.market) params.market = options.market;\n if (options.bookmaker) params.bookmaker = options.bookmaker;\n if (options.since) params.since = options.since;\n if (options.until) params.until = options.until;\n\n const url = this._buildUrl(\"/exports/odds-history\", params);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method: \"GET\",\n headers: { \"X-API-Key\": this.apiKey },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n throw new AuthError();\n }\n if (resp.status === 403) {\n const d = await readDetail(\n resp,\n \"Historical Backfill pass or Enterprise required\"\n );\n throw new PropLineError(403, d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n const buf = new Uint8Array(await resp.arrayBuffer());\n if (options.outPath) {\n await writeFile(options.outPath, buf);\n return options.outPath;\n }\n return buf;\n }\n\n // ------------------------------------------------------------------\n // Webhooks (Streaming tier)\n // ------------------------------------------------------------------\n\n /**\n * Register a webhook subscription. Streaming tier only.\n *\n * The returned object includes the full signing `secret` — this is the\n * ONLY time it's revealed. Store it securely.\n */\n createWebhook(options: CreateWebhookOptions): Promise<Webhook> {\n return this._request<Webhook>(\"POST\", \"/webhooks\", {\n body: webhookBody(options),\n });\n }\n\n /** List your webhook subscriptions. Secrets are masked. */\n listWebhooks(): Promise<Webhook[]> {\n return this._request<Webhook[]>(\"GET\", \"/webhooks\");\n }\n\n /** Get a single webhook subscription. Secret is masked. */\n getWebhook(webhookId: number): Promise<Webhook> {\n return this._request<Webhook>(\"GET\", `/webhooks/${webhookId}`);\n }\n\n /** Update fields on a webhook. Only supplied fields are changed. */\n updateWebhook(webhookId: number, options: UpdateWebhookOptions): Promise<Webhook> {\n return this._request<Webhook>(\"PATCH\", `/webhooks/${webhookId}`, {\n body: webhookBody(options),\n });\n }\n\n /** Delete a webhook (cascades its delivery history). */\n deleteWebhook(webhookId: number): Promise<{ ok: boolean } | unknown> {\n return this._request(\"DELETE\", `/webhooks/${webhookId}`);\n }\n\n /** Queue a sample `test` payload to the webhook's URL. */\n testWebhook(webhookId: number): Promise<unknown> {\n return this._request(\"POST\", `/webhooks/${webhookId}/test`);\n }\n\n /** Last 50 (default) delivery attempts for a webhook. */\n listWebhookDeliveries(\n webhookId: number,\n options: ListWebhookDeliveriesOptions = {}\n ): Promise<WebhookDelivery[]> {\n return this._request<WebhookDelivery[]>(\n \"GET\",\n `/webhooks/${webhookId}/deliveries`,\n { params: { limit: options.limit ?? 50, before_id: options.beforeId } }\n );\n }\n\n /**\n * Re-read this subscription's events in order, from a cursor.\n *\n * Answers \"my endpoint was down — what did I miss?\". Every delivery carries\n * an `X-PropLine-Sequence` header: a counter monotonic *within your\n * subscription*. Store the highest one you processed and pass it as\n * `sinceSeq`.\n *\n * Do NOT use `X-PropLine-Delivery` as the cursor — that id is global across\n * every subscription, so its gaps are other customers' traffic.\n *\n * Events come back oldest-first (the opposite of `listWebhookDeliveries`,\n * which is a newest-first debugging log). Page by passing `next_seq` back\n * as `sinceSeq` while `has_more` is true.\n *\n * **Check `truncated`.** True means events after your cursor have aged out\n * of retention (2 days, max 5,000 deliveries per subscription) and are gone\n * — resync from the REST endpoints instead of assuming you are current.\n *\n * Does not count against your daily request quota.\n */\n replayWebhookEvents(\n webhookId: number,\n options: ReplayWebhookEventsOptions = {}\n ): Promise<ReplayPage> {\n return this._request<ReplayPage>(\n \"GET\",\n `/webhooks/${webhookId}/replay`,\n { params: { since_seq: options.sinceSeq ?? 0, limit: options.limit ?? 100 } }\n );\n }\n\n /**\n * Stream a websocket subscription as an async iterable.\n *\n * ```ts\n * for await (const ev of client.stream({ webhookId: 12, sinceSeq: 4180 })) {\n * console.log(ev.seq, ev.event_type, ev.data);\n * }\n * ```\n *\n * The subscription must have been created with `transport: \"websocket\"`.\n * Same events, same filters, same `seq` as an HTTP webhook — one\n * subscription, different transport.\n *\n * **Reconnects automatically and resumes from the last `seq` it saw**, which\n * is the whole point of the sequence: a dropped connection does not become a\n * gap in your data. Set `reconnect: false` to get a single connection that\n * ends when the socket closes.\n *\n * If the server reports `truncated` — events after your cursor aged out of\n * retention and are gone — `onTruncated` fires. Handle it: that is the one\n * case where the stream cannot make you whole and you should resync from the\n * REST endpoints.\n */\n async *stream(options: StreamOptions): AsyncGenerator<ReplayEvent, void, void> {\n // ⚠️ Streaming has its OWN origin and does NOT derive from baseUrl.\n // Deriving it (the first cut of this method did) sends the socket to\n // api.prop-line.com — which serves /v1/stream too, from the same ASGI\n // app, so it WORKS and nothing complains. It just parks a persistent\n // connection on the REST tier's event loop, which is precisely what the\n // separate websocket tier exists to prevent. Caught 2026-08-31 by reading\n // the machine id in ws_connections and finding an app-tier machine.\n // Override with `wsUrl` only for self-hosted or local development.\n const wsBase = (options.wsUrl ?? DEFAULT_WS_URL)\n .replace(/^http:/, \"ws:\")\n .replace(/^https:/, \"wss:\")\n .replace(/\\/v1\\/?$/, \"\")\n .replace(/\\/$/, \"\");\n const url = `${wsBase}/v1/stream`;\n let cursor = options.sinceSeq ?? 0;\n let attempt = 0;\n\n for (;;) {\n const queue: ReplayEvent[] = [];\n let notify: (() => void) | null = null;\n let closed: Error | null = null;\n let opened = false;\n\n const ws = new WebSocket(url);\n const wake = () => { const n = notify; notify = null; n?.(); };\n\n ws.addEventListener(\"open\", () => {\n opened = true;\n ws.send(JSON.stringify({\n type: \"auth\",\n api_key: this.apiKey,\n webhook_id: options.webhookId,\n since_seq: cursor,\n }));\n });\n ws.addEventListener(\"message\", (e: MessageEvent) => {\n let msg: Record<string, unknown>;\n try { msg = JSON.parse(String(e.data)); } catch { return; }\n if (msg.type === \"ready\") {\n attempt = 0; // a successful handshake resets backoff\n if (msg.truncated) options.onTruncated?.(msg as unknown as ReplayPage);\n options.onReady?.(msg as unknown as ReplayPage);\n } else if (msg.type === \"event\") {\n queue.push(msg as unknown as ReplayEvent);\n wake();\n }\n // \"ping\" needs no reply — it exists to keep idle proxies from closing.\n });\n ws.addEventListener(\"close\", (e: CloseEvent) => {\n // 4401/4403/4404/4400 are terminal: retrying cannot fix a bad key, a\n // tier without access, or a subscription that is not yours. Only\n // transport failures and 4429 are worth reconnecting for.\n const terminal = [4400, 4401, 4403, 4404].includes(e.code);\n closed = new PropLineError(\n e.code,\n `stream closed${e.reason ? `: ${e.reason}` : \"\"}`,\n );\n (closed as PropLineError & { terminal?: boolean }).terminal = terminal;\n wake();\n });\n ws.addEventListener(\"error\", () => {\n if (!closed) closed = new PropLineError(0, \"stream connection error\");\n wake();\n });\n\n try {\n for (;;) {\n while (queue.length) {\n const ev = queue.shift()!;\n cursor = ev.seq; // advance BEFORE yielding, so a\n yield ev; // consumer `break` still resumes right\n }\n if (closed) break;\n await new Promise<void>((r) => { notify = r; });\n }\n } finally {\n try { ws.close(); } catch { /* already closed */ }\n }\n\n const err = closed as (PropLineError & { terminal?: boolean }) | null;\n if (err?.terminal) throw err;\n if (options.reconnect === false) {\n if (err && !opened) throw err;\n return;\n }\n // Capped exponential backoff. Without the cap a long outage would have\n // clients reconnecting hours apart; without backoff they would stampede.\n const delayMs = Math.min(30_000, 500 * 2 ** attempt++);\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n\n /**\n * Grade placed bets against their closing lines (CLV).\n *\n * Closing line value is the only durable proxy for whether a bettor has\n * edge: did the price you took beat the number the market settled on?\n * Send the bets you actually placed; each comes back with its closing\n * price, the de-vigged closing fair probability, CLV, and — once the\n * game settles — the graded result and actual stat value.\n *\n * Stateless: nothing is stored server-side.\n *\n * **Two CLV numbers are returned deliberately.** `clv_pct` is\n * price-vs-price — familiar and quotable, but vig-blind, so it flatters\n * a bet taken on the juicy side of a wide market. `ev_vs_close_pct`\n * scores your price against the DE-VIGGED close and is the honest one;\n * on a real bet the two came out +6.52% and +0.08%.\n *\n * The de-vig anchors to the **sharpest book quoting that line at close**\n * (`fair_source`), not the book you bet at — de-vigging your own book\n * always returns a negative number, because you paid its hold.\n *\n * Bets whose event has not started carry `closing_is_final: false`, land\n * in `summary.pending`, and are excluded from the summary averages:\n * before kickoff the \"closing\" price is just the latest price.\n *\n * Matching is fail-closed — a bet that cannot be pinned to exactly one\n * stored outcome returns `matched: false` with an `unmatched_reason`\n * rather than a confident wrong match. Max 500 bets per request.\n *\n * Hobby+ required; free tier receives the structure with numbers nulled.\n *\n * @example\n * const res = await client.gradeClv([{\n * ref: \"b1\",\n * sport_key: \"baseball_mlb\",\n * event_id: 150791,\n * market: \"batter_hits_runs_rbis\",\n * bookmaker: \"lowvig\",\n * selection: \"Drake Baldwin\",\n * side: \"Under\",\n * point: 0.5,\n * price: 145,\n * stake: 1,\n * }]);\n * console.log(res.summary.avg_ev_vs_close_pct);\n */\n gradeClv(bets: ClvBetInput[]): Promise<ClvGradeResponse> {\n return this._request<ClvGradeResponse>(\"POST\", \"/clv/grade\", { body: bets });\n }\n\n /**\n * Price a same-game parlay at the book's own correlated odds.\n *\n * Send two to ten legs from ONE event and get back the book's own price\n * for that exact slip — what a FanDuel customer would be offered for it\n * at that moment, not a model of it — beside `independent_price` (the\n * product of the live single-leg prices) and `correlation_factor`\n * (their ratio: the correlation the book is charging, below 1, or\n * paying, above 1, for). Measured live: Cardinals ML +205 × Freddie\n * Freeman to record a hit -260 → SGP +592 against an independent +322.\n *\n * Book-native. `bookmaker` is \"fanduel\" (its own betslip pricer) or\n * \"betonlineag\" / \"lowvig\" (the Sportcast engine both Chico brands embed,\n * same builder price); an unsupported value is a 422.\n *\n * Legs are named exactly as `/odds` names an outcome (market, name,\n * description, point, period), or by `book_outcome_id` from\n * `includeBookIds: true`. Matching is fail-closed — a leg that does not\n * pin to exactly one stored outcome is a 422 `leg_unmatched` naming the\n * leg. `quoted: false` means the book will not offer that combination as\n * a same-game parlay; refused legs carry the book's own `failure_code`.\n * Quotes for an identical slip are shared for 15 seconds.\n *\n * Hobby+ required; free tier receives the matched legs with every price\n * nulled and never triggers a book call.\n *\n * @example\n * const q = await client.priceSgp(\"baseball_mlb\", 150791, [\n * { market: \"h2h\", name: \"St. Louis Cardinals\" },\n * { market: \"batter_1plus_hits\", name: \"Freddie Freeman\", description: \"Freddie Freeman\" },\n * ]);\n * console.log(q.sgp_price, q.independent_price, q.correlation_factor);\n */\n priceSgp(\n sportKey: string,\n eventId: number | string,\n legs: SgpLegInput[],\n bookmaker = \"fanduel\",\n ): Promise<SgpQuoteResponse> {\n return this._request<SgpQuoteResponse>(\n \"POST\",\n `/sports/${encodeURIComponent(sportKey)}/events/${encodeURIComponent(String(eventId))}/sgp`,\n { body: { bookmaker, legs } },\n );\n }\n\n /**\n * Verify that an inbound webhook delivery was signed by PropLine.\n *\n * Compares HMAC-SHA256(secret, `${timestamp}.` + body) against the\n * `X-PropLine-Signature` header in constant time.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * app.post(\"/hooks/propline\", express.raw({ type: \"*\\/*\" }), (req, res) => {\n * const ok = PropLine.verifySignature({\n * secret: process.env.WEBHOOK_SECRET!,\n * timestamp: req.header(\"X-PropLine-Timestamp\")!,\n * body: req.body, // raw Buffer\n * signature: req.header(\"X-PropLine-Signature\")!,\n * });\n * if (!ok) return res.status(401).end();\n * // ...\n * });\n * ```\n */\n static verifySignature(options: VerifySignatureOptions): boolean {\n const { secret, timestamp, body, signature } = options;\n const bodyBuf =\n typeof body === \"string\"\n ? Buffer.from(body, \"utf8\")\n : body instanceof Buffer\n ? body\n : Buffer.from(body);\n const message = Buffer.concat([Buffer.from(`${timestamp}.`, \"utf8\"), bodyBuf]);\n const expected = createHmac(\"sha256\", secret).update(message).digest(\"hex\");\n if (expected.length !== signature.length) return false;\n try {\n return timingSafeEqual(Buffer.from(expected, \"hex\"), Buffer.from(signature, \"hex\"));\n } catch {\n return false;\n }\n }\n}\n\nfunction webhookBody(options: CreateWebhookOptions | UpdateWebhookOptions): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n const map: Array<[keyof (CreateWebhookOptions & UpdateWebhookOptions), string]> = [\n [\"url\", \"url\"],\n [\"events\", \"events\"],\n [\"filterSportKey\", \"filter_sport_key\"],\n [\"filterEventId\", \"filter_event_id\"],\n [\"filterMarketKey\", \"filter_market_key\"],\n [\"filterPlayerName\", \"filter_player_name\"],\n [\"filterBookmakerKey\", \"filter_bookmaker_key\"],\n [\"minPriceChangePct\", \"min_price_change_pct\"],\n [\"minSteamScore\", \"min_steam_score\"],\n [\"minBooksAgreeing\", \"min_books_agreeing\"],\n [\"batchMax\", \"batch_max\"],\n [\"active\", \"active\"],\n ];\n for (const [src, dst] of map) {\n const v = (options as Record<string, unknown>)[src as string];\n if (v !== undefined) body[dst] = v;\n }\n return body;\n}\n\ninterface ReadDetailResult {\n message: string;\n info?: PropLineErrorInfo;\n}\n\nasync function readDetail(\n resp: Response,\n fallback: string,\n): Promise<ReadDetailResult> {\n try {\n const text = await resp.text();\n if (!text) return { message: fallback };\n try {\n const json = JSON.parse(text) as { detail?: unknown };\n if (typeof json.detail === \"string\") return { message: json.detail };\n if (json.detail && typeof json.detail === \"object\") {\n const info = json.detail as PropLineErrorInfo;\n return {\n message: typeof info.message === \"string\" ? info.message : text,\n info,\n };\n }\n } catch {\n // not JSON\n }\n return { message: text || fallback };\n } catch {\n return { message: fallback };\n }\n}\n"],"mappings":"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","names":[]}
package/dist/index.d.cts CHANGED
@@ -107,6 +107,12 @@ interface Outcome {
107
107
  * id identifies the contract; `name` says which side.
108
108
  */
109
109
  book_outcome_id?: string | null;
110
+ /**
111
+ * PropLine's own stable id for this selection (`outcomes.id`), present when
112
+ * `includeBookIds=true`. Shared with `/odds/history`, `/odds/closing`, the
113
+ * resolved-props CSV and webhook payloads — the join key across all of them.
114
+ */
115
+ outcome_id?: number | null;
110
116
  /**
111
117
  * Dollars a bettor can actually stake at the quoted `price` — exchange
112
118
  * books that publish resting-offer size only (ProphetX today). `null`/
@@ -278,6 +284,8 @@ interface OutcomeSnapshot {
278
284
  interface OddsHistoryOutcome {
279
285
  name: string;
280
286
  description?: string | null;
287
+ /** PropLine's stable id for this selection — shared with `/odds?includeBookIds=true`, `/odds/closing`, the resolved-props CSV and webhook payloads. */
288
+ outcome_id?: number | null;
281
289
  snapshots: OutcomeSnapshot[];
282
290
  snapshots_available?: number;
283
291
  redacted?: boolean;
@@ -317,6 +325,8 @@ interface OddsHistoryResponse {
317
325
  interface ClosingOutcome {
318
326
  name: string;
319
327
  description?: string | null;
328
+ /** PropLine's stable id for this selection — shared with `/odds?includeBookIds=true`, `/odds/history`, the resolved-props CSV and webhook payloads. */
329
+ outcome_id?: number | null;
320
330
  price: number | null;
321
331
  point: number | null;
322
332
  /** recorded_at of the snapshot we picked as "closing" (last at-or-before commence_time). */
@@ -2173,6 +2183,6 @@ declare const Bookmakers: {
2173
2183
  readonly PRIZEPICKS: "prizepicks";
2174
2184
  };
2175
2185
  type BookmakerKey = (typeof Bookmakers)[keyof typeof Bookmakers];
2176
- declare const VERSION = "0.48.2";
2186
+ declare const VERSION = "0.48.3";
2177
2187
 
2178
2188
  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 SgpLegInput, type SgpLegQuote, 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 };
package/dist/index.d.ts CHANGED
@@ -107,6 +107,12 @@ interface Outcome {
107
107
  * id identifies the contract; `name` says which side.
108
108
  */
109
109
  book_outcome_id?: string | null;
110
+ /**
111
+ * PropLine's own stable id for this selection (`outcomes.id`), present when
112
+ * `includeBookIds=true`. Shared with `/odds/history`, `/odds/closing`, the
113
+ * resolved-props CSV and webhook payloads — the join key across all of them.
114
+ */
115
+ outcome_id?: number | null;
110
116
  /**
111
117
  * Dollars a bettor can actually stake at the quoted `price` — exchange
112
118
  * books that publish resting-offer size only (ProphetX today). `null`/
@@ -278,6 +284,8 @@ interface OutcomeSnapshot {
278
284
  interface OddsHistoryOutcome {
279
285
  name: string;
280
286
  description?: string | null;
287
+ /** PropLine's stable id for this selection — shared with `/odds?includeBookIds=true`, `/odds/closing`, the resolved-props CSV and webhook payloads. */
288
+ outcome_id?: number | null;
281
289
  snapshots: OutcomeSnapshot[];
282
290
  snapshots_available?: number;
283
291
  redacted?: boolean;
@@ -317,6 +325,8 @@ interface OddsHistoryResponse {
317
325
  interface ClosingOutcome {
318
326
  name: string;
319
327
  description?: string | null;
328
+ /** PropLine's stable id for this selection — shared with `/odds?includeBookIds=true`, `/odds/history`, the resolved-props CSV and webhook payloads. */
329
+ outcome_id?: number | null;
320
330
  price: number | null;
321
331
  point: number | null;
322
332
  /** recorded_at of the snapshot we picked as "closing" (last at-or-before commence_time). */
@@ -2173,6 +2183,6 @@ declare const Bookmakers: {
2173
2183
  readonly PRIZEPICKS: "prizepicks";
2174
2184
  };
2175
2185
  type BookmakerKey = (typeof Bookmakers)[keyof typeof Bookmakers];
2176
- declare const VERSION = "0.48.2";
2186
+ declare const VERSION = "0.48.3";
2177
2187
 
2178
2188
  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 SgpLegInput, type SgpLegQuote, 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 };
package/dist/index.js CHANGED
@@ -1101,7 +1101,7 @@ var Bookmakers = {
1101
1101
  POLYMARKET: "polymarket",
1102
1102
  PRIZEPICKS: "prizepicks"
1103
1103
  };
1104
- var VERSION = "0.48.2";
1104
+ var VERSION = "0.48.3";
1105
1105
  export {
1106
1106
  AuthError,
1107
1107
  Bookmakers,
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/client.ts","../src/index.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport type {\n ClvBetInput,\n ClvGradeResponse,\n SgpLegInput,\n SgpQuoteResponse,\n Sport,\n Event as PropLineEvent,\n OddsResponse,\n MarketSummary,\n OddsHistoryResponse,\n OddsClosingResponse,\n ScoreEvent,\n MlbGrandSalamiResponse,\n NhlDailyGoalsTotalResponse,\n ResolutionSummary,\n StatsResponse,\n ContextResponse,\n MovementResponse,\n ResultsResponse,\n PlayerGameLog,\n PlayerHistoryResponse,\n PlayerTrends,\n EventEvResponse,\n EventProjectionsResponse,\n EventEvCalcResponse,\n EventBestLineResponse,\n FuturesEvent,\n Webhook,\n WebhookDelivery,\n ReplayEvent,\n ReplayPage,\n DfsPayoutsResponse,\n} from \"./types.js\";\n\n/** Options for {@link PropLineClient.getDfsPayouts}. */\nexport interface GetDfsPayoutsOptions {\n /** DFS platform. Only \"prizepicks\" today. */\n platform?: string;\n /**\n * Assumed per-leg win probability in [0, 1]. When supplied, each play\n * also carries `expected_return` (per $1) and `is_plus_ev` at that rate.\n */\n legWinProb?: number;\n}\n\n/**\n * Structured error body returned by gated/throttled endpoints\n * (see https://prop-line.com/docs#errors). Branch on `error` — the\n * codes are stable — and follow the URLs instead of parsing prose.\n */\nexport interface PropLineErrorInfo {\n /** Stable machine-readable code, e.g. \"upgrade_required\", \"daily_limit_exceeded\". */\n error?: string;\n /** Human-readable sentence. */\n message?: string;\n /** Cheapest tier that unlocks a gated feature (403s). */\n required_tier?: string;\n /** Where to unlock it — pre-filled one-click URL on daily-cap 429s. */\n upgrade_url?: string;\n docs_url?: string;\n signup_url?: string;\n backfill_url?: string;\n /** Burst-limit backoff hint (429s). */\n retry_after_seconds?: number;\n /** Daily-cap 429s: recommended next plan incl. its own upgrade_url. */\n recommended?: { plan?: string; upgrade_url?: string; [key: string]: unknown };\n [key: string]: unknown;\n}\n\n/** Base error for all PropLine API failures. */\nexport class PropLineError extends Error {\n readonly statusCode: number;\n /** Human-readable detail message. */\n readonly detail: string;\n /** Structured error body, when the API returned one. */\n readonly info?: PropLineErrorInfo;\n\n constructor(statusCode: number, detail: string, info?: PropLineErrorInfo) {\n super(`[${statusCode}] ${detail}`);\n this.name = \"PropLineError\";\n this.statusCode = statusCode;\n this.detail = detail;\n this.info = info;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Stable machine-readable code (e.g. \"upgrade_required\"), if present. */\n get errorCode(): string | undefined {\n return this.info?.error;\n }\n\n /** The URL that unlocks a gated feature or lifts a cap, if present. */\n get upgradeUrl(): string | undefined {\n return this.info?.upgrade_url ?? this.info?.recommended?.upgrade_url;\n }\n}\n\n/** Thrown when the API key is missing or invalid (HTTP 401). */\nexport class AuthError extends PropLineError {\n constructor(detail = \"Invalid API key\", info?: PropLineErrorInfo) {\n super(401, detail, info);\n this.name = \"AuthError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when the daily request limit is exceeded (HTTP 429). */\nexport class RateLimitError extends PropLineError {\n constructor(detail = \"Rate limit exceeded\", info?: PropLineErrorInfo) {\n super(429, detail, info);\n this.name = \"RateLimitError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport interface PropLineOptions {\n /** API base URL. Default: `https://api.prop-line.com/v1`. */\n baseUrl?: string;\n /** Request timeout in milliseconds. Default: 15000. */\n timeoutMs?: number;\n /** Custom fetch implementation. Defaults to global `fetch` (Node 18+). */\n fetch?: typeof fetch;\n}\n\n/**\n * Game-period filter. String of canonical codes, optionally\n * comma-separated, or the sentinel `\"all\"`. Omitted = full-game\n * markets only (backwards-compatible default).\n *\n * \"q1\" — 1st quarter\n * \"q1,q2\" — 1st and 2nd quarters\n * [\"q1\",\"q2\"] — same, as an array\n * \"h1\" — 1st half\n * \"p1\"|\"p2\"|\"p3\" — hockey periods\n * \"i6\" — 6th inning\n * \"f3\"|\"f5\"|\"f7\" — first N innings\n * \"all\" — every period including full game\n */\nexport type PeriodFilter = string | string[];\n\nexport interface GetOddsOptions {\n /** Specific event ID to get odds (with player props) for. Omit for bulk odds. */\n eventId?: number | string;\n /**\n * Market keys to filter by. If omitted, the bulk `/odds` endpoint\n * defaults to `h2h` and the per-event `/odds` endpoint defaults to\n * `h2h,spreads,totals` — game-line markets every book carries across\n * every sport. Pass an explicit list to fetch player props (e.g.\n * `[\"pitcher_strikeouts\", \"batter_home_runs\"]` for MLB,\n * `[\"player_points\", \"player_rebounds\"]` for NBA).\n */\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /**\n * Bookmaker key(s) to restrict the response to (e.g. `\"draftkings\"` or\n * `[\"draftkings\", \"fanduel\"]`). Omitted = all books. Same parameter name\n * as the-odds-api.\n */\n bookmakers?: string | string[];\n /**\n * When true, each bookmaker block carries a `link` — that book's\n * public event-page URL (plain navigation, no affiliate tagging),\n * so your UI can click out from a line to the book. Links ship for\n * Bovada, DraftKings, FanDuel, BetMGM, Kalshi, Polymarket and\n * Smarkets; other books return null. Maps to the\n * the-odds-api-compatible `includeLinks=true` query param.\n */\n includeLinks?: boolean;\n /**\n * When true, each bookmaker block carries a `book_event_id` and each\n * outcome a `book_outcome_id` — that book's OWN identifiers for the\n * event and the priced selection. Use these to join PropLine rows onto\n * a book's native feed by id instead of matching on team names,\n * players and lines. Kalshi ships both (the event ticker and the\n * per-contract market ticker, e.g. `KXMLBGAME-26AUG08NYYBOS-NYY`);\n * most other books ship an event id. Books without a stable id return\n * null.\n *\n * NB a two-sided market can share ONE `book_outcome_id` across both\n * legs — a Kalshi contract is binary, so Over and Under are its YES\n * and NO sides. The id identifies the contract; the outcome's `name`\n * says which side.\n *\n * PropLine-specific (`includeBookIds=true`); the-odds-api has no\n * equivalent.\n */\n includeBookIds?: boolean;\n}\n\nexport interface GetOddsHistoryOptions {\n markets?: string[];\n /** ISO timestamp; only include snapshots at or after this time. Mutually exclusive with `relativeFrom`. */\n from?: string;\n /** ISO timestamp; only include snapshots at or before this time. Mutually exclusive with `relativeTo`. */\n to?: string;\n /** Offset relative to commence_time, e.g. \"-3h\", \"-30m\", \"-90s\". Mutually exclusive with `from`. */\n relativeFrom?: string;\n /** Offset relative to commence_time, e.g. \"-1m\" or \"0\" for commence_time itself. Mutually exclusive with `to`. */\n relativeTo?: string;\n /** Downsample to one snapshot per bucket. Latest snapshot in each bucket wins. */\n interval?: \"30s\" | \"1m\" | \"5m\" | \"15m\" | \"30m\" | \"1h\";\n /** When true, drop snapshots whose (price, point) match the previous one. Opening line is always kept. */\n changesOnly?: boolean;\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nexport interface GetOddsClosingOptions {\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nexport interface GetMovementOptions {\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nfunction _periodParam(p: PeriodFilter | undefined): string | undefined {\n if (p === undefined) return undefined;\n return typeof p === \"string\" ? p : p.join(\",\");\n}\n\nfunction _bookmakersParam(b: string | string[] | undefined): string | undefined {\n if (b === undefined || b.length === 0) return undefined;\n return typeof b === \"string\" ? b : b.join(\",\");\n}\n\nexport interface GetScoresOptions {\n /** Days back to include (default 3). */\n daysFrom?: number;\n}\n\nexport interface GetMlbGrandSalamiOptions {\n /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */\n date?: string;\n}\n\nexport interface GetNhlDailyGoalsTotalOptions {\n /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */\n date?: string;\n}\n\nexport interface GetStatsOptions {\n /** Stat types to filter by (e.g. `[\"strikeouts\", \"hits\"]`). */\n statType?: string[];\n}\n\nexport interface GetResultsOptions {\n markets?: string[];\n}\n\nexport interface GetPlayerHistoryOptions {\n /** Market key (e.g. `\"pitcher_strikeouts\"`). Required. */\n market: string;\n /** Restrict to a single bookmaker (e.g. `\"draftkings\"`). */\n bookmaker?: string;\n /** Max entries (1-100). Default 20. */\n limit?: number;\n}\n\nexport interface GetPlayerGamesOptions {\n /** Games to return, 1-100. Default 20. */\n limit?: number;\n /**\n * Head-to-head filter. Accepts a full name, nickname or abbreviation\n * (\"Boston Red Sox\", \"Red Sox\", \"BOS\"). The limit applies AFTER this\n * filter, so `{ opponent: \"BOS\", limit: 10 }` is the last 10 MEETINGS,\n * not the Boston games among the last 10 games. Not capped to the\n * current season.\n */\n opponent?: string;\n /**\n * Stat name(s) to return; omit for all. Vocabulary is per-sport —\n * see https://prop-line.com/docs#stats\n */\n statType?: string | string[];\n}\n\nexport interface GetPlayerTrendsOptions {\n /** Market key (e.g. `\"batter_total_bases\"`). Omit for all markets. */\n market?: string;\n /**\n * PrizePicks pick-em flavor to compute trends against: `\"standard\"`\n * (default market line), `\"goblin\"`, or `\"demon\"`. When set, the trend is\n * computed against that flavor's PrizePicks line only. Omit for the default\n * cross-book behavior. Flavor tagging began 2026-06-16.\n */\n dfsOddsType?: \"standard\" | \"goblin\" | \"demon\";\n}\n\nexport interface GetEventProjectionsOptions {\n /** Optional market-key filter (comma-separated string or array). */\n markets?: string | string[];\n}\n\nexport interface GetEventEvOptions {\n /**\n * Optional market filter. Pass a single comma-separated string or an\n * array of market keys (e.g. `[\"pitcher_strikeouts\", \"batter_hits\"]`).\n * Omit to evaluate every market on the event.\n */\n markets?: string | string[];\n /**\n * Optional bookmaker filter (the-odds-api-compatible). Pass a\n * comma-separated string or an array of book keys (e.g.\n * `[\"draftkings\", \"fanduel\"]`) to price only the books you hold\n * accounts at. Omit for every book.\n *\n * This narrows the PRICES, never the fair-line anchor:\n * `bookmakers: [\"draftkings\"]` still returns DraftKings EV% measured\n * against Pinnacle. Lines where none of your books quote a price are\n * omitted.\n */\n bookmakers?: string | string[];\n}\n\nexport interface GetEventBestLineOptions {\n /**\n * Optional market filter. Pass a single comma-separated string or an\n * array of market keys (e.g. `[\"pitcher_strikeouts\", \"h2h\"]`). Omit\n * to include every market on the event.\n */\n markets?: string | string[];\n /**\n * Optional bookmaker filter (the-odds-api-compatible). Pass a\n * comma-separated string or an array of book keys (e.g.\n * `[\"draftkings\", \"fanduel\"]`) to shop only the books you hold\n * accounts at. Omit for all comparable books.\n */\n bookmakers?: string | string[];\n /**\n * When true, every price row carries a `link` — that book's public\n * event-page URL, the click-out for \"go bet this\". Books without a\n * verified URL template return null. Links appear on free-tier\n * redacted responses too (navigation isn't the paid data).\n */\n includeLinks?: boolean;\n}\n\nexport interface CalcEventEvOptions {\n /** Market key — h2h / spreads / totals / pitcher_strikeouts / etc. */\n market: string;\n /** Outcome name. Team for h2h/spreads; \"Over\" or \"Under\" for totals/props. */\n name: string;\n /** American odds at your book, e.g. -118 or 145. */\n price: number;\n /** Line/point for spreads, totals, player props. Sign matters for spreads (-1.5 favorite). Omit for h2h. */\n point?: number;\n /** Player name for player-prop markets. Omit for game-line markets. */\n description?: string;\n}\n\nexport interface ExportResolvedPropsOptions {\n /** Sport key (e.g. `\"baseball_mlb\"`). Required. */\n sport: string;\n /** Optional market filter. */\n market?: string;\n /** Optional bookmaker filter. */\n bookmaker?: string;\n /** ISO datetime lower bound on `resolved_at`. */\n since?: string;\n /** ISO datetime upper bound on `resolved_at`. */\n until?: string;\n /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */\n outPath?: string;\n}\n\nexport interface ExportOddsHistoryOptions {\n /** Sport key (e.g. `\"baseball_mlb\"`). Required. */\n sport: string;\n /** Optional market filter. */\n market?: string;\n /** Optional bookmaker filter. */\n bookmaker?: string;\n /** ISO datetime lower bound on `recorded_at`. */\n since?: string;\n /** ISO datetime upper bound on `recorded_at`. */\n until?: string;\n /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */\n outPath?: string;\n}\n\n/**\n * Webhook event types. `steam` = cross-book sharp-money alert.\n * `market_suspended` = a book took a market off the board pregame (one\n * delivery per (book, event, player) withdrawal, with `books_agreeing`).\n */\nexport type WebhookEventType = \"line_movement\" | \"resolution\" | \"steam\" | \"market_suspended\";\n\nexport interface CreateWebhookOptions {\n /** HTTPS endpoint to receive POSTed events. Required. */\n url: string;\n /** Event types to subscribe to. Default: all. */\n events?: WebhookEventType[];\n filterSportKey?: string;\n filterEventId?: number;\n filterMarketKey?: string;\n filterPlayerName?: string;\n /**\n * Comma-separated book keys, same vocabulary as the `?bookmakers=`\n * query param (e.g. \"draftkings,fanduel\"). Unset = all books; unknown\n * keys match nothing. Applies to line_movement, resolution and\n * market_suspended; steam is cross-book and unaffected.\n */\n filterBookmakerKey?: string;\n /** Minimum % change in American odds to fire a line_movement. Point-only shifts always pass. */\n minPriceChangePct?: number;\n /** Minimum 0-100 steam score to fire a `steam` event. Null = detector's global floor. */\n minSteamScore?: number;\n /**\n * `market_suspended` only: how many books must have pulled the same\n * player/market on the same event before you are told. Unset/1 = every\n * drop (right if you price off one book); 3+ = corroborated late\n * scratches only. Every payload carries `books_agreeing` regardless.\n */\n minBooksAgreeing?: number;\n /**\n * Batched delivery opt-in (1-500): up to N events per POST as a signed\n * envelope `{\"batch\": true, \"event_type\": ..., \"count\": N, \"events\":\n * [{\"delivery_id\": ..., \"data\": <per-event payload>}, ...]}` with an\n * `X-PropLine-Batch` header. Strongly recommended for high-volume\n * subscriptions — one POST per event caps your delivery rate at your\n * endpoint's response time. 0 reverts to per-event. JSON format only.\n */\n batchMax?: number;\n}\n\nexport interface UpdateWebhookOptions {\n url?: string;\n events?: WebhookEventType[];\n filterSportKey?: string;\n filterEventId?: number;\n filterMarketKey?: string;\n filterPlayerName?: string;\n /**\n * Comma-separated book keys, same vocabulary as the `?bookmakers=`\n * query param (e.g. \"draftkings,fanduel\"). Unset = all books; unknown\n * keys match nothing. Applies to line_movement, resolution and\n * market_suspended; steam is cross-book and unaffected.\n */\n filterBookmakerKey?: string;\n minPriceChangePct?: number;\n minSteamScore?: number;\n minBooksAgreeing?: number;\n /** Batched delivery (see CreateWebhookOptions.batchMax). 0 = per-event. */\n batchMax?: number;\n active?: boolean;\n}\n\nexport interface ListWebhookDeliveriesOptions {\n /** Max deliveries to return. Default 50, max 200. */\n limit?: number;\n /**\n * Page backwards: pass the smallest `id` from the previous page to get\n * the next-older page. Pages are newest-first; a page shorter than\n * `limit` is the last one.\n */\n beforeId?: number;\n}\n\nexport interface ReplayWebhookEventsOptions {\n /**\n * Read events after this cursor — the highest `X-PropLine-Sequence` you\n * have processed. Defaults to 0 (from the oldest retained event).\n */\n sinceSeq?: number;\n /** Max events per page. Default 100, max 500. */\n limit?: number;\n}\n\nexport interface StreamOptions {\n /** Subscription to stream. Must be transport=\"websocket\". */\n webhookId: number;\n /** Resume point — the last `seq` you processed. Default 0. */\n sinceSeq?: number;\n /** Auto-reconnect and resume from the last seq. Default true. */\n reconnect?: boolean;\n /** Override the websocket origin (default: derived from baseUrl). */\n wsUrl?: string;\n /** Called on every successful handshake with the `ready` frame. */\n onReady?: (ready: ReplayPage) => void;\n /**\n * Called when the server reports events after your cursor have aged out of\n * retention. This is the one case the stream cannot make you whole —\n * resync from the REST endpoints.\n */\n onTruncated?: (ready: ReplayPage) => void;\n}\n\nexport interface VerifySignatureOptions {\n /** Webhook signing secret (returned once from `createWebhook`). */\n secret: string;\n /** Value of the `X-PropLine-Timestamp` header. */\n timestamp: string;\n /** Raw request body. */\n body: Uint8Array | Buffer | string;\n /** Value of the `X-PropLine-Signature` header. */\n signature: string;\n}\n\n/**\n * Live daily-quota state, parsed from the `X-Daily-*` headers the API\n * returns on every authenticated response.\n */\nexport interface QuotaStatus {\n /** Your tier's daily request cap. */\n limit: number;\n /** Requests used today (including the request that produced this). */\n used: number;\n /** Requests left before the cap. */\n remaining: number;\n /** Unix seconds when the quota resets (00:00 UTC — a hard reset, not a rolling window). */\n resetEpoch: number;\n /** Quota reset time as a `Date`. */\n resetAt: Date;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.prop-line.com/v1\";\n\n// Streaming origin. Separate Fly app, separate machines — see stream().\nconst DEFAULT_WS_URL = \"wss://ws.prop-line.com\";\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Client for the PropLine player props API.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * const client = new PropLine(\"your_api_key\");\n * const sports = await client.getSports();\n * ```\n */\nexport class PropLine {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n /**\n * Daily-quota state from the most recent API response, or `null` before\n * the first request. Updated on every call (including 429s):\n *\n * ```ts\n * await client.getSports();\n * console.log(client.lastQuota?.remaining); // 999\n * ```\n */\n lastQuota: QuotaStatus | null = null;\n private readonly _fetch: typeof fetch;\n\n constructor(apiKey: string, options: PropLineOptions = {}) {\n if (!apiKey) {\n throw new Error(\"PropLine: apiKey is required\");\n }\n this.apiKey = apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this._fetch = options.fetch ?? globalThis.fetch;\n if (!this._fetch) {\n throw new Error(\n \"PropLine: global fetch is unavailable. Use Node 18+ or pass options.fetch.\"\n );\n }\n }\n\n // ------------------------------------------------------------------\n // Internals\n // ------------------------------------------------------------------\n\n private _buildUrl(path: string, params?: Record<string, string | number | undefined>): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null) {\n url.searchParams.set(k, String(v));\n }\n }\n }\n return url.toString();\n }\n\n /**\n * Record the X-Daily-* quota headers when present (absent on\n * unauthenticated errors, e.g. an invalid key's 401).\n */\n private _captureQuota(resp: Response): void {\n const limit = Number(resp.headers.get(\"X-Daily-Limit\"));\n const used = Number(resp.headers.get(\"X-Daily-Used\"));\n const remaining = Number(resp.headers.get(\"X-Daily-Remaining\"));\n const resetEpoch = Number(resp.headers.get(\"X-Daily-Reset\"));\n if (\n resp.headers.has(\"X-Daily-Limit\") &&\n Number.isFinite(limit) &&\n Number.isFinite(used) &&\n Number.isFinite(remaining) &&\n Number.isFinite(resetEpoch)\n ) {\n this.lastQuota = {\n limit,\n used,\n remaining,\n resetEpoch,\n resetAt: new Date(resetEpoch * 1000),\n };\n }\n }\n\n private async _request<T>(\n method: string,\n path: string,\n init: { params?: Record<string, string | number | undefined>; body?: unknown } = {}\n ): Promise<T> {\n const url = this._buildUrl(path, init.params);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method,\n headers: {\n \"X-API-Key\": this.apiKey,\n ...(init.body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n const d = await readDetail(resp, \"Invalid API key\");\n throw new AuthError(d.message, d.info);\n }\n if (resp.status === 429) {\n const d = await readDetail(resp, \"Rate limit exceeded\");\n throw new RateLimitError(d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n if (resp.status === 204) {\n return undefined as T;\n }\n return (await resp.json()) as T;\n }\n\n // ------------------------------------------------------------------\n // Public API\n // ------------------------------------------------------------------\n\n /** List all available sports. */\n getSports(): Promise<Sport[]> {\n return this._request<Sport[]>(\"GET\", \"/sports\");\n }\n\n /** List upcoming events for a sport. */\n getEvents(sport: string): Promise<PropLineEvent[]> {\n return this._request<PropLineEvent[]>(\"GET\", `/sports/${encodeURIComponent(sport)}/events`);\n }\n\n /**\n * Get current odds. With `eventId`, returns single-event odds (including\n * player props). Without, returns bulk odds for all upcoming events.\n *\n * Each response carries a `bookmakers` array — iterate it to compare\n * lines across Bovada, DraftKings, FanDuel, Pinnacle, Unibet, and\n * PrizePicks (coverage varies by sport).\n */\n getOdds(\n sport: string,\n options: GetOddsOptions & { eventId: number | string }\n ): Promise<OddsResponse>;\n getOdds(\n sport: string,\n options?: Omit<GetOddsOptions, \"eventId\"> & { eventId?: undefined }\n ): Promise<OddsResponse[]>;\n getOdds(\n sport: string,\n options: GetOddsOptions = {}\n ): Promise<OddsResponse | OddsResponse[]> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n const periodParam = _periodParam(options.period);\n if (periodParam !== undefined) params.period = periodParam;\n const bookmakersParam = _bookmakersParam(options.bookmakers);\n if (bookmakersParam !== undefined) params.bookmakers = bookmakersParam;\n if (options.includeLinks) params.includeLinks = \"true\";\n if (options.includeBookIds) params.includeBookIds = \"true\";\n const sp = encodeURIComponent(sport);\n if (options.eventId !== undefined) {\n return this._request<OddsResponse>(\n \"GET\",\n `/sports/${sp}/events/${encodeURIComponent(String(options.eventId))}/odds`,\n { params }\n );\n }\n return this._request<OddsResponse[]>(\"GET\", `/sports/${sp}/odds`, { params });\n }\n\n /** List the available market types for an event. */\n getMarkets(sport: string, eventId: number | string): Promise<MarketSummary[]> {\n return this._request<MarketSummary[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/markets`\n );\n }\n\n /**\n * Get historical odds movement for an event.\n *\n * Hobby+: full snapshots. Free tier: redacted (snapshot counts only).\n *\n * Supports period-historical filters:\n * - `from` / `to` — absolute ISO timestamps\n * - `relativeFrom` / `relativeTo` — offsets to commence_time (\"-3h\", \"-30m\", \"0\")\n * - `interval` — downsample to a fixed bucket size\n * - `changesOnly` — drop unchanged adjacent snapshots\n */\n getOddsHistory(\n sport: string,\n eventId: number | string,\n options: GetOddsHistoryOptions = {}\n ): Promise<OddsHistoryResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n if (options.from !== undefined) params.from = options.from;\n if (options.to !== undefined) params.to = options.to;\n if (options.relativeFrom !== undefined) params.relative_from = options.relativeFrom;\n if (options.relativeTo !== undefined) params.relative_to = options.relativeTo;\n if (options.interval !== undefined) params.interval = options.interval;\n if (options.changesOnly) params.changes_only = \"true\";\n const periodParam2 = _periodParam(options.period);\n if (periodParam2 !== undefined) params.period = periodParam2;\n const bookmakersParam2 = _bookmakersParam(options.bookmakers);\n if (bookmakersParam2 !== undefined) params.bookmakers = bookmakersParam2;\n return this._request<OddsHistoryResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/odds/history`,\n { params }\n );\n }\n\n /**\n * Get the opening AND closing line per `(book, market, outcome)` for an\n * event. Closing is the last snapshot at or before commence_time\n * (`price` / `point` / `closing_at`); opening is the first snapshot in\n * the same 14-day pre-kickoff window (`opening_price` / `opening_point`\n * / `opening_at`). Canonical CLV helper: replaces \"fetch full history →\n * find the first and last pre-game rows\" with one call.\n *\n * Compare the *points* as well as the prices — on spreads and totals\n * the number moves as much as the price, so a price-only comparison\n * mis-measures those markets.\n *\n * Hobby+: full data. Free tier: redacted.\n */\n getOddsClosing(\n sport: string,\n eventId: number | string,\n options: GetOddsClosingOptions = {}\n ): Promise<OddsClosingResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n const periodParam3 = _periodParam(options.period);\n if (periodParam3 !== undefined) params.period = periodParam3;\n const bookmakersParam3 = _bookmakersParam(options.bookmakers);\n if (bookmakersParam3 !== undefined) params.bookmakers = bookmakersParam3;\n return this._request<OddsClosingResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/odds/closing`,\n { params }\n );\n }\n\n /** Get game scores and status (free tier). */\n getScores(sport: string, options: GetScoresOptions = {}): Promise<ScoreEvent[]> {\n return this._request<ScoreEvent[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/scores`,\n { params: { days_from: options.daysFrom ?? 3 } }\n );\n }\n\n /**\n * PrizePicks Power/Flex entry payout schedule (2-6 legs) plus the per-leg\n * breakeven win probability for each play. Pass `legWinProb` to also get\n * `expected_return` (per $1) and `is_plus_ev` per play — turning a slip\n * into the hit rate it actually needs to clear.\n *\n * These are PrizePicks's *standard* published payouts; demon/goblin per-pick\n * modifiers aren't in PrizePicks's feed, so they're not reflected. Breakeven\n * assumes independent legs. See the `disclaimer` field on the response.\n */\n getDfsPayouts(\n options: GetDfsPayoutsOptions = {}\n ): Promise<DfsPayoutsResponse> {\n const params: Record<string, string | number> = {\n platform: options.platform ?? \"prizepicks\",\n };\n if (options.legWinProb !== undefined) params.leg_win_prob = options.legWinProb;\n return this._request<DfsPayoutsResponse>(\"GET\", \"/dfs/payouts\", { params });\n }\n\n /**\n * Synthetic MLB Grand Salami for a given UTC date — total runs scored\n * across every MLB game on the slate, plus each book's implied Grand\n * Salami line (median of per-game primary totals across our MLB books).\n *\n * No retail sportsbook quotes this as a single market, so historical\n * cross-book Grand Salami data isn't available elsewhere. Free tier;\n * defaults to today (UTC).\n */\n getMlbGrandSalami(\n options: GetMlbGrandSalamiOptions = {}\n ): Promise<MlbGrandSalamiResponse> {\n const params: Record<string, string> = {};\n if (options.date) params.date = options.date;\n return this._request<MlbGrandSalamiResponse>(\n \"GET\",\n \"/sports/baseball_mlb/grand-salami\",\n { params }\n );\n }\n\n /**\n * Synthetic NHL Daily Goals Total for a given UTC date — total goals\n * scored (incl. OT/SO) across every NHL game on the slate, plus each\n * book's implied Daily Goals Total line (median of per-game primary\n * totals across our NHL books).\n *\n * Hockey's equivalent of the MLB Grand Salami. No retail sportsbook\n * quotes this as a single market. Free tier; defaults to today (UTC).\n */\n getNhlDailyGoalsTotal(\n options: GetNhlDailyGoalsTotalOptions = {}\n ): Promise<NhlDailyGoalsTotalResponse> {\n const params: Record<string, string> = {};\n if (options.date) params.date = options.date;\n return this._request<NhlDailyGoalsTotalResponse>(\n \"GET\",\n \"/sports/hockey_nhl/daily-goals-total\",\n { params }\n );\n }\n\n /**\n * Factual volume of graded player props over the last N days (free tier).\n *\n * Aggregated counts only — a coverage proof (every outcome counted was\n * graded against the real box score), never a profitability claim.\n *\n * @param days Look-back window, 1-90 (default 30).\n */\n getResolutionSummary(days = 30): Promise<ResolutionSummary> {\n return this._request<ResolutionSummary>(\n \"GET\",\n \"/markets/resolution-summary\",\n { params: { days: String(days) } }\n );\n }\n\n /**\n * Get raw player/team box-score stats (book-agnostic, free tier).\n *\n * Returns actual stat values decoupled from any bookmaker's lines.\n *\n * Live during games for major US sports (MLB + WNBA now; NFL, NCAAF,\n * NBA, NHL at season start): while the event's status is \"in_progress\",\n * stats refresh roughly every 90 seconds with cumulative in-game values —\n * treat them as partial until status flips to \"final\". Other sports\n * populate stats at game completion.\n */\n getStats(\n sport: string,\n eventId: number | string,\n options: GetStatsOptions = {}\n ): Promise<StatsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.statType?.length) {\n params.stat_type = options.statType.join(\",\");\n }\n return this._request<StatsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/stats`,\n { params }\n );\n }\n\n /**\n * Get game context — the conditions a prop settles under.\n *\n * For MLB: probable starting pitchers and their throwing hand\n * (`home_probable_pitcher_hand` / `away_probable_pitcher_hand`, \"L\"/\"R\"/\"S\"\n * — platoon-split context for every batter prop), a confirmed-lineup flag,\n * the home-plate umpire, and first-pitch weather (outdoor / open-roof\n * venues; indoor venues return `weather: null` with `is_indoor: true`).\n * For NFL & NCAAF: the venue and kickoff weather (pitcher/umpire/lineup\n * fields are null for football). The same block is embedded in\n * {@link getResults}, so every graded prop carries its conditions — unique\n * to PropLine. Free tier. Rejects with a 404 when no context is on file\n * for the event yet.\n */\n getContext(\n sport: string,\n eventId: number | string\n ): Promise<ContextResponse> {\n return this._request<ContextResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/context`\n );\n }\n\n /**\n * Get line movement + steam detection from the snapshot tick history.\n *\n * Per (book, market, outcome): opening line, latest line, signed\n * implied-probability shift, point shift, direction. The `steam` array\n * flags outcomes multiple books moved the same direction — the\n * sharp-money signal across every book PropLine polls. When a book moves\n * the line itself, that outcome's `prob_shift` is null and `direction` is\n * `\"line_moved\"` (excluded from the steam signal). Unique to PropLine.\n * Hobby+ full; free tier redacted.\n */\n getMovement(\n sport: string,\n eventId: number | string,\n options: GetMovementOptions = {}\n ): Promise<MovementResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n params.period = _periodParam(options.period);\n params.bookmakers = _bookmakersParam(options.bookmakers);\n return this._request<MovementResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/movement`,\n { params }\n );\n }\n\n /**\n * Get resolved prop outcomes with actual player stats.\n *\n * Pro tier: full data. Free tier: redacted (resolution + actual nulled).\n */\n getResults(\n sport: string,\n eventId: number | string,\n options: GetResultsOptions = {}\n ): Promise<ResultsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n return this._request<ResultsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/results`,\n { params }\n );\n }\n\n /**\n * Recent resolved prop history for a player on a market.\n *\n * One entry per (event, bookmaker) pair. Pro: full. Free: redacted.\n */\n getPlayerHistory(\n sport: string,\n playerName: string,\n options: GetPlayerHistoryOptions\n ): Promise<PlayerHistoryResponse> {\n const params: Record<string, string | number | undefined> = {\n market: options.market,\n limit: options.limit ?? 20,\n };\n if (options.bookmaker) {\n params.bookmaker = options.bookmaker;\n }\n return this._request<PlayerHistoryResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/players/${encodeURIComponent(playerName)}/history`,\n { params }\n );\n }\n\n /**\n * A player's game log — recent games with every raw box-score stat.\n *\n * One call replaces one request per event, so L5/L10/L20, season splits,\n * charts and head-to-head can all be built from the raw rows. Free tier.\n *\n * Reads the RAW-STATS archive, not graded-prop history: it covers every\n * game with a box score on file, including games no sportsbook priced, so\n * a \"last 10 games\" window here really is the last 10 games — unlike one\n * built from {@link getPlayerHistory} or {@link getPlayerTrends}. Carries\n * no line, price or grade.\n *\n * @example\n * ```ts\n * const log = await client.getPlayerGames(\"baseball_mlb\", \"Aaron Judge\", { limit: 10 });\n * const hits = log.games.reduce((n, g) => n + (g.stats.hits ?? 0), 0);\n *\n * // Last 5 meetings with Boston — not the Boston games among his last 5.\n * const h2h = await client.getPlayerGames(\"baseball_mlb\", \"Aaron Judge\", {\n * limit: 5,\n * opponent: \"BOS\",\n * });\n * ```\n */\n getPlayerGames(\n sportKey: string,\n playerName: string,\n options: GetPlayerGamesOptions = {}\n ): Promise<PlayerGameLog> {\n const params: Record<string, string | number | undefined> = {\n limit: options.limit ?? 20,\n };\n if (options.opponent) {\n params.opponent = options.opponent;\n }\n if (options.statType) {\n params.stat_type = Array.isArray(options.statType)\n ? options.statType.join(\",\")\n : options.statType;\n }\n return this._request<PlayerGameLog>(\n \"GET\",\n `/sports/${encodeURIComponent(sportKey)}/players/${encodeURIComponent(playerName)}/games`,\n { params }\n );\n }\n\n /**\n * Rolling hit-rate trends for a player across one or all markets.\n *\n * Returns over/under/push splits over the last 5/10/20/50 graded games,\n * the current streak, and the most recent game per market. Pro: full.\n * Free: redacted.\n */\n getPlayerTrends(\n sportKey: string,\n playerName: string,\n options: GetPlayerTrendsOptions = {}\n ): Promise<PlayerTrends> {\n const params: Record<string, string | undefined> = {};\n if (options.market) {\n params.market = options.market;\n }\n if (options.dfsOddsType) {\n params.dfs_odds_type = options.dfsOddsType;\n }\n return this._request<PlayerTrends>(\n \"GET\",\n `/sports/${encodeURIComponent(sportKey)}/players/${encodeURIComponent(playerName)}/trends`,\n { params }\n );\n }\n\n /**\n * Cross-book +EV analysis for a single event (Pro+ tier).\n *\n * Groups every outcome by (market, player, line) across the books we\n * carry, derives a no-vig fair line from a sharp anchor (Pinnacle\n * preferred, Bovada fallback), and returns EV% per book at the same\n * line. Outcomes are sorted with +EV plays floated to the top.\n *\n * PrizePicks is excluded — its synthetic +100/+100 prices aren't\n * payout odds. Lines without sharp-anchor coverage are dropped.\n *\n * @example\n * ```ts\n * const ev = await client.getEventEv(\"baseball_mlb\", 12345);\n * for (const line of ev.lines) {\n * const plus = line.outcomes.filter(o => o.is_plus_ev);\n * if (plus.length) console.log(line.market_key, line.description, plus);\n * }\n * ```\n */\n /**\n * List futures markets for a sport — championship winner, MVP,\n * division winner, season win totals, etc. Each row is one (futures\n * event, book, market) with every team or player priced. Free tier;\n * aggregated across each book's futures feed (Bovada, FanDuel,\n * DraftKings, and Pinnacle).\n *\n * @example\n * ```ts\n * const futures = await client.getFutures(\"baseball_mlb\");\n * for (const event of futures) {\n * console.log(`${event.title} @ ${event.commence_time}`);\n * for (const m of event.markets) {\n * const top3 = [...m.outcomes].sort((a, b) => a.price - b.price).slice(0, 3);\n * for (const o of top3) console.log(` ${o.name}: ${o.price}`);\n * }\n * }\n * ```\n *\n * @param options.bookmakers Optional book key(s) to restrict the per-book\n * market rows (the-odds-api-compatible; omitted = all books, unknown keys\n * match nothing). A futures event left with no matching market is dropped.\n */\n getFutures(\n sport: string,\n options: { bookmakers?: string | string[] } = {}\n ): Promise<FuturesEvent[]> {\n const params: Record<string, string | undefined> = {};\n const bookmakersParam = _bookmakersParam(options.bookmakers);\n if (bookmakersParam !== undefined) params.bookmakers = bookmakersParam;\n return this._request<FuturesEvent[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/futures`,\n { params }\n );\n }\n\n /**\n * Market-implied consensus projections for a single event.\n *\n * One row per (market, player): the statistical value the betting\n * market collectively implies — the line where the no-vig P(over)\n * crosses 50%, median across contributing sportsbooks. Built for\n * validating your own statistical/fantasy projections against the\n * live market. Market-implied arithmetic over sportsbook prices,\n * never a forecast. DFS pick'em pricing is excluded.\n *\n * Paid tier required (Hobby+); free tier receives the structure with\n * projected values nulled and `redacted: true`.\n *\n * @example\n * ```ts\n * const proj = await client.getEventProjections(\"football_nfl\", 25070);\n * for (const row of proj.projections) {\n * console.log(row.player, row.market_key, row.projected_value);\n * }\n * ```\n */\n getEventProjections(\n sport: string,\n eventId: number | string,\n options: GetEventProjectionsOptions = {}\n ): Promise<EventProjectionsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n return this._request<EventProjectionsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/projections`,\n { params }\n );\n }\n\n getEventEv(\n sport: string,\n eventId: number | string,\n options: GetEventEvOptions = {}\n ): Promise<EventEvResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n if (options.bookmakers) {\n params.bookmakers = Array.isArray(options.bookmakers)\n ? options.bookmakers.join(\",\")\n : options.bookmakers;\n }\n return this._request<EventEvResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/ev`,\n { params }\n );\n }\n\n /**\n * Cross-book best-line lookup for a single event.\n *\n * For each (market, player, line) tuple, returns the single best\n * American price across every book we carry, with the book name\n * attached. Companion to `getEventEv`: best-line tells you which\n * book has the highest payout right now; +EV tells you whether\n * that price beats a sharp no-vig fair line. Most line shoppers\n * want both.\n *\n * PrizePicks is excluded from the comparison — its DFS payout\n * structure (synthetic +100/+100 quotes) isn't directly comparable\n * to traditional sportsbook odds.\n *\n * Hobby tier or higher sees prices. Free tier gets a redacted\n * teaser: the full structure — every line, side, book identity, and\n * the best-first ranking — with every price null, plus\n * `redacted: true` and an `upgrade_url`.\n *\n * @example\n * ```ts\n * const bl = await client.getEventBestLine(\"baseball_mlb\", 12345);\n * for (const line of bl.lines) {\n * for (const [side, info] of Object.entries(line.sides)) {\n * console.log(\n * `${line.description} ${side} ${line.point}: ` +\n * `${info.best.price} @ ${info.best.book_title}`\n * );\n * }\n * }\n * ```\n */\n getEventBestLine(\n sport: string,\n eventId: number | string,\n options: GetEventBestLineOptions = {}\n ): Promise<EventBestLineResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n if (options.bookmakers) {\n params.bookmakers = Array.isArray(options.bookmakers)\n ? options.bookmakers.join(\",\")\n : options.bookmakers;\n }\n if (options.includeLinks) params.includeLinks = \"true\";\n return this._request<EventBestLineResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/best-line`,\n { params }\n );\n }\n\n /**\n * Calculate EV% for a user-supplied price against the event's\n * no-vig fair anchor. Useful for books PropLine doesn't carry —\n * Caesars, BetMGM, Fanatics, BetUS, Hard Rock — where you have a\n * price in hand and want to know if it's +EV against the sharp\n * consensus we do carry.\n *\n * Same fair-line math as `getEventEv` (Pinnacle-preferred anchor,\n * no-vig devigging) but takes one user price as input. Pro tier.\n *\n * @example\n * ```ts\n * const r = await client.calcEventEv(\"baseball_mlb\", 12614, {\n * market: \"h2h\",\n * name: \"Pittsburgh Pirates\",\n * price: -118,\n * });\n * console.log(`EV ${r.ev_pct}% fair=${r.fair_prob}`);\n * ```\n */\n calcEventEv(\n sport: string,\n eventId: number | string,\n options: CalcEventEvOptions\n ): Promise<EventEvCalcResponse> {\n const params: Record<string, string | number | undefined> = {\n market: options.market,\n name: options.name,\n price: options.price,\n };\n if (options.point !== undefined) params.point = options.point;\n if (options.description) params.description = options.description;\n return this._request<EventEvCalcResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/ev/calc`,\n { params }\n );\n }\n\n /**\n * Bulk CSV export of resolved prop outcomes (Pro+ tier).\n *\n * If `outPath` is provided, streams the CSV to disk and resolves to the\n * path. Otherwise resolves to the full CSV bytes as a `Uint8Array`.\n *\n * @example\n * ```ts\n * await client.exportResolvedProps({\n * sport: \"baseball_mlb\",\n * market: \"pitcher_strikeouts\",\n * since: \"2026-04-01T00:00:00Z\",\n * outPath: \"./mlb-strikeouts.csv\",\n * });\n * ```\n */\n async exportResolvedProps(\n options: ExportResolvedPropsOptions & { outPath: string }\n ): Promise<string>;\n async exportResolvedProps(\n options: ExportResolvedPropsOptions & { outPath?: undefined }\n ): Promise<Uint8Array>;\n async exportResolvedProps(\n options: ExportResolvedPropsOptions\n ): Promise<string | Uint8Array> {\n const params: Record<string, string | undefined> = { sport: options.sport };\n if (options.market) params.market = options.market;\n if (options.bookmaker) params.bookmaker = options.bookmaker;\n if (options.since) params.since = options.since;\n if (options.until) params.until = options.until;\n\n const url = this._buildUrl(\"/exports/resolved-props\", params);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method: \"GET\",\n headers: { \"X-API-Key\": this.apiKey },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n throw new AuthError();\n }\n if (resp.status === 403) {\n const d = await readDetail(resp, \"Pro tier required\");\n throw new PropLineError(403, d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n const buf = new Uint8Array(await resp.arrayBuffer());\n if (options.outPath) {\n await writeFile(options.outPath, buf);\n return options.outPath;\n }\n return buf;\n }\n\n /**\n * Bulk CSV export of the full line-movement time-series.\n *\n * One row per (outcome, snapshot): every recorded odds snapshot (price +\n * line, per book, including period markets), not just the closing line.\n * This is the raw tick history no subscription tier can pull in bulk —\n * Pro/Streaming get per-event {@link getOddsHistory} only; this bulk\n * firehose is exclusive to the one-time Historical Backfill pass and\n * Enterprise.\n *\n * A full archive runs to gigabytes per sport — page month by month with\n * `since`/`until`. If `outPath` is provided, streams to disk and resolves\n * to the path; otherwise resolves to the CSV bytes as a `Uint8Array`.\n *\n * @example\n * ```ts\n * await client.exportOddsHistory({\n * sport: \"baseball_mlb\",\n * since: \"2026-04-01T00:00:00Z\",\n * until: \"2026-05-01T00:00:00Z\",\n * outPath: \"./mlb-line-history-apr.csv\",\n * });\n * ```\n */\n async exportOddsHistory(\n options: ExportOddsHistoryOptions & { outPath: string }\n ): Promise<string>;\n async exportOddsHistory(\n options: ExportOddsHistoryOptions & { outPath?: undefined }\n ): Promise<Uint8Array>;\n async exportOddsHistory(\n options: ExportOddsHistoryOptions\n ): Promise<string | Uint8Array> {\n const params: Record<string, string | undefined> = { sport: options.sport };\n if (options.market) params.market = options.market;\n if (options.bookmaker) params.bookmaker = options.bookmaker;\n if (options.since) params.since = options.since;\n if (options.until) params.until = options.until;\n\n const url = this._buildUrl(\"/exports/odds-history\", params);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method: \"GET\",\n headers: { \"X-API-Key\": this.apiKey },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n throw new AuthError();\n }\n if (resp.status === 403) {\n const d = await readDetail(\n resp,\n \"Historical Backfill pass or Enterprise required\"\n );\n throw new PropLineError(403, d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n const buf = new Uint8Array(await resp.arrayBuffer());\n if (options.outPath) {\n await writeFile(options.outPath, buf);\n return options.outPath;\n }\n return buf;\n }\n\n // ------------------------------------------------------------------\n // Webhooks (Streaming tier)\n // ------------------------------------------------------------------\n\n /**\n * Register a webhook subscription. Streaming tier only.\n *\n * The returned object includes the full signing `secret` — this is the\n * ONLY time it's revealed. Store it securely.\n */\n createWebhook(options: CreateWebhookOptions): Promise<Webhook> {\n return this._request<Webhook>(\"POST\", \"/webhooks\", {\n body: webhookBody(options),\n });\n }\n\n /** List your webhook subscriptions. Secrets are masked. */\n listWebhooks(): Promise<Webhook[]> {\n return this._request<Webhook[]>(\"GET\", \"/webhooks\");\n }\n\n /** Get a single webhook subscription. Secret is masked. */\n getWebhook(webhookId: number): Promise<Webhook> {\n return this._request<Webhook>(\"GET\", `/webhooks/${webhookId}`);\n }\n\n /** Update fields on a webhook. Only supplied fields are changed. */\n updateWebhook(webhookId: number, options: UpdateWebhookOptions): Promise<Webhook> {\n return this._request<Webhook>(\"PATCH\", `/webhooks/${webhookId}`, {\n body: webhookBody(options),\n });\n }\n\n /** Delete a webhook (cascades its delivery history). */\n deleteWebhook(webhookId: number): Promise<{ ok: boolean } | unknown> {\n return this._request(\"DELETE\", `/webhooks/${webhookId}`);\n }\n\n /** Queue a sample `test` payload to the webhook's URL. */\n testWebhook(webhookId: number): Promise<unknown> {\n return this._request(\"POST\", `/webhooks/${webhookId}/test`);\n }\n\n /** Last 50 (default) delivery attempts for a webhook. */\n listWebhookDeliveries(\n webhookId: number,\n options: ListWebhookDeliveriesOptions = {}\n ): Promise<WebhookDelivery[]> {\n return this._request<WebhookDelivery[]>(\n \"GET\",\n `/webhooks/${webhookId}/deliveries`,\n { params: { limit: options.limit ?? 50, before_id: options.beforeId } }\n );\n }\n\n /**\n * Re-read this subscription's events in order, from a cursor.\n *\n * Answers \"my endpoint was down — what did I miss?\". Every delivery carries\n * an `X-PropLine-Sequence` header: a counter monotonic *within your\n * subscription*. Store the highest one you processed and pass it as\n * `sinceSeq`.\n *\n * Do NOT use `X-PropLine-Delivery` as the cursor — that id is global across\n * every subscription, so its gaps are other customers' traffic.\n *\n * Events come back oldest-first (the opposite of `listWebhookDeliveries`,\n * which is a newest-first debugging log). Page by passing `next_seq` back\n * as `sinceSeq` while `has_more` is true.\n *\n * **Check `truncated`.** True means events after your cursor have aged out\n * of retention (2 days, max 5,000 deliveries per subscription) and are gone\n * — resync from the REST endpoints instead of assuming you are current.\n *\n * Does not count against your daily request quota.\n */\n replayWebhookEvents(\n webhookId: number,\n options: ReplayWebhookEventsOptions = {}\n ): Promise<ReplayPage> {\n return this._request<ReplayPage>(\n \"GET\",\n `/webhooks/${webhookId}/replay`,\n { params: { since_seq: options.sinceSeq ?? 0, limit: options.limit ?? 100 } }\n );\n }\n\n /**\n * Stream a websocket subscription as an async iterable.\n *\n * ```ts\n * for await (const ev of client.stream({ webhookId: 12, sinceSeq: 4180 })) {\n * console.log(ev.seq, ev.event_type, ev.data);\n * }\n * ```\n *\n * The subscription must have been created with `transport: \"websocket\"`.\n * Same events, same filters, same `seq` as an HTTP webhook — one\n * subscription, different transport.\n *\n * **Reconnects automatically and resumes from the last `seq` it saw**, which\n * is the whole point of the sequence: a dropped connection does not become a\n * gap in your data. Set `reconnect: false` to get a single connection that\n * ends when the socket closes.\n *\n * If the server reports `truncated` — events after your cursor aged out of\n * retention and are gone — `onTruncated` fires. Handle it: that is the one\n * case where the stream cannot make you whole and you should resync from the\n * REST endpoints.\n */\n async *stream(options: StreamOptions): AsyncGenerator<ReplayEvent, void, void> {\n // ⚠️ Streaming has its OWN origin and does NOT derive from baseUrl.\n // Deriving it (the first cut of this method did) sends the socket to\n // api.prop-line.com — which serves /v1/stream too, from the same ASGI\n // app, so it WORKS and nothing complains. It just parks a persistent\n // connection on the REST tier's event loop, which is precisely what the\n // separate websocket tier exists to prevent. Caught 2026-08-31 by reading\n // the machine id in ws_connections and finding an app-tier machine.\n // Override with `wsUrl` only for self-hosted or local development.\n const wsBase = (options.wsUrl ?? DEFAULT_WS_URL)\n .replace(/^http:/, \"ws:\")\n .replace(/^https:/, \"wss:\")\n .replace(/\\/v1\\/?$/, \"\")\n .replace(/\\/$/, \"\");\n const url = `${wsBase}/v1/stream`;\n let cursor = options.sinceSeq ?? 0;\n let attempt = 0;\n\n for (;;) {\n const queue: ReplayEvent[] = [];\n let notify: (() => void) | null = null;\n let closed: Error | null = null;\n let opened = false;\n\n const ws = new WebSocket(url);\n const wake = () => { const n = notify; notify = null; n?.(); };\n\n ws.addEventListener(\"open\", () => {\n opened = true;\n ws.send(JSON.stringify({\n type: \"auth\",\n api_key: this.apiKey,\n webhook_id: options.webhookId,\n since_seq: cursor,\n }));\n });\n ws.addEventListener(\"message\", (e: MessageEvent) => {\n let msg: Record<string, unknown>;\n try { msg = JSON.parse(String(e.data)); } catch { return; }\n if (msg.type === \"ready\") {\n attempt = 0; // a successful handshake resets backoff\n if (msg.truncated) options.onTruncated?.(msg as unknown as ReplayPage);\n options.onReady?.(msg as unknown as ReplayPage);\n } else if (msg.type === \"event\") {\n queue.push(msg as unknown as ReplayEvent);\n wake();\n }\n // \"ping\" needs no reply — it exists to keep idle proxies from closing.\n });\n ws.addEventListener(\"close\", (e: CloseEvent) => {\n // 4401/4403/4404/4400 are terminal: retrying cannot fix a bad key, a\n // tier without access, or a subscription that is not yours. Only\n // transport failures and 4429 are worth reconnecting for.\n const terminal = [4400, 4401, 4403, 4404].includes(e.code);\n closed = new PropLineError(\n e.code,\n `stream closed${e.reason ? `: ${e.reason}` : \"\"}`,\n );\n (closed as PropLineError & { terminal?: boolean }).terminal = terminal;\n wake();\n });\n ws.addEventListener(\"error\", () => {\n if (!closed) closed = new PropLineError(0, \"stream connection error\");\n wake();\n });\n\n try {\n for (;;) {\n while (queue.length) {\n const ev = queue.shift()!;\n cursor = ev.seq; // advance BEFORE yielding, so a\n yield ev; // consumer `break` still resumes right\n }\n if (closed) break;\n await new Promise<void>((r) => { notify = r; });\n }\n } finally {\n try { ws.close(); } catch { /* already closed */ }\n }\n\n const err = closed as (PropLineError & { terminal?: boolean }) | null;\n if (err?.terminal) throw err;\n if (options.reconnect === false) {\n if (err && !opened) throw err;\n return;\n }\n // Capped exponential backoff. Without the cap a long outage would have\n // clients reconnecting hours apart; without backoff they would stampede.\n const delayMs = Math.min(30_000, 500 * 2 ** attempt++);\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n\n /**\n * Grade placed bets against their closing lines (CLV).\n *\n * Closing line value is the only durable proxy for whether a bettor has\n * edge: did the price you took beat the number the market settled on?\n * Send the bets you actually placed; each comes back with its closing\n * price, the de-vigged closing fair probability, CLV, and — once the\n * game settles — the graded result and actual stat value.\n *\n * Stateless: nothing is stored server-side.\n *\n * **Two CLV numbers are returned deliberately.** `clv_pct` is\n * price-vs-price — familiar and quotable, but vig-blind, so it flatters\n * a bet taken on the juicy side of a wide market. `ev_vs_close_pct`\n * scores your price against the DE-VIGGED close and is the honest one;\n * on a real bet the two came out +6.52% and +0.08%.\n *\n * The de-vig anchors to the **sharpest book quoting that line at close**\n * (`fair_source`), not the book you bet at — de-vigging your own book\n * always returns a negative number, because you paid its hold.\n *\n * Bets whose event has not started carry `closing_is_final: false`, land\n * in `summary.pending`, and are excluded from the summary averages:\n * before kickoff the \"closing\" price is just the latest price.\n *\n * Matching is fail-closed — a bet that cannot be pinned to exactly one\n * stored outcome returns `matched: false` with an `unmatched_reason`\n * rather than a confident wrong match. Max 500 bets per request.\n *\n * Hobby+ required; free tier receives the structure with numbers nulled.\n *\n * @example\n * const res = await client.gradeClv([{\n * ref: \"b1\",\n * sport_key: \"baseball_mlb\",\n * event_id: 150791,\n * market: \"batter_hits_runs_rbis\",\n * bookmaker: \"lowvig\",\n * selection: \"Drake Baldwin\",\n * side: \"Under\",\n * point: 0.5,\n * price: 145,\n * stake: 1,\n * }]);\n * console.log(res.summary.avg_ev_vs_close_pct);\n */\n gradeClv(bets: ClvBetInput[]): Promise<ClvGradeResponse> {\n return this._request<ClvGradeResponse>(\"POST\", \"/clv/grade\", { body: bets });\n }\n\n /**\n * Price a same-game parlay at the book's own correlated odds.\n *\n * Send two to ten legs from ONE event and get back the book's own price\n * for that exact slip — what a FanDuel customer would be offered for it\n * at that moment, not a model of it — beside `independent_price` (the\n * product of the live single-leg prices) and `correlation_factor`\n * (their ratio: the correlation the book is charging, below 1, or\n * paying, above 1, for). Measured live: Cardinals ML +205 × Freddie\n * Freeman to record a hit -260 → SGP +592 against an independent +322.\n *\n * Book-native. `bookmaker` is \"fanduel\" (its own betslip pricer) or\n * \"betonlineag\" / \"lowvig\" (the Sportcast engine both Chico brands embed,\n * same builder price); an unsupported value is a 422.\n *\n * Legs are named exactly as `/odds` names an outcome (market, name,\n * description, point, period), or by `book_outcome_id` from\n * `includeBookIds: true`. Matching is fail-closed — a leg that does not\n * pin to exactly one stored outcome is a 422 `leg_unmatched` naming the\n * leg. `quoted: false` means the book will not offer that combination as\n * a same-game parlay; refused legs carry the book's own `failure_code`.\n * Quotes for an identical slip are shared for 15 seconds.\n *\n * Hobby+ required; free tier receives the matched legs with every price\n * nulled and never triggers a book call.\n *\n * @example\n * const q = await client.priceSgp(\"baseball_mlb\", 150791, [\n * { market: \"h2h\", name: \"St. Louis Cardinals\" },\n * { market: \"batter_1plus_hits\", name: \"Freddie Freeman\", description: \"Freddie Freeman\" },\n * ]);\n * console.log(q.sgp_price, q.independent_price, q.correlation_factor);\n */\n priceSgp(\n sportKey: string,\n eventId: number | string,\n legs: SgpLegInput[],\n bookmaker = \"fanduel\",\n ): Promise<SgpQuoteResponse> {\n return this._request<SgpQuoteResponse>(\n \"POST\",\n `/sports/${encodeURIComponent(sportKey)}/events/${encodeURIComponent(String(eventId))}/sgp`,\n { body: { bookmaker, legs } },\n );\n }\n\n /**\n * Verify that an inbound webhook delivery was signed by PropLine.\n *\n * Compares HMAC-SHA256(secret, `${timestamp}.` + body) against the\n * `X-PropLine-Signature` header in constant time.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * app.post(\"/hooks/propline\", express.raw({ type: \"*\\/*\" }), (req, res) => {\n * const ok = PropLine.verifySignature({\n * secret: process.env.WEBHOOK_SECRET!,\n * timestamp: req.header(\"X-PropLine-Timestamp\")!,\n * body: req.body, // raw Buffer\n * signature: req.header(\"X-PropLine-Signature\")!,\n * });\n * if (!ok) return res.status(401).end();\n * // ...\n * });\n * ```\n */\n static verifySignature(options: VerifySignatureOptions): boolean {\n const { secret, timestamp, body, signature } = options;\n const bodyBuf =\n typeof body === \"string\"\n ? Buffer.from(body, \"utf8\")\n : body instanceof Buffer\n ? body\n : Buffer.from(body);\n const message = Buffer.concat([Buffer.from(`${timestamp}.`, \"utf8\"), bodyBuf]);\n const expected = createHmac(\"sha256\", secret).update(message).digest(\"hex\");\n if (expected.length !== signature.length) return false;\n try {\n return timingSafeEqual(Buffer.from(expected, \"hex\"), Buffer.from(signature, \"hex\"));\n } catch {\n return false;\n }\n }\n}\n\nfunction webhookBody(options: CreateWebhookOptions | UpdateWebhookOptions): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n const map: Array<[keyof (CreateWebhookOptions & UpdateWebhookOptions), string]> = [\n [\"url\", \"url\"],\n [\"events\", \"events\"],\n [\"filterSportKey\", \"filter_sport_key\"],\n [\"filterEventId\", \"filter_event_id\"],\n [\"filterMarketKey\", \"filter_market_key\"],\n [\"filterPlayerName\", \"filter_player_name\"],\n [\"filterBookmakerKey\", \"filter_bookmaker_key\"],\n [\"minPriceChangePct\", \"min_price_change_pct\"],\n [\"minSteamScore\", \"min_steam_score\"],\n [\"minBooksAgreeing\", \"min_books_agreeing\"],\n [\"batchMax\", \"batch_max\"],\n [\"active\", \"active\"],\n ];\n for (const [src, dst] of map) {\n const v = (options as Record<string, unknown>)[src as string];\n if (v !== undefined) body[dst] = v;\n }\n return body;\n}\n\ninterface ReadDetailResult {\n message: string;\n info?: PropLineErrorInfo;\n}\n\nasync function readDetail(\n resp: Response,\n fallback: string,\n): Promise<ReadDetailResult> {\n try {\n const text = await resp.text();\n if (!text) return { message: fallback };\n try {\n const json = JSON.parse(text) as { detail?: unknown };\n if (typeof json.detail === \"string\") return { message: json.detail };\n if (json.detail && typeof json.detail === \"object\") {\n const info = json.detail as PropLineErrorInfo;\n return {\n message: typeof info.message === \"string\" ? info.message : text,\n info,\n };\n }\n } catch {\n // not JSON\n }\n return { message: text || fallback };\n } catch {\n return { message: fallback };\n }\n}\n","/**\n * PropLine — Node/TypeScript SDK for the PropLine player props API.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * const client = new PropLine(\"your_api_key\");\n * const events = await client.getEvents(\"basketball_nba\");\n * const odds = await client.getOdds(\"basketball_nba\", {\n * eventId: events[0].id,\n * markets: [\"player_points\", \"player_rebounds\"],\n * });\n * ```\n */\n\nexport { PropLine } from \"./client.js\";\nexport {\n PropLineError,\n AuthError,\n RateLimitError,\n} from \"./client.js\";\nexport type {\n PropLineErrorInfo,\n PropLineOptions,\n QuotaStatus,\n GetOddsOptions,\n GetOddsHistoryOptions,\n GetOddsClosingOptions,\n PeriodFilter,\n GetScoresOptions,\n GetDfsPayoutsOptions,\n GetMlbGrandSalamiOptions,\n GetNhlDailyGoalsTotalOptions,\n GetStatsOptions,\n GetResultsOptions,\n GetPlayerHistoryOptions,\n GetPlayerGamesOptions,\n GetPlayerTrendsOptions,\n GetEventEvOptions,\n GetEventProjectionsOptions,\n GetEventBestLineOptions,\n CalcEventEvOptions,\n ExportResolvedPropsOptions,\n ExportOddsHistoryOptions,\n WebhookEventType,\n CreateWebhookOptions,\n UpdateWebhookOptions,\n ListWebhookDeliveriesOptions,\n ReplayWebhookEventsOptions,\n StreamOptions,\n VerifySignatureOptions,\n} from \"./client.js\";\n\nexport type {\n ClvBetInput,\n ClvGradedBet,\n ClvSummary,\n ClvGradeResponse,\n SgpLegInput,\n SgpLegQuote,\n SgpQuoteResponse,\n Sport,\n Event,\n Outcome,\n ResolvedOutcome,\n Market,\n Bookmaker,\n OddsResponse,\n MarketSummary,\n OutcomeSnapshot,\n OddsHistoryOutcome,\n OddsHistoryMarket,\n OddsHistoryBookmaker,\n OddsHistoryResponse,\n ClosingOutcome,\n ClosingMarket,\n ClosingBookmaker,\n OddsClosingResponse,\n ScoreEvent,\n MlbGrandSalamiBook,\n MlbGrandSalamiResponse,\n NhlDailyGoalsTotalBook,\n NhlDailyGoalsTotalResponse,\n ResolutionSummary,\n ResolutionSummarySport,\n ResolutionSummaryMarket,\n PlayerStat,\n StatsResponse,\n WeatherInfo,\n ContextResponse,\n MovementOutcome,\n MovementMarket,\n MovementBookmaker,\n SteamMove,\n MovementResponse,\n ResultsMarket,\n ResultsResponse,\n PlayerHistoryEntry,\n PlayerHistoryResponse,\n HitRateSplit,\n TrendStreak,\n TrendLastGame,\n PlayerMarketTrend,\n PlayerGame,\n PlayerGameLog,\n PlayerTrends,\n EvOutcome,\n EvLine,\n EventEvResponse,\n EventProjectionsResponse,\n ProjectionRow,\n EventEvCalcResponse,\n BestPrice,\n BestLineSide,\n BestLine,\n EventBestLineResponse,\n FuturesOutcome,\n FuturesMarket,\n FuturesEvent,\n Webhook,\n WebhookDelivery,\n ReplayEvent,\n ReplayPage,\n DfsPayoutTier,\n DfsPlayPayout,\n DfsPayoutsResponse,\n} from \"./types.js\";\n\n/** String constants for bookmaker keys in odds responses. */\nexport const Bookmakers = {\n BOVADA: \"bovada\",\n DRAFTKINGS: \"draftkings\",\n FANDUEL: \"fanduel\",\n PINNACLE: \"pinnacle\",\n UNIBET: \"unibet\",\n UNDERDOG: \"underdog\",\n KALSHI: \"kalshi\",\n POLYMARKET: \"polymarket\",\n PRIZEPICKS: \"prizepicks\",\n} as const;\n\nexport type BookmakerKey = (typeof Bookmakers)[keyof typeof Bookmakers];\n\nexport const VERSION = \"0.48.2\";\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/client.ts","../src/index.ts"],"sourcesContent":["import { createHmac, timingSafeEqual } from \"node:crypto\";\nimport { writeFile } from \"node:fs/promises\";\n\nimport type {\n ClvBetInput,\n ClvGradeResponse,\n SgpLegInput,\n SgpQuoteResponse,\n Sport,\n Event as PropLineEvent,\n OddsResponse,\n MarketSummary,\n OddsHistoryResponse,\n OddsClosingResponse,\n ScoreEvent,\n MlbGrandSalamiResponse,\n NhlDailyGoalsTotalResponse,\n ResolutionSummary,\n StatsResponse,\n ContextResponse,\n MovementResponse,\n ResultsResponse,\n PlayerGameLog,\n PlayerHistoryResponse,\n PlayerTrends,\n EventEvResponse,\n EventProjectionsResponse,\n EventEvCalcResponse,\n EventBestLineResponse,\n FuturesEvent,\n Webhook,\n WebhookDelivery,\n ReplayEvent,\n ReplayPage,\n DfsPayoutsResponse,\n} from \"./types.js\";\n\n/** Options for {@link PropLineClient.getDfsPayouts}. */\nexport interface GetDfsPayoutsOptions {\n /** DFS platform. Only \"prizepicks\" today. */\n platform?: string;\n /**\n * Assumed per-leg win probability in [0, 1]. When supplied, each play\n * also carries `expected_return` (per $1) and `is_plus_ev` at that rate.\n */\n legWinProb?: number;\n}\n\n/**\n * Structured error body returned by gated/throttled endpoints\n * (see https://prop-line.com/docs#errors). Branch on `error` — the\n * codes are stable — and follow the URLs instead of parsing prose.\n */\nexport interface PropLineErrorInfo {\n /** Stable machine-readable code, e.g. \"upgrade_required\", \"daily_limit_exceeded\". */\n error?: string;\n /** Human-readable sentence. */\n message?: string;\n /** Cheapest tier that unlocks a gated feature (403s). */\n required_tier?: string;\n /** Where to unlock it — pre-filled one-click URL on daily-cap 429s. */\n upgrade_url?: string;\n docs_url?: string;\n signup_url?: string;\n backfill_url?: string;\n /** Burst-limit backoff hint (429s). */\n retry_after_seconds?: number;\n /** Daily-cap 429s: recommended next plan incl. its own upgrade_url. */\n recommended?: { plan?: string; upgrade_url?: string; [key: string]: unknown };\n [key: string]: unknown;\n}\n\n/** Base error for all PropLine API failures. */\nexport class PropLineError extends Error {\n readonly statusCode: number;\n /** Human-readable detail message. */\n readonly detail: string;\n /** Structured error body, when the API returned one. */\n readonly info?: PropLineErrorInfo;\n\n constructor(statusCode: number, detail: string, info?: PropLineErrorInfo) {\n super(`[${statusCode}] ${detail}`);\n this.name = \"PropLineError\";\n this.statusCode = statusCode;\n this.detail = detail;\n this.info = info;\n Object.setPrototypeOf(this, new.target.prototype);\n }\n\n /** Stable machine-readable code (e.g. \"upgrade_required\"), if present. */\n get errorCode(): string | undefined {\n return this.info?.error;\n }\n\n /** The URL that unlocks a gated feature or lifts a cap, if present. */\n get upgradeUrl(): string | undefined {\n return this.info?.upgrade_url ?? this.info?.recommended?.upgrade_url;\n }\n}\n\n/** Thrown when the API key is missing or invalid (HTTP 401). */\nexport class AuthError extends PropLineError {\n constructor(detail = \"Invalid API key\", info?: PropLineErrorInfo) {\n super(401, detail, info);\n this.name = \"AuthError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\n/** Thrown when the daily request limit is exceeded (HTTP 429). */\nexport class RateLimitError extends PropLineError {\n constructor(detail = \"Rate limit exceeded\", info?: PropLineErrorInfo) {\n super(429, detail, info);\n this.name = \"RateLimitError\";\n Object.setPrototypeOf(this, new.target.prototype);\n }\n}\n\nexport interface PropLineOptions {\n /** API base URL. Default: `https://api.prop-line.com/v1`. */\n baseUrl?: string;\n /** Request timeout in milliseconds. Default: 15000. */\n timeoutMs?: number;\n /** Custom fetch implementation. Defaults to global `fetch` (Node 18+). */\n fetch?: typeof fetch;\n}\n\n/**\n * Game-period filter. String of canonical codes, optionally\n * comma-separated, or the sentinel `\"all\"`. Omitted = full-game\n * markets only (backwards-compatible default).\n *\n * \"q1\" — 1st quarter\n * \"q1,q2\" — 1st and 2nd quarters\n * [\"q1\",\"q2\"] — same, as an array\n * \"h1\" — 1st half\n * \"p1\"|\"p2\"|\"p3\" — hockey periods\n * \"i6\" — 6th inning\n * \"f3\"|\"f5\"|\"f7\" — first N innings\n * \"all\" — every period including full game\n */\nexport type PeriodFilter = string | string[];\n\nexport interface GetOddsOptions {\n /** Specific event ID to get odds (with player props) for. Omit for bulk odds. */\n eventId?: number | string;\n /**\n * Market keys to filter by. If omitted, the bulk `/odds` endpoint\n * defaults to `h2h` and the per-event `/odds` endpoint defaults to\n * `h2h,spreads,totals` — game-line markets every book carries across\n * every sport. Pass an explicit list to fetch player props (e.g.\n * `[\"pitcher_strikeouts\", \"batter_home_runs\"]` for MLB,\n * `[\"player_points\", \"player_rebounds\"]` for NBA).\n */\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /**\n * Bookmaker key(s) to restrict the response to (e.g. `\"draftkings\"` or\n * `[\"draftkings\", \"fanduel\"]`). Omitted = all books. Same parameter name\n * as the-odds-api.\n */\n bookmakers?: string | string[];\n /**\n * When true, each bookmaker block carries a `link` — that book's\n * public event-page URL (plain navigation, no affiliate tagging),\n * so your UI can click out from a line to the book. Links ship for\n * Bovada, DraftKings, FanDuel, BetMGM, Kalshi, Polymarket and\n * Smarkets; other books return null. Maps to the\n * the-odds-api-compatible `includeLinks=true` query param.\n */\n includeLinks?: boolean;\n /**\n * When true, each bookmaker block carries a `book_event_id` and each\n * outcome a `book_outcome_id` — that book's OWN identifiers for the\n * event and the priced selection. Use these to join PropLine rows onto\n * a book's native feed by id instead of matching on team names,\n * players and lines. Kalshi ships both (the event ticker and the\n * per-contract market ticker, e.g. `KXMLBGAME-26AUG08NYYBOS-NYY`);\n * most other books ship an event id. Books without a stable id return\n * null.\n *\n * NB a two-sided market can share ONE `book_outcome_id` across both\n * legs — a Kalshi contract is binary, so Over and Under are its YES\n * and NO sides. The id identifies the contract; the outcome's `name`\n * says which side.\n *\n * PropLine-specific (`includeBookIds=true`); the-odds-api has no\n * equivalent.\n */\n includeBookIds?: boolean;\n}\n\nexport interface GetOddsHistoryOptions {\n markets?: string[];\n /** ISO timestamp; only include snapshots at or after this time. Mutually exclusive with `relativeFrom`. */\n from?: string;\n /** ISO timestamp; only include snapshots at or before this time. Mutually exclusive with `relativeTo`. */\n to?: string;\n /** Offset relative to commence_time, e.g. \"-3h\", \"-30m\", \"-90s\". Mutually exclusive with `from`. */\n relativeFrom?: string;\n /** Offset relative to commence_time, e.g. \"-1m\" or \"0\" for commence_time itself. Mutually exclusive with `to`. */\n relativeTo?: string;\n /** Downsample to one snapshot per bucket. Latest snapshot in each bucket wins. */\n interval?: \"30s\" | \"1m\" | \"5m\" | \"15m\" | \"30m\" | \"1h\";\n /** When true, drop snapshots whose (price, point) match the previous one. Opening line is always kept. */\n changesOnly?: boolean;\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nexport interface GetOddsClosingOptions {\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nexport interface GetMovementOptions {\n markets?: string[];\n /** Game-period filter — see `PeriodFilter`. */\n period?: PeriodFilter;\n /** Bookmaker key(s) to restrict the response to. Omitted = all books. */\n bookmakers?: string | string[];\n}\n\nfunction _periodParam(p: PeriodFilter | undefined): string | undefined {\n if (p === undefined) return undefined;\n return typeof p === \"string\" ? p : p.join(\",\");\n}\n\nfunction _bookmakersParam(b: string | string[] | undefined): string | undefined {\n if (b === undefined || b.length === 0) return undefined;\n return typeof b === \"string\" ? b : b.join(\",\");\n}\n\nexport interface GetScoresOptions {\n /** Days back to include (default 3). */\n daysFrom?: number;\n}\n\nexport interface GetMlbGrandSalamiOptions {\n /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */\n date?: string;\n}\n\nexport interface GetNhlDailyGoalsTotalOptions {\n /** YYYY-MM-DD UTC date. Defaults to today (UTC) when omitted. */\n date?: string;\n}\n\nexport interface GetStatsOptions {\n /** Stat types to filter by (e.g. `[\"strikeouts\", \"hits\"]`). */\n statType?: string[];\n}\n\nexport interface GetResultsOptions {\n markets?: string[];\n}\n\nexport interface GetPlayerHistoryOptions {\n /** Market key (e.g. `\"pitcher_strikeouts\"`). Required. */\n market: string;\n /** Restrict to a single bookmaker (e.g. `\"draftkings\"`). */\n bookmaker?: string;\n /** Max entries (1-100). Default 20. */\n limit?: number;\n}\n\nexport interface GetPlayerGamesOptions {\n /** Games to return, 1-100. Default 20. */\n limit?: number;\n /**\n * Head-to-head filter. Accepts a full name, nickname or abbreviation\n * (\"Boston Red Sox\", \"Red Sox\", \"BOS\"). The limit applies AFTER this\n * filter, so `{ opponent: \"BOS\", limit: 10 }` is the last 10 MEETINGS,\n * not the Boston games among the last 10 games. Not capped to the\n * current season.\n */\n opponent?: string;\n /**\n * Stat name(s) to return; omit for all. Vocabulary is per-sport —\n * see https://prop-line.com/docs#stats\n */\n statType?: string | string[];\n}\n\nexport interface GetPlayerTrendsOptions {\n /** Market key (e.g. `\"batter_total_bases\"`). Omit for all markets. */\n market?: string;\n /**\n * PrizePicks pick-em flavor to compute trends against: `\"standard\"`\n * (default market line), `\"goblin\"`, or `\"demon\"`. When set, the trend is\n * computed against that flavor's PrizePicks line only. Omit for the default\n * cross-book behavior. Flavor tagging began 2026-06-16.\n */\n dfsOddsType?: \"standard\" | \"goblin\" | \"demon\";\n}\n\nexport interface GetEventProjectionsOptions {\n /** Optional market-key filter (comma-separated string or array). */\n markets?: string | string[];\n}\n\nexport interface GetEventEvOptions {\n /**\n * Optional market filter. Pass a single comma-separated string or an\n * array of market keys (e.g. `[\"pitcher_strikeouts\", \"batter_hits\"]`).\n * Omit to evaluate every market on the event.\n */\n markets?: string | string[];\n /**\n * Optional bookmaker filter (the-odds-api-compatible). Pass a\n * comma-separated string or an array of book keys (e.g.\n * `[\"draftkings\", \"fanduel\"]`) to price only the books you hold\n * accounts at. Omit for every book.\n *\n * This narrows the PRICES, never the fair-line anchor:\n * `bookmakers: [\"draftkings\"]` still returns DraftKings EV% measured\n * against Pinnacle. Lines where none of your books quote a price are\n * omitted.\n */\n bookmakers?: string | string[];\n}\n\nexport interface GetEventBestLineOptions {\n /**\n * Optional market filter. Pass a single comma-separated string or an\n * array of market keys (e.g. `[\"pitcher_strikeouts\", \"h2h\"]`). Omit\n * to include every market on the event.\n */\n markets?: string | string[];\n /**\n * Optional bookmaker filter (the-odds-api-compatible). Pass a\n * comma-separated string or an array of book keys (e.g.\n * `[\"draftkings\", \"fanduel\"]`) to shop only the books you hold\n * accounts at. Omit for all comparable books.\n */\n bookmakers?: string | string[];\n /**\n * When true, every price row carries a `link` — that book's public\n * event-page URL, the click-out for \"go bet this\". Books without a\n * verified URL template return null. Links appear on free-tier\n * redacted responses too (navigation isn't the paid data).\n */\n includeLinks?: boolean;\n}\n\nexport interface CalcEventEvOptions {\n /** Market key — h2h / spreads / totals / pitcher_strikeouts / etc. */\n market: string;\n /** Outcome name. Team for h2h/spreads; \"Over\" or \"Under\" for totals/props. */\n name: string;\n /** American odds at your book, e.g. -118 or 145. */\n price: number;\n /** Line/point for spreads, totals, player props. Sign matters for spreads (-1.5 favorite). Omit for h2h. */\n point?: number;\n /** Player name for player-prop markets. Omit for game-line markets. */\n description?: string;\n}\n\nexport interface ExportResolvedPropsOptions {\n /** Sport key (e.g. `\"baseball_mlb\"`). Required. */\n sport: string;\n /** Optional market filter. */\n market?: string;\n /** Optional bookmaker filter. */\n bookmaker?: string;\n /** ISO datetime lower bound on `resolved_at`. */\n since?: string;\n /** ISO datetime upper bound on `resolved_at`. */\n until?: string;\n /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */\n outPath?: string;\n}\n\nexport interface ExportOddsHistoryOptions {\n /** Sport key (e.g. `\"baseball_mlb\"`). Required. */\n sport: string;\n /** Optional market filter. */\n market?: string;\n /** Optional bookmaker filter. */\n bookmaker?: string;\n /** ISO datetime lower bound on `recorded_at`. */\n since?: string;\n /** ISO datetime upper bound on `recorded_at`. */\n until?: string;\n /** If set, stream the CSV to this path and resolve to the path. Otherwise resolve to the CSV bytes. */\n outPath?: string;\n}\n\n/**\n * Webhook event types. `steam` = cross-book sharp-money alert.\n * `market_suspended` = a book took a market off the board pregame (one\n * delivery per (book, event, player) withdrawal, with `books_agreeing`).\n */\nexport type WebhookEventType = \"line_movement\" | \"resolution\" | \"steam\" | \"market_suspended\";\n\nexport interface CreateWebhookOptions {\n /** HTTPS endpoint to receive POSTed events. Required. */\n url: string;\n /** Event types to subscribe to. Default: all. */\n events?: WebhookEventType[];\n filterSportKey?: string;\n filterEventId?: number;\n filterMarketKey?: string;\n filterPlayerName?: string;\n /**\n * Comma-separated book keys, same vocabulary as the `?bookmakers=`\n * query param (e.g. \"draftkings,fanduel\"). Unset = all books; unknown\n * keys match nothing. Applies to line_movement, resolution and\n * market_suspended; steam is cross-book and unaffected.\n */\n filterBookmakerKey?: string;\n /** Minimum % change in American odds to fire a line_movement. Point-only shifts always pass. */\n minPriceChangePct?: number;\n /** Minimum 0-100 steam score to fire a `steam` event. Null = detector's global floor. */\n minSteamScore?: number;\n /**\n * `market_suspended` only: how many books must have pulled the same\n * player/market on the same event before you are told. Unset/1 = every\n * drop (right if you price off one book); 3+ = corroborated late\n * scratches only. Every payload carries `books_agreeing` regardless.\n */\n minBooksAgreeing?: number;\n /**\n * Batched delivery opt-in (1-500): up to N events per POST as a signed\n * envelope `{\"batch\": true, \"event_type\": ..., \"count\": N, \"events\":\n * [{\"delivery_id\": ..., \"data\": <per-event payload>}, ...]}` with an\n * `X-PropLine-Batch` header. Strongly recommended for high-volume\n * subscriptions — one POST per event caps your delivery rate at your\n * endpoint's response time. 0 reverts to per-event. JSON format only.\n */\n batchMax?: number;\n}\n\nexport interface UpdateWebhookOptions {\n url?: string;\n events?: WebhookEventType[];\n filterSportKey?: string;\n filterEventId?: number;\n filterMarketKey?: string;\n filterPlayerName?: string;\n /**\n * Comma-separated book keys, same vocabulary as the `?bookmakers=`\n * query param (e.g. \"draftkings,fanduel\"). Unset = all books; unknown\n * keys match nothing. Applies to line_movement, resolution and\n * market_suspended; steam is cross-book and unaffected.\n */\n filterBookmakerKey?: string;\n minPriceChangePct?: number;\n minSteamScore?: number;\n minBooksAgreeing?: number;\n /** Batched delivery (see CreateWebhookOptions.batchMax). 0 = per-event. */\n batchMax?: number;\n active?: boolean;\n}\n\nexport interface ListWebhookDeliveriesOptions {\n /** Max deliveries to return. Default 50, max 200. */\n limit?: number;\n /**\n * Page backwards: pass the smallest `id` from the previous page to get\n * the next-older page. Pages are newest-first; a page shorter than\n * `limit` is the last one.\n */\n beforeId?: number;\n}\n\nexport interface ReplayWebhookEventsOptions {\n /**\n * Read events after this cursor — the highest `X-PropLine-Sequence` you\n * have processed. Defaults to 0 (from the oldest retained event).\n */\n sinceSeq?: number;\n /** Max events per page. Default 100, max 500. */\n limit?: number;\n}\n\nexport interface StreamOptions {\n /** Subscription to stream. Must be transport=\"websocket\". */\n webhookId: number;\n /** Resume point — the last `seq` you processed. Default 0. */\n sinceSeq?: number;\n /** Auto-reconnect and resume from the last seq. Default true. */\n reconnect?: boolean;\n /** Override the websocket origin (default: derived from baseUrl). */\n wsUrl?: string;\n /** Called on every successful handshake with the `ready` frame. */\n onReady?: (ready: ReplayPage) => void;\n /**\n * Called when the server reports events after your cursor have aged out of\n * retention. This is the one case the stream cannot make you whole —\n * resync from the REST endpoints.\n */\n onTruncated?: (ready: ReplayPage) => void;\n}\n\nexport interface VerifySignatureOptions {\n /** Webhook signing secret (returned once from `createWebhook`). */\n secret: string;\n /** Value of the `X-PropLine-Timestamp` header. */\n timestamp: string;\n /** Raw request body. */\n body: Uint8Array | Buffer | string;\n /** Value of the `X-PropLine-Signature` header. */\n signature: string;\n}\n\n/**\n * Live daily-quota state, parsed from the `X-Daily-*` headers the API\n * returns on every authenticated response.\n */\nexport interface QuotaStatus {\n /** Your tier's daily request cap. */\n limit: number;\n /** Requests used today (including the request that produced this). */\n used: number;\n /** Requests left before the cap. */\n remaining: number;\n /** Unix seconds when the quota resets (00:00 UTC — a hard reset, not a rolling window). */\n resetEpoch: number;\n /** Quota reset time as a `Date`. */\n resetAt: Date;\n}\n\nconst DEFAULT_BASE_URL = \"https://api.prop-line.com/v1\";\n\n// Streaming origin. Separate Fly app, separate machines — see stream().\nconst DEFAULT_WS_URL = \"wss://ws.prop-line.com\";\nconst DEFAULT_TIMEOUT_MS = 15_000;\n\n/**\n * Client for the PropLine player props API.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * const client = new PropLine(\"your_api_key\");\n * const sports = await client.getSports();\n * ```\n */\nexport class PropLine {\n readonly apiKey: string;\n readonly baseUrl: string;\n readonly timeoutMs: number;\n /**\n * Daily-quota state from the most recent API response, or `null` before\n * the first request. Updated on every call (including 429s):\n *\n * ```ts\n * await client.getSports();\n * console.log(client.lastQuota?.remaining); // 999\n * ```\n */\n lastQuota: QuotaStatus | null = null;\n private readonly _fetch: typeof fetch;\n\n constructor(apiKey: string, options: PropLineOptions = {}) {\n if (!apiKey) {\n throw new Error(\"PropLine: apiKey is required\");\n }\n this.apiKey = apiKey;\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, \"\");\n this.timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;\n this._fetch = options.fetch ?? globalThis.fetch;\n if (!this._fetch) {\n throw new Error(\n \"PropLine: global fetch is unavailable. Use Node 18+ or pass options.fetch.\"\n );\n }\n }\n\n // ------------------------------------------------------------------\n // Internals\n // ------------------------------------------------------------------\n\n private _buildUrl(path: string, params?: Record<string, string | number | undefined>): string {\n const url = new URL(`${this.baseUrl}${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n if (v !== undefined && v !== null) {\n url.searchParams.set(k, String(v));\n }\n }\n }\n return url.toString();\n }\n\n /**\n * Record the X-Daily-* quota headers when present (absent on\n * unauthenticated errors, e.g. an invalid key's 401).\n */\n private _captureQuota(resp: Response): void {\n const limit = Number(resp.headers.get(\"X-Daily-Limit\"));\n const used = Number(resp.headers.get(\"X-Daily-Used\"));\n const remaining = Number(resp.headers.get(\"X-Daily-Remaining\"));\n const resetEpoch = Number(resp.headers.get(\"X-Daily-Reset\"));\n if (\n resp.headers.has(\"X-Daily-Limit\") &&\n Number.isFinite(limit) &&\n Number.isFinite(used) &&\n Number.isFinite(remaining) &&\n Number.isFinite(resetEpoch)\n ) {\n this.lastQuota = {\n limit,\n used,\n remaining,\n resetEpoch,\n resetAt: new Date(resetEpoch * 1000),\n };\n }\n }\n\n private async _request<T>(\n method: string,\n path: string,\n init: { params?: Record<string, string | number | undefined>; body?: unknown } = {}\n ): Promise<T> {\n const url = this._buildUrl(path, init.params);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method,\n headers: {\n \"X-API-Key\": this.apiKey,\n ...(init.body !== undefined ? { \"Content-Type\": \"application/json\" } : {}),\n },\n body: init.body !== undefined ? JSON.stringify(init.body) : undefined,\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n const d = await readDetail(resp, \"Invalid API key\");\n throw new AuthError(d.message, d.info);\n }\n if (resp.status === 429) {\n const d = await readDetail(resp, \"Rate limit exceeded\");\n throw new RateLimitError(d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n if (resp.status === 204) {\n return undefined as T;\n }\n return (await resp.json()) as T;\n }\n\n // ------------------------------------------------------------------\n // Public API\n // ------------------------------------------------------------------\n\n /** List all available sports. */\n getSports(): Promise<Sport[]> {\n return this._request<Sport[]>(\"GET\", \"/sports\");\n }\n\n /** List upcoming events for a sport. */\n getEvents(sport: string): Promise<PropLineEvent[]> {\n return this._request<PropLineEvent[]>(\"GET\", `/sports/${encodeURIComponent(sport)}/events`);\n }\n\n /**\n * Get current odds. With `eventId`, returns single-event odds (including\n * player props). Without, returns bulk odds for all upcoming events.\n *\n * Each response carries a `bookmakers` array — iterate it to compare\n * lines across Bovada, DraftKings, FanDuel, Pinnacle, Unibet, and\n * PrizePicks (coverage varies by sport).\n */\n getOdds(\n sport: string,\n options: GetOddsOptions & { eventId: number | string }\n ): Promise<OddsResponse>;\n getOdds(\n sport: string,\n options?: Omit<GetOddsOptions, \"eventId\"> & { eventId?: undefined }\n ): Promise<OddsResponse[]>;\n getOdds(\n sport: string,\n options: GetOddsOptions = {}\n ): Promise<OddsResponse | OddsResponse[]> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n const periodParam = _periodParam(options.period);\n if (periodParam !== undefined) params.period = periodParam;\n const bookmakersParam = _bookmakersParam(options.bookmakers);\n if (bookmakersParam !== undefined) params.bookmakers = bookmakersParam;\n if (options.includeLinks) params.includeLinks = \"true\";\n if (options.includeBookIds) params.includeBookIds = \"true\";\n const sp = encodeURIComponent(sport);\n if (options.eventId !== undefined) {\n return this._request<OddsResponse>(\n \"GET\",\n `/sports/${sp}/events/${encodeURIComponent(String(options.eventId))}/odds`,\n { params }\n );\n }\n return this._request<OddsResponse[]>(\"GET\", `/sports/${sp}/odds`, { params });\n }\n\n /** List the available market types for an event. */\n getMarkets(sport: string, eventId: number | string): Promise<MarketSummary[]> {\n return this._request<MarketSummary[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/markets`\n );\n }\n\n /**\n * Get historical odds movement for an event.\n *\n * Hobby+: full snapshots. Free tier: redacted (snapshot counts only).\n *\n * Supports period-historical filters:\n * - `from` / `to` — absolute ISO timestamps\n * - `relativeFrom` / `relativeTo` — offsets to commence_time (\"-3h\", \"-30m\", \"0\")\n * - `interval` — downsample to a fixed bucket size\n * - `changesOnly` — drop unchanged adjacent snapshots\n */\n getOddsHistory(\n sport: string,\n eventId: number | string,\n options: GetOddsHistoryOptions = {}\n ): Promise<OddsHistoryResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n if (options.from !== undefined) params.from = options.from;\n if (options.to !== undefined) params.to = options.to;\n if (options.relativeFrom !== undefined) params.relative_from = options.relativeFrom;\n if (options.relativeTo !== undefined) params.relative_to = options.relativeTo;\n if (options.interval !== undefined) params.interval = options.interval;\n if (options.changesOnly) params.changes_only = \"true\";\n const periodParam2 = _periodParam(options.period);\n if (periodParam2 !== undefined) params.period = periodParam2;\n const bookmakersParam2 = _bookmakersParam(options.bookmakers);\n if (bookmakersParam2 !== undefined) params.bookmakers = bookmakersParam2;\n return this._request<OddsHistoryResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/odds/history`,\n { params }\n );\n }\n\n /**\n * Get the opening AND closing line per `(book, market, outcome)` for an\n * event. Closing is the last snapshot at or before commence_time\n * (`price` / `point` / `closing_at`); opening is the first snapshot in\n * the same 14-day pre-kickoff window (`opening_price` / `opening_point`\n * / `opening_at`). Canonical CLV helper: replaces \"fetch full history →\n * find the first and last pre-game rows\" with one call.\n *\n * Compare the *points* as well as the prices — on spreads and totals\n * the number moves as much as the price, so a price-only comparison\n * mis-measures those markets.\n *\n * Hobby+: full data. Free tier: redacted.\n */\n getOddsClosing(\n sport: string,\n eventId: number | string,\n options: GetOddsClosingOptions = {}\n ): Promise<OddsClosingResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n const periodParam3 = _periodParam(options.period);\n if (periodParam3 !== undefined) params.period = periodParam3;\n const bookmakersParam3 = _bookmakersParam(options.bookmakers);\n if (bookmakersParam3 !== undefined) params.bookmakers = bookmakersParam3;\n return this._request<OddsClosingResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/odds/closing`,\n { params }\n );\n }\n\n /** Get game scores and status (free tier). */\n getScores(sport: string, options: GetScoresOptions = {}): Promise<ScoreEvent[]> {\n return this._request<ScoreEvent[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/scores`,\n { params: { days_from: options.daysFrom ?? 3 } }\n );\n }\n\n /**\n * PrizePicks Power/Flex entry payout schedule (2-6 legs) plus the per-leg\n * breakeven win probability for each play. Pass `legWinProb` to also get\n * `expected_return` (per $1) and `is_plus_ev` per play — turning a slip\n * into the hit rate it actually needs to clear.\n *\n * These are PrizePicks's *standard* published payouts; demon/goblin per-pick\n * modifiers aren't in PrizePicks's feed, so they're not reflected. Breakeven\n * assumes independent legs. See the `disclaimer` field on the response.\n */\n getDfsPayouts(\n options: GetDfsPayoutsOptions = {}\n ): Promise<DfsPayoutsResponse> {\n const params: Record<string, string | number> = {\n platform: options.platform ?? \"prizepicks\",\n };\n if (options.legWinProb !== undefined) params.leg_win_prob = options.legWinProb;\n return this._request<DfsPayoutsResponse>(\"GET\", \"/dfs/payouts\", { params });\n }\n\n /**\n * Synthetic MLB Grand Salami for a given UTC date — total runs scored\n * across every MLB game on the slate, plus each book's implied Grand\n * Salami line (median of per-game primary totals across our MLB books).\n *\n * No retail sportsbook quotes this as a single market, so historical\n * cross-book Grand Salami data isn't available elsewhere. Free tier;\n * defaults to today (UTC).\n */\n getMlbGrandSalami(\n options: GetMlbGrandSalamiOptions = {}\n ): Promise<MlbGrandSalamiResponse> {\n const params: Record<string, string> = {};\n if (options.date) params.date = options.date;\n return this._request<MlbGrandSalamiResponse>(\n \"GET\",\n \"/sports/baseball_mlb/grand-salami\",\n { params }\n );\n }\n\n /**\n * Synthetic NHL Daily Goals Total for a given UTC date — total goals\n * scored (incl. OT/SO) across every NHL game on the slate, plus each\n * book's implied Daily Goals Total line (median of per-game primary\n * totals across our NHL books).\n *\n * Hockey's equivalent of the MLB Grand Salami. No retail sportsbook\n * quotes this as a single market. Free tier; defaults to today (UTC).\n */\n getNhlDailyGoalsTotal(\n options: GetNhlDailyGoalsTotalOptions = {}\n ): Promise<NhlDailyGoalsTotalResponse> {\n const params: Record<string, string> = {};\n if (options.date) params.date = options.date;\n return this._request<NhlDailyGoalsTotalResponse>(\n \"GET\",\n \"/sports/hockey_nhl/daily-goals-total\",\n { params }\n );\n }\n\n /**\n * Factual volume of graded player props over the last N days (free tier).\n *\n * Aggregated counts only — a coverage proof (every outcome counted was\n * graded against the real box score), never a profitability claim.\n *\n * @param days Look-back window, 1-90 (default 30).\n */\n getResolutionSummary(days = 30): Promise<ResolutionSummary> {\n return this._request<ResolutionSummary>(\n \"GET\",\n \"/markets/resolution-summary\",\n { params: { days: String(days) } }\n );\n }\n\n /**\n * Get raw player/team box-score stats (book-agnostic, free tier).\n *\n * Returns actual stat values decoupled from any bookmaker's lines.\n *\n * Live during games for major US sports (MLB + WNBA now; NFL, NCAAF,\n * NBA, NHL at season start): while the event's status is \"in_progress\",\n * stats refresh roughly every 90 seconds with cumulative in-game values —\n * treat them as partial until status flips to \"final\". Other sports\n * populate stats at game completion.\n */\n getStats(\n sport: string,\n eventId: number | string,\n options: GetStatsOptions = {}\n ): Promise<StatsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.statType?.length) {\n params.stat_type = options.statType.join(\",\");\n }\n return this._request<StatsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/stats`,\n { params }\n );\n }\n\n /**\n * Get game context — the conditions a prop settles under.\n *\n * For MLB: probable starting pitchers and their throwing hand\n * (`home_probable_pitcher_hand` / `away_probable_pitcher_hand`, \"L\"/\"R\"/\"S\"\n * — platoon-split context for every batter prop), a confirmed-lineup flag,\n * the home-plate umpire, and first-pitch weather (outdoor / open-roof\n * venues; indoor venues return `weather: null` with `is_indoor: true`).\n * For NFL & NCAAF: the venue and kickoff weather (pitcher/umpire/lineup\n * fields are null for football). The same block is embedded in\n * {@link getResults}, so every graded prop carries its conditions — unique\n * to PropLine. Free tier. Rejects with a 404 when no context is on file\n * for the event yet.\n */\n getContext(\n sport: string,\n eventId: number | string\n ): Promise<ContextResponse> {\n return this._request<ContextResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/context`\n );\n }\n\n /**\n * Get line movement + steam detection from the snapshot tick history.\n *\n * Per (book, market, outcome): opening line, latest line, signed\n * implied-probability shift, point shift, direction. The `steam` array\n * flags outcomes multiple books moved the same direction — the\n * sharp-money signal across every book PropLine polls. When a book moves\n * the line itself, that outcome's `prob_shift` is null and `direction` is\n * `\"line_moved\"` (excluded from the steam signal). Unique to PropLine.\n * Hobby+ full; free tier redacted.\n */\n getMovement(\n sport: string,\n eventId: number | string,\n options: GetMovementOptions = {}\n ): Promise<MovementResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n params.period = _periodParam(options.period);\n params.bookmakers = _bookmakersParam(options.bookmakers);\n return this._request<MovementResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/movement`,\n { params }\n );\n }\n\n /**\n * Get resolved prop outcomes with actual player stats.\n *\n * Pro tier: full data. Free tier: redacted (resolution + actual nulled).\n */\n getResults(\n sport: string,\n eventId: number | string,\n options: GetResultsOptions = {}\n ): Promise<ResultsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets?.length) {\n params.markets = options.markets.join(\",\");\n }\n return this._request<ResultsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/results`,\n { params }\n );\n }\n\n /**\n * Recent resolved prop history for a player on a market.\n *\n * One entry per (event, bookmaker) pair. Pro: full. Free: redacted.\n */\n getPlayerHistory(\n sport: string,\n playerName: string,\n options: GetPlayerHistoryOptions\n ): Promise<PlayerHistoryResponse> {\n const params: Record<string, string | number | undefined> = {\n market: options.market,\n limit: options.limit ?? 20,\n };\n if (options.bookmaker) {\n params.bookmaker = options.bookmaker;\n }\n return this._request<PlayerHistoryResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/players/${encodeURIComponent(playerName)}/history`,\n { params }\n );\n }\n\n /**\n * A player's game log — recent games with every raw box-score stat.\n *\n * One call replaces one request per event, so L5/L10/L20, season splits,\n * charts and head-to-head can all be built from the raw rows. Free tier.\n *\n * Reads the RAW-STATS archive, not graded-prop history: it covers every\n * game with a box score on file, including games no sportsbook priced, so\n * a \"last 10 games\" window here really is the last 10 games — unlike one\n * built from {@link getPlayerHistory} or {@link getPlayerTrends}. Carries\n * no line, price or grade.\n *\n * @example\n * ```ts\n * const log = await client.getPlayerGames(\"baseball_mlb\", \"Aaron Judge\", { limit: 10 });\n * const hits = log.games.reduce((n, g) => n + (g.stats.hits ?? 0), 0);\n *\n * // Last 5 meetings with Boston — not the Boston games among his last 5.\n * const h2h = await client.getPlayerGames(\"baseball_mlb\", \"Aaron Judge\", {\n * limit: 5,\n * opponent: \"BOS\",\n * });\n * ```\n */\n getPlayerGames(\n sportKey: string,\n playerName: string,\n options: GetPlayerGamesOptions = {}\n ): Promise<PlayerGameLog> {\n const params: Record<string, string | number | undefined> = {\n limit: options.limit ?? 20,\n };\n if (options.opponent) {\n params.opponent = options.opponent;\n }\n if (options.statType) {\n params.stat_type = Array.isArray(options.statType)\n ? options.statType.join(\",\")\n : options.statType;\n }\n return this._request<PlayerGameLog>(\n \"GET\",\n `/sports/${encodeURIComponent(sportKey)}/players/${encodeURIComponent(playerName)}/games`,\n { params }\n );\n }\n\n /**\n * Rolling hit-rate trends for a player across one or all markets.\n *\n * Returns over/under/push splits over the last 5/10/20/50 graded games,\n * the current streak, and the most recent game per market. Pro: full.\n * Free: redacted.\n */\n getPlayerTrends(\n sportKey: string,\n playerName: string,\n options: GetPlayerTrendsOptions = {}\n ): Promise<PlayerTrends> {\n const params: Record<string, string | undefined> = {};\n if (options.market) {\n params.market = options.market;\n }\n if (options.dfsOddsType) {\n params.dfs_odds_type = options.dfsOddsType;\n }\n return this._request<PlayerTrends>(\n \"GET\",\n `/sports/${encodeURIComponent(sportKey)}/players/${encodeURIComponent(playerName)}/trends`,\n { params }\n );\n }\n\n /**\n * Cross-book +EV analysis for a single event (Pro+ tier).\n *\n * Groups every outcome by (market, player, line) across the books we\n * carry, derives a no-vig fair line from a sharp anchor (Pinnacle\n * preferred, Bovada fallback), and returns EV% per book at the same\n * line. Outcomes are sorted with +EV plays floated to the top.\n *\n * PrizePicks is excluded — its synthetic +100/+100 prices aren't\n * payout odds. Lines without sharp-anchor coverage are dropped.\n *\n * @example\n * ```ts\n * const ev = await client.getEventEv(\"baseball_mlb\", 12345);\n * for (const line of ev.lines) {\n * const plus = line.outcomes.filter(o => o.is_plus_ev);\n * if (plus.length) console.log(line.market_key, line.description, plus);\n * }\n * ```\n */\n /**\n * List futures markets for a sport — championship winner, MVP,\n * division winner, season win totals, etc. Each row is one (futures\n * event, book, market) with every team or player priced. Free tier;\n * aggregated across each book's futures feed (Bovada, FanDuel,\n * DraftKings, and Pinnacle).\n *\n * @example\n * ```ts\n * const futures = await client.getFutures(\"baseball_mlb\");\n * for (const event of futures) {\n * console.log(`${event.title} @ ${event.commence_time}`);\n * for (const m of event.markets) {\n * const top3 = [...m.outcomes].sort((a, b) => a.price - b.price).slice(0, 3);\n * for (const o of top3) console.log(` ${o.name}: ${o.price}`);\n * }\n * }\n * ```\n *\n * @param options.bookmakers Optional book key(s) to restrict the per-book\n * market rows (the-odds-api-compatible; omitted = all books, unknown keys\n * match nothing). A futures event left with no matching market is dropped.\n */\n getFutures(\n sport: string,\n options: { bookmakers?: string | string[] } = {}\n ): Promise<FuturesEvent[]> {\n const params: Record<string, string | undefined> = {};\n const bookmakersParam = _bookmakersParam(options.bookmakers);\n if (bookmakersParam !== undefined) params.bookmakers = bookmakersParam;\n return this._request<FuturesEvent[]>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/futures`,\n { params }\n );\n }\n\n /**\n * Market-implied consensus projections for a single event.\n *\n * One row per (market, player): the statistical value the betting\n * market collectively implies — the line where the no-vig P(over)\n * crosses 50%, median across contributing sportsbooks. Built for\n * validating your own statistical/fantasy projections against the\n * live market. Market-implied arithmetic over sportsbook prices,\n * never a forecast. DFS pick'em pricing is excluded.\n *\n * Paid tier required (Hobby+); free tier receives the structure with\n * projected values nulled and `redacted: true`.\n *\n * @example\n * ```ts\n * const proj = await client.getEventProjections(\"football_nfl\", 25070);\n * for (const row of proj.projections) {\n * console.log(row.player, row.market_key, row.projected_value);\n * }\n * ```\n */\n getEventProjections(\n sport: string,\n eventId: number | string,\n options: GetEventProjectionsOptions = {}\n ): Promise<EventProjectionsResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n return this._request<EventProjectionsResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/projections`,\n { params }\n );\n }\n\n getEventEv(\n sport: string,\n eventId: number | string,\n options: GetEventEvOptions = {}\n ): Promise<EventEvResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n if (options.bookmakers) {\n params.bookmakers = Array.isArray(options.bookmakers)\n ? options.bookmakers.join(\",\")\n : options.bookmakers;\n }\n return this._request<EventEvResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/ev`,\n { params }\n );\n }\n\n /**\n * Cross-book best-line lookup for a single event.\n *\n * For each (market, player, line) tuple, returns the single best\n * American price across every book we carry, with the book name\n * attached. Companion to `getEventEv`: best-line tells you which\n * book has the highest payout right now; +EV tells you whether\n * that price beats a sharp no-vig fair line. Most line shoppers\n * want both.\n *\n * PrizePicks is excluded from the comparison — its DFS payout\n * structure (synthetic +100/+100 quotes) isn't directly comparable\n * to traditional sportsbook odds.\n *\n * Hobby tier or higher sees prices. Free tier gets a redacted\n * teaser: the full structure — every line, side, book identity, and\n * the best-first ranking — with every price null, plus\n * `redacted: true` and an `upgrade_url`.\n *\n * @example\n * ```ts\n * const bl = await client.getEventBestLine(\"baseball_mlb\", 12345);\n * for (const line of bl.lines) {\n * for (const [side, info] of Object.entries(line.sides)) {\n * console.log(\n * `${line.description} ${side} ${line.point}: ` +\n * `${info.best.price} @ ${info.best.book_title}`\n * );\n * }\n * }\n * ```\n */\n getEventBestLine(\n sport: string,\n eventId: number | string,\n options: GetEventBestLineOptions = {}\n ): Promise<EventBestLineResponse> {\n const params: Record<string, string | undefined> = {};\n if (options.markets) {\n params.markets = Array.isArray(options.markets)\n ? options.markets.join(\",\")\n : options.markets;\n }\n if (options.bookmakers) {\n params.bookmakers = Array.isArray(options.bookmakers)\n ? options.bookmakers.join(\",\")\n : options.bookmakers;\n }\n if (options.includeLinks) params.includeLinks = \"true\";\n return this._request<EventBestLineResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/best-line`,\n { params }\n );\n }\n\n /**\n * Calculate EV% for a user-supplied price against the event's\n * no-vig fair anchor. Useful for books PropLine doesn't carry —\n * Caesars, BetMGM, Fanatics, BetUS, Hard Rock — where you have a\n * price in hand and want to know if it's +EV against the sharp\n * consensus we do carry.\n *\n * Same fair-line math as `getEventEv` (Pinnacle-preferred anchor,\n * no-vig devigging) but takes one user price as input. Pro tier.\n *\n * @example\n * ```ts\n * const r = await client.calcEventEv(\"baseball_mlb\", 12614, {\n * market: \"h2h\",\n * name: \"Pittsburgh Pirates\",\n * price: -118,\n * });\n * console.log(`EV ${r.ev_pct}% fair=${r.fair_prob}`);\n * ```\n */\n calcEventEv(\n sport: string,\n eventId: number | string,\n options: CalcEventEvOptions\n ): Promise<EventEvCalcResponse> {\n const params: Record<string, string | number | undefined> = {\n market: options.market,\n name: options.name,\n price: options.price,\n };\n if (options.point !== undefined) params.point = options.point;\n if (options.description) params.description = options.description;\n return this._request<EventEvCalcResponse>(\n \"GET\",\n `/sports/${encodeURIComponent(sport)}/events/${encodeURIComponent(String(eventId))}/ev/calc`,\n { params }\n );\n }\n\n /**\n * Bulk CSV export of resolved prop outcomes (Pro+ tier).\n *\n * If `outPath` is provided, streams the CSV to disk and resolves to the\n * path. Otherwise resolves to the full CSV bytes as a `Uint8Array`.\n *\n * @example\n * ```ts\n * await client.exportResolvedProps({\n * sport: \"baseball_mlb\",\n * market: \"pitcher_strikeouts\",\n * since: \"2026-04-01T00:00:00Z\",\n * outPath: \"./mlb-strikeouts.csv\",\n * });\n * ```\n */\n async exportResolvedProps(\n options: ExportResolvedPropsOptions & { outPath: string }\n ): Promise<string>;\n async exportResolvedProps(\n options: ExportResolvedPropsOptions & { outPath?: undefined }\n ): Promise<Uint8Array>;\n async exportResolvedProps(\n options: ExportResolvedPropsOptions\n ): Promise<string | Uint8Array> {\n const params: Record<string, string | undefined> = { sport: options.sport };\n if (options.market) params.market = options.market;\n if (options.bookmaker) params.bookmaker = options.bookmaker;\n if (options.since) params.since = options.since;\n if (options.until) params.until = options.until;\n\n const url = this._buildUrl(\"/exports/resolved-props\", params);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method: \"GET\",\n headers: { \"X-API-Key\": this.apiKey },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n throw new AuthError();\n }\n if (resp.status === 403) {\n const d = await readDetail(resp, \"Pro tier required\");\n throw new PropLineError(403, d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n const buf = new Uint8Array(await resp.arrayBuffer());\n if (options.outPath) {\n await writeFile(options.outPath, buf);\n return options.outPath;\n }\n return buf;\n }\n\n /**\n * Bulk CSV export of the full line-movement time-series.\n *\n * One row per (outcome, snapshot): every recorded odds snapshot (price +\n * line, per book, including period markets), not just the closing line.\n * This is the raw tick history no subscription tier can pull in bulk —\n * Pro/Streaming get per-event {@link getOddsHistory} only; this bulk\n * firehose is exclusive to the one-time Historical Backfill pass and\n * Enterprise.\n *\n * A full archive runs to gigabytes per sport — page month by month with\n * `since`/`until`. If `outPath` is provided, streams to disk and resolves\n * to the path; otherwise resolves to the CSV bytes as a `Uint8Array`.\n *\n * @example\n * ```ts\n * await client.exportOddsHistory({\n * sport: \"baseball_mlb\",\n * since: \"2026-04-01T00:00:00Z\",\n * until: \"2026-05-01T00:00:00Z\",\n * outPath: \"./mlb-line-history-apr.csv\",\n * });\n * ```\n */\n async exportOddsHistory(\n options: ExportOddsHistoryOptions & { outPath: string }\n ): Promise<string>;\n async exportOddsHistory(\n options: ExportOddsHistoryOptions & { outPath?: undefined }\n ): Promise<Uint8Array>;\n async exportOddsHistory(\n options: ExportOddsHistoryOptions\n ): Promise<string | Uint8Array> {\n const params: Record<string, string | undefined> = { sport: options.sport };\n if (options.market) params.market = options.market;\n if (options.bookmaker) params.bookmaker = options.bookmaker;\n if (options.since) params.since = options.since;\n if (options.until) params.until = options.until;\n\n const url = this._buildUrl(\"/exports/odds-history\", params);\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.timeoutMs);\n\n let resp: Response;\n try {\n resp = await this._fetch(url, {\n method: \"GET\",\n headers: { \"X-API-Key\": this.apiKey },\n signal: controller.signal,\n });\n } finally {\n clearTimeout(timer);\n }\n this._captureQuota(resp);\n\n if (resp.status === 401) {\n throw new AuthError();\n }\n if (resp.status === 403) {\n const d = await readDetail(\n resp,\n \"Historical Backfill pass or Enterprise required\"\n );\n throw new PropLineError(403, d.message, d.info);\n }\n if (resp.status >= 400) {\n const d = await readDetail(resp, resp.statusText);\n throw new PropLineError(resp.status, d.message, d.info);\n }\n\n const buf = new Uint8Array(await resp.arrayBuffer());\n if (options.outPath) {\n await writeFile(options.outPath, buf);\n return options.outPath;\n }\n return buf;\n }\n\n // ------------------------------------------------------------------\n // Webhooks (Streaming tier)\n // ------------------------------------------------------------------\n\n /**\n * Register a webhook subscription. Streaming tier only.\n *\n * The returned object includes the full signing `secret` — this is the\n * ONLY time it's revealed. Store it securely.\n */\n createWebhook(options: CreateWebhookOptions): Promise<Webhook> {\n return this._request<Webhook>(\"POST\", \"/webhooks\", {\n body: webhookBody(options),\n });\n }\n\n /** List your webhook subscriptions. Secrets are masked. */\n listWebhooks(): Promise<Webhook[]> {\n return this._request<Webhook[]>(\"GET\", \"/webhooks\");\n }\n\n /** Get a single webhook subscription. Secret is masked. */\n getWebhook(webhookId: number): Promise<Webhook> {\n return this._request<Webhook>(\"GET\", `/webhooks/${webhookId}`);\n }\n\n /** Update fields on a webhook. Only supplied fields are changed. */\n updateWebhook(webhookId: number, options: UpdateWebhookOptions): Promise<Webhook> {\n return this._request<Webhook>(\"PATCH\", `/webhooks/${webhookId}`, {\n body: webhookBody(options),\n });\n }\n\n /** Delete a webhook (cascades its delivery history). */\n deleteWebhook(webhookId: number): Promise<{ ok: boolean } | unknown> {\n return this._request(\"DELETE\", `/webhooks/${webhookId}`);\n }\n\n /** Queue a sample `test` payload to the webhook's URL. */\n testWebhook(webhookId: number): Promise<unknown> {\n return this._request(\"POST\", `/webhooks/${webhookId}/test`);\n }\n\n /** Last 50 (default) delivery attempts for a webhook. */\n listWebhookDeliveries(\n webhookId: number,\n options: ListWebhookDeliveriesOptions = {}\n ): Promise<WebhookDelivery[]> {\n return this._request<WebhookDelivery[]>(\n \"GET\",\n `/webhooks/${webhookId}/deliveries`,\n { params: { limit: options.limit ?? 50, before_id: options.beforeId } }\n );\n }\n\n /**\n * Re-read this subscription's events in order, from a cursor.\n *\n * Answers \"my endpoint was down — what did I miss?\". Every delivery carries\n * an `X-PropLine-Sequence` header: a counter monotonic *within your\n * subscription*. Store the highest one you processed and pass it as\n * `sinceSeq`.\n *\n * Do NOT use `X-PropLine-Delivery` as the cursor — that id is global across\n * every subscription, so its gaps are other customers' traffic.\n *\n * Events come back oldest-first (the opposite of `listWebhookDeliveries`,\n * which is a newest-first debugging log). Page by passing `next_seq` back\n * as `sinceSeq` while `has_more` is true.\n *\n * **Check `truncated`.** True means events after your cursor have aged out\n * of retention (2 days, max 5,000 deliveries per subscription) and are gone\n * — resync from the REST endpoints instead of assuming you are current.\n *\n * Does not count against your daily request quota.\n */\n replayWebhookEvents(\n webhookId: number,\n options: ReplayWebhookEventsOptions = {}\n ): Promise<ReplayPage> {\n return this._request<ReplayPage>(\n \"GET\",\n `/webhooks/${webhookId}/replay`,\n { params: { since_seq: options.sinceSeq ?? 0, limit: options.limit ?? 100 } }\n );\n }\n\n /**\n * Stream a websocket subscription as an async iterable.\n *\n * ```ts\n * for await (const ev of client.stream({ webhookId: 12, sinceSeq: 4180 })) {\n * console.log(ev.seq, ev.event_type, ev.data);\n * }\n * ```\n *\n * The subscription must have been created with `transport: \"websocket\"`.\n * Same events, same filters, same `seq` as an HTTP webhook — one\n * subscription, different transport.\n *\n * **Reconnects automatically and resumes from the last `seq` it saw**, which\n * is the whole point of the sequence: a dropped connection does not become a\n * gap in your data. Set `reconnect: false` to get a single connection that\n * ends when the socket closes.\n *\n * If the server reports `truncated` — events after your cursor aged out of\n * retention and are gone — `onTruncated` fires. Handle it: that is the one\n * case where the stream cannot make you whole and you should resync from the\n * REST endpoints.\n */\n async *stream(options: StreamOptions): AsyncGenerator<ReplayEvent, void, void> {\n // ⚠️ Streaming has its OWN origin and does NOT derive from baseUrl.\n // Deriving it (the first cut of this method did) sends the socket to\n // api.prop-line.com — which serves /v1/stream too, from the same ASGI\n // app, so it WORKS and nothing complains. It just parks a persistent\n // connection on the REST tier's event loop, which is precisely what the\n // separate websocket tier exists to prevent. Caught 2026-08-31 by reading\n // the machine id in ws_connections and finding an app-tier machine.\n // Override with `wsUrl` only for self-hosted or local development.\n const wsBase = (options.wsUrl ?? DEFAULT_WS_URL)\n .replace(/^http:/, \"ws:\")\n .replace(/^https:/, \"wss:\")\n .replace(/\\/v1\\/?$/, \"\")\n .replace(/\\/$/, \"\");\n const url = `${wsBase}/v1/stream`;\n let cursor = options.sinceSeq ?? 0;\n let attempt = 0;\n\n for (;;) {\n const queue: ReplayEvent[] = [];\n let notify: (() => void) | null = null;\n let closed: Error | null = null;\n let opened = false;\n\n const ws = new WebSocket(url);\n const wake = () => { const n = notify; notify = null; n?.(); };\n\n ws.addEventListener(\"open\", () => {\n opened = true;\n ws.send(JSON.stringify({\n type: \"auth\",\n api_key: this.apiKey,\n webhook_id: options.webhookId,\n since_seq: cursor,\n }));\n });\n ws.addEventListener(\"message\", (e: MessageEvent) => {\n let msg: Record<string, unknown>;\n try { msg = JSON.parse(String(e.data)); } catch { return; }\n if (msg.type === \"ready\") {\n attempt = 0; // a successful handshake resets backoff\n if (msg.truncated) options.onTruncated?.(msg as unknown as ReplayPage);\n options.onReady?.(msg as unknown as ReplayPage);\n } else if (msg.type === \"event\") {\n queue.push(msg as unknown as ReplayEvent);\n wake();\n }\n // \"ping\" needs no reply — it exists to keep idle proxies from closing.\n });\n ws.addEventListener(\"close\", (e: CloseEvent) => {\n // 4401/4403/4404/4400 are terminal: retrying cannot fix a bad key, a\n // tier without access, or a subscription that is not yours. Only\n // transport failures and 4429 are worth reconnecting for.\n const terminal = [4400, 4401, 4403, 4404].includes(e.code);\n closed = new PropLineError(\n e.code,\n `stream closed${e.reason ? `: ${e.reason}` : \"\"}`,\n );\n (closed as PropLineError & { terminal?: boolean }).terminal = terminal;\n wake();\n });\n ws.addEventListener(\"error\", () => {\n if (!closed) closed = new PropLineError(0, \"stream connection error\");\n wake();\n });\n\n try {\n for (;;) {\n while (queue.length) {\n const ev = queue.shift()!;\n cursor = ev.seq; // advance BEFORE yielding, so a\n yield ev; // consumer `break` still resumes right\n }\n if (closed) break;\n await new Promise<void>((r) => { notify = r; });\n }\n } finally {\n try { ws.close(); } catch { /* already closed */ }\n }\n\n const err = closed as (PropLineError & { terminal?: boolean }) | null;\n if (err?.terminal) throw err;\n if (options.reconnect === false) {\n if (err && !opened) throw err;\n return;\n }\n // Capped exponential backoff. Without the cap a long outage would have\n // clients reconnecting hours apart; without backoff they would stampede.\n const delayMs = Math.min(30_000, 500 * 2 ** attempt++);\n await new Promise((r) => setTimeout(r, delayMs));\n }\n }\n\n /**\n * Grade placed bets against their closing lines (CLV).\n *\n * Closing line value is the only durable proxy for whether a bettor has\n * edge: did the price you took beat the number the market settled on?\n * Send the bets you actually placed; each comes back with its closing\n * price, the de-vigged closing fair probability, CLV, and — once the\n * game settles — the graded result and actual stat value.\n *\n * Stateless: nothing is stored server-side.\n *\n * **Two CLV numbers are returned deliberately.** `clv_pct` is\n * price-vs-price — familiar and quotable, but vig-blind, so it flatters\n * a bet taken on the juicy side of a wide market. `ev_vs_close_pct`\n * scores your price against the DE-VIGGED close and is the honest one;\n * on a real bet the two came out +6.52% and +0.08%.\n *\n * The de-vig anchors to the **sharpest book quoting that line at close**\n * (`fair_source`), not the book you bet at — de-vigging your own book\n * always returns a negative number, because you paid its hold.\n *\n * Bets whose event has not started carry `closing_is_final: false`, land\n * in `summary.pending`, and are excluded from the summary averages:\n * before kickoff the \"closing\" price is just the latest price.\n *\n * Matching is fail-closed — a bet that cannot be pinned to exactly one\n * stored outcome returns `matched: false` with an `unmatched_reason`\n * rather than a confident wrong match. Max 500 bets per request.\n *\n * Hobby+ required; free tier receives the structure with numbers nulled.\n *\n * @example\n * const res = await client.gradeClv([{\n * ref: \"b1\",\n * sport_key: \"baseball_mlb\",\n * event_id: 150791,\n * market: \"batter_hits_runs_rbis\",\n * bookmaker: \"lowvig\",\n * selection: \"Drake Baldwin\",\n * side: \"Under\",\n * point: 0.5,\n * price: 145,\n * stake: 1,\n * }]);\n * console.log(res.summary.avg_ev_vs_close_pct);\n */\n gradeClv(bets: ClvBetInput[]): Promise<ClvGradeResponse> {\n return this._request<ClvGradeResponse>(\"POST\", \"/clv/grade\", { body: bets });\n }\n\n /**\n * Price a same-game parlay at the book's own correlated odds.\n *\n * Send two to ten legs from ONE event and get back the book's own price\n * for that exact slip — what a FanDuel customer would be offered for it\n * at that moment, not a model of it — beside `independent_price` (the\n * product of the live single-leg prices) and `correlation_factor`\n * (their ratio: the correlation the book is charging, below 1, or\n * paying, above 1, for). Measured live: Cardinals ML +205 × Freddie\n * Freeman to record a hit -260 → SGP +592 against an independent +322.\n *\n * Book-native. `bookmaker` is \"fanduel\" (its own betslip pricer) or\n * \"betonlineag\" / \"lowvig\" (the Sportcast engine both Chico brands embed,\n * same builder price); an unsupported value is a 422.\n *\n * Legs are named exactly as `/odds` names an outcome (market, name,\n * description, point, period), or by `book_outcome_id` from\n * `includeBookIds: true`. Matching is fail-closed — a leg that does not\n * pin to exactly one stored outcome is a 422 `leg_unmatched` naming the\n * leg. `quoted: false` means the book will not offer that combination as\n * a same-game parlay; refused legs carry the book's own `failure_code`.\n * Quotes for an identical slip are shared for 15 seconds.\n *\n * Hobby+ required; free tier receives the matched legs with every price\n * nulled and never triggers a book call.\n *\n * @example\n * const q = await client.priceSgp(\"baseball_mlb\", 150791, [\n * { market: \"h2h\", name: \"St. Louis Cardinals\" },\n * { market: \"batter_1plus_hits\", name: \"Freddie Freeman\", description: \"Freddie Freeman\" },\n * ]);\n * console.log(q.sgp_price, q.independent_price, q.correlation_factor);\n */\n priceSgp(\n sportKey: string,\n eventId: number | string,\n legs: SgpLegInput[],\n bookmaker = \"fanduel\",\n ): Promise<SgpQuoteResponse> {\n return this._request<SgpQuoteResponse>(\n \"POST\",\n `/sports/${encodeURIComponent(sportKey)}/events/${encodeURIComponent(String(eventId))}/sgp`,\n { body: { bookmaker, legs } },\n );\n }\n\n /**\n * Verify that an inbound webhook delivery was signed by PropLine.\n *\n * Compares HMAC-SHA256(secret, `${timestamp}.` + body) against the\n * `X-PropLine-Signature` header in constant time.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * app.post(\"/hooks/propline\", express.raw({ type: \"*\\/*\" }), (req, res) => {\n * const ok = PropLine.verifySignature({\n * secret: process.env.WEBHOOK_SECRET!,\n * timestamp: req.header(\"X-PropLine-Timestamp\")!,\n * body: req.body, // raw Buffer\n * signature: req.header(\"X-PropLine-Signature\")!,\n * });\n * if (!ok) return res.status(401).end();\n * // ...\n * });\n * ```\n */\n static verifySignature(options: VerifySignatureOptions): boolean {\n const { secret, timestamp, body, signature } = options;\n const bodyBuf =\n typeof body === \"string\"\n ? Buffer.from(body, \"utf8\")\n : body instanceof Buffer\n ? body\n : Buffer.from(body);\n const message = Buffer.concat([Buffer.from(`${timestamp}.`, \"utf8\"), bodyBuf]);\n const expected = createHmac(\"sha256\", secret).update(message).digest(\"hex\");\n if (expected.length !== signature.length) return false;\n try {\n return timingSafeEqual(Buffer.from(expected, \"hex\"), Buffer.from(signature, \"hex\"));\n } catch {\n return false;\n }\n }\n}\n\nfunction webhookBody(options: CreateWebhookOptions | UpdateWebhookOptions): Record<string, unknown> {\n const body: Record<string, unknown> = {};\n const map: Array<[keyof (CreateWebhookOptions & UpdateWebhookOptions), string]> = [\n [\"url\", \"url\"],\n [\"events\", \"events\"],\n [\"filterSportKey\", \"filter_sport_key\"],\n [\"filterEventId\", \"filter_event_id\"],\n [\"filterMarketKey\", \"filter_market_key\"],\n [\"filterPlayerName\", \"filter_player_name\"],\n [\"filterBookmakerKey\", \"filter_bookmaker_key\"],\n [\"minPriceChangePct\", \"min_price_change_pct\"],\n [\"minSteamScore\", \"min_steam_score\"],\n [\"minBooksAgreeing\", \"min_books_agreeing\"],\n [\"batchMax\", \"batch_max\"],\n [\"active\", \"active\"],\n ];\n for (const [src, dst] of map) {\n const v = (options as Record<string, unknown>)[src as string];\n if (v !== undefined) body[dst] = v;\n }\n return body;\n}\n\ninterface ReadDetailResult {\n message: string;\n info?: PropLineErrorInfo;\n}\n\nasync function readDetail(\n resp: Response,\n fallback: string,\n): Promise<ReadDetailResult> {\n try {\n const text = await resp.text();\n if (!text) return { message: fallback };\n try {\n const json = JSON.parse(text) as { detail?: unknown };\n if (typeof json.detail === \"string\") return { message: json.detail };\n if (json.detail && typeof json.detail === \"object\") {\n const info = json.detail as PropLineErrorInfo;\n return {\n message: typeof info.message === \"string\" ? info.message : text,\n info,\n };\n }\n } catch {\n // not JSON\n }\n return { message: text || fallback };\n } catch {\n return { message: fallback };\n }\n}\n","/**\n * PropLine — Node/TypeScript SDK for the PropLine player props API.\n *\n * @example\n * ```ts\n * import { PropLine } from \"propline\";\n *\n * const client = new PropLine(\"your_api_key\");\n * const events = await client.getEvents(\"basketball_nba\");\n * const odds = await client.getOdds(\"basketball_nba\", {\n * eventId: events[0].id,\n * markets: [\"player_points\", \"player_rebounds\"],\n * });\n * ```\n */\n\nexport { PropLine } from \"./client.js\";\nexport {\n PropLineError,\n AuthError,\n RateLimitError,\n} from \"./client.js\";\nexport type {\n PropLineErrorInfo,\n PropLineOptions,\n QuotaStatus,\n GetOddsOptions,\n GetOddsHistoryOptions,\n GetOddsClosingOptions,\n PeriodFilter,\n GetScoresOptions,\n GetDfsPayoutsOptions,\n GetMlbGrandSalamiOptions,\n GetNhlDailyGoalsTotalOptions,\n GetStatsOptions,\n GetResultsOptions,\n GetPlayerHistoryOptions,\n GetPlayerGamesOptions,\n GetPlayerTrendsOptions,\n GetEventEvOptions,\n GetEventProjectionsOptions,\n GetEventBestLineOptions,\n CalcEventEvOptions,\n ExportResolvedPropsOptions,\n ExportOddsHistoryOptions,\n WebhookEventType,\n CreateWebhookOptions,\n UpdateWebhookOptions,\n ListWebhookDeliveriesOptions,\n ReplayWebhookEventsOptions,\n StreamOptions,\n VerifySignatureOptions,\n} from \"./client.js\";\n\nexport type {\n ClvBetInput,\n ClvGradedBet,\n ClvSummary,\n ClvGradeResponse,\n SgpLegInput,\n SgpLegQuote,\n SgpQuoteResponse,\n Sport,\n Event,\n Outcome,\n ResolvedOutcome,\n Market,\n Bookmaker,\n OddsResponse,\n MarketSummary,\n OutcomeSnapshot,\n OddsHistoryOutcome,\n OddsHistoryMarket,\n OddsHistoryBookmaker,\n OddsHistoryResponse,\n ClosingOutcome,\n ClosingMarket,\n ClosingBookmaker,\n OddsClosingResponse,\n ScoreEvent,\n MlbGrandSalamiBook,\n MlbGrandSalamiResponse,\n NhlDailyGoalsTotalBook,\n NhlDailyGoalsTotalResponse,\n ResolutionSummary,\n ResolutionSummarySport,\n ResolutionSummaryMarket,\n PlayerStat,\n StatsResponse,\n WeatherInfo,\n ContextResponse,\n MovementOutcome,\n MovementMarket,\n MovementBookmaker,\n SteamMove,\n MovementResponse,\n ResultsMarket,\n ResultsResponse,\n PlayerHistoryEntry,\n PlayerHistoryResponse,\n HitRateSplit,\n TrendStreak,\n TrendLastGame,\n PlayerMarketTrend,\n PlayerGame,\n PlayerGameLog,\n PlayerTrends,\n EvOutcome,\n EvLine,\n EventEvResponse,\n EventProjectionsResponse,\n ProjectionRow,\n EventEvCalcResponse,\n BestPrice,\n BestLineSide,\n BestLine,\n EventBestLineResponse,\n FuturesOutcome,\n FuturesMarket,\n FuturesEvent,\n Webhook,\n WebhookDelivery,\n ReplayEvent,\n ReplayPage,\n DfsPayoutTier,\n DfsPlayPayout,\n DfsPayoutsResponse,\n} from \"./types.js\";\n\n/** String constants for bookmaker keys in odds responses. */\nexport const Bookmakers = {\n BOVADA: \"bovada\",\n DRAFTKINGS: \"draftkings\",\n FANDUEL: \"fanduel\",\n PINNACLE: \"pinnacle\",\n UNIBET: \"unibet\",\n UNDERDOG: \"underdog\",\n KALSHI: \"kalshi\",\n POLYMARKET: \"polymarket\",\n PRIZEPICKS: \"prizepicks\",\n} as const;\n\nexport type BookmakerKey = (typeof Bookmakers)[keyof typeof Bookmakers];\n\nexport const VERSION = \"0.48.3\";\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "propline",
3
- "version": "0.48.2",
3
+ "version": "0.48.3",
4
4
  "description": "Node.js / TypeScript SDK for the PropLine player props betting odds API",
5
5
  "keywords": [
6
6
  "sports",