{"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  SgpMultiQuoteResponse,\n  SgpBookError,\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.52.0\";\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  SgpMultiQuoteResponse,\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   * How the anchor's vig is removed before the fair line is derived.\n   * `\"multiplicative\"` (the default when omitted) divides each implied\n   * probability by the booksum; `\"shin\"` solves Shin's insider-trading\n   * model, which loads the overround onto the longshot and corrects the\n   * favourite-longshot bias — negligible on a -110/-110 total, material\n   * on a +600 anytime scorer. The response echoes it as `devig_method`.\n   */\n  devig?: \"multiplicative\" | \"shin\";\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    if (options.devig) params.devig = options.devig;\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(\n    bets: ClvBetInput[],\n    options: { devig?: \"multiplicative\" | \"shin\" } = {}\n  ): Promise<ClvGradeResponse> {\n    // `devig` picks how the closing anchor's vig is removed before\n    // closing_fair_prob / ev_vs_close_pct — same vocabulary as getEventEv;\n    // echoed on the response as devig_method.\n    return this._request<ClvGradeResponse>(\"POST\", \"/clv/grade\", {\n      body: bets,\n      params: options.devig ? { devig: options.devig } : undefined,\n    });\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   * // Every book on the same legs, side by side — best_bookmaker is the\n   * // one charging the smallest correlation reduction:\n   * const all = await client.priceSgp(\"baseball_mlb\", 150791, legs, \"all\");\n   * console.log(all.best_bookmaker, all.quotes.map((x) => [x.bookmaker, x.correlation_factor]));\n   */\n  priceSgp(\n    sportKey: string,\n    eventId: number | string,\n    legs: SgpLegInput[],\n    bookmaker: \"all\",\n  ): Promise<SgpMultiQuoteResponse>;\n  priceSgp(\n    sportKey: string,\n    eventId: number | string,\n    legs: SgpLegInput[],\n    bookmaker?: string,\n  ): Promise<SgpQuoteResponse>;\n  priceSgp(\n    sportKey: string,\n    eventId: number | string,\n    legs: SgpLegInput[],\n    bookmaker = \"fanduel\",\n  ): Promise<SgpQuoteResponse | SgpMultiQuoteResponse> {\n    return this._request<SgpQuoteResponse | SgpMultiQuoteResponse>(\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":[]}