# PropLine — Complete API Reference for LLMs Last updated: 2026-05-01. Tested with Cursor, Claude Code, ChatGPT, GitHub Copilot, Continue.dev. This is the full PropLine API reference, designed to give an AI assistant everything it needs to write working code against the PropLine API. Paste this URL (`https://prop-line.com/llms-full.txt`) into ChatGPT, Claude, or any LLM and ask it to help you build. PropLine is a real-time sports betting odds API that returns player props, game lines, prop resolution against actual box scores, cross-book +EV plays with no-vig fair lines, historical line movement, and webhook push deliveries. Response format is compatible with the-odds-api so existing integrations swap with one base-URL change. ## Table of Contents 1. Base URL 2. Authentication 3. Pricing & rate limits 4. Endpoints (sports, events, odds, props, history, closing-line/CLV, period markets, +EV, futures, results, scores, MLB Grand Salami, NHL Daily Goals Total, stats, player history, player trends, hit rates, exports, webhooks) 5. Sport keys 6. Market keys (per sport) 7. Bookmaker keys 8. Resolution model 9. Cross-book +EV math 10. SDKs (Python, Node, MCP server) 11. Errors 12. Common patterns (working code recipes) 13. Differentiation vs the-odds-api / OddsBlaze / Owls Insight 14. Endpoint URLs (quick reference) ## Base URL ``` https://api.prop-line.com ``` ## Authentication Every endpoint requires an API key. Free signup at https://prop-line.com (no credit card; key emailed via Resend). Pass the key one of two ways: ```bash # Query parameter (works in browsers, easy curl) curl "https://api.prop-line.com/v1/sports?apiKey=YOUR_KEY" # HTTP header (preferred for production code) curl -H "X-API-Key: YOUR_KEY" "https://api.prop-line.com/v1/sports" ``` Demo key for read-only testing (free tier limits apply): `99c1393f8c921749d193f87b3ef688c5` ## Pricing & rate limits | Tier | Price | Requests/day | Notable | |---|---|---|---| | Free | $0 | 1,000 | All sports, real-time odds, scores. History/results redacted. | | Hobby | $9/mo | 5,000 | + Historical line movement + prop resolution + +EV | | Pro | $19/mo | 25,000 | + Bulk CSV exports + priority support | | Streaming Lite | $39/mo | 100,000 | + Webhook push (5 active) for line-movement and resolution | | Streaming | $79/mo | 1,000,000 | + Webhook push (10 active) for line-movement and resolution | | Enterprise | Custom | Unlimited | Custom markets, SLA | Rate limits are daily-quota based. There's also a per-key burst limit (token bucket) sized free=10/burst+5/sec, pro=50/burst+20/sec, streaming=200/burst+50/sec. Daily limits ≥5M bypass the burst limiter. ## Endpoints ### List sports `GET /v1/sports` Returns the list of supported sport keys. ```json [ {"key": "baseball_mlb", "title": "MLB", "active": true}, {"key": "basketball_nba", "title": "NBA", "active": true}, {"key": "hockey_nhl", "title": "NHL", "active": true}, {"key": "soccer_epl", "title": "Premier League", "active": true} ] ``` ### List events for a sport `GET /v1/sports/{sport_key}/events` Returns upcoming + in-progress events. ```json [ { "id": "12649", "sport_key": "baseball_mlb", "home_team": "Baltimore Orioles", "away_team": "Houston Astros", "commence_time": "2026-04-30T20:05:00Z", "live": false, "completed": false } ] ``` ### Bulk game-line odds `GET /v1/sports/{sport_key}/odds?markets=h2h,spreads,totals` Returns game-line odds (moneyline, spreads, totals) for every upcoming event in the sport, grouped by bookmaker. ### Per-event player props (the money endpoint) `GET /v1/sports/{sport_key}/events/{event_id}/odds?markets=batter_hits,pitcher_strikeouts` Returns full player prop markets for one event. Up to 500+ markets per game including alt lines. ```json { "id": "12649", "sport_key": "baseball_mlb", "home_team": "Baltimore Orioles", "away_team": "Houston Astros", "commence_time": "2026-04-30T20:05:00Z", "bookmakers": [ { "key": "fanduel", "title": "FanDuel", "markets": [ { "key": "batter_hits", "outcomes": [ {"name": "Over", "description": "Yordan Alvarez", "price": -278, "point": 0.5}, {"name": "Under", "description": "Yordan Alvarez", "price": 200, "point": 0.5} ] } ] } ] } ``` Underdog Fantasy outcomes also carry `payout_multiplier` — a DFS boost/discount factor. It is `null` on a standard pick (the quoted `price` carries the full payout) and set on a boost/discount "special" (e.g. `1.5` boost, `0.75` discount) that scales the effective payout. Filter out non-null multipliers when comparing DFS lines against sportsbook consensus so a boosted special does not read as a mispriced edge. The-odds-api does not expose DFS multipliers. PrizePicks outcomes carry `dfs_odds_type` instead — the projection flavor: `standard` (the true market line), `goblin` (easier line, lower payout) or `demon` (harder line, higher payout). It is `null` for every traditional sportsbook. PrizePicks posts up to three flavors of the same player+stat at different lines; filter to `dfs_odds_type == "standard"` to get the market line. Goblin and demon variants arrive as their own per-line markets (e.g. `Points (demon 27.5)`) so they never overwrite the standard line. PrizePicks publishes no numeric multiplier for goblin/demon, so only the flavor (not a payout factor) is surfaced; the synthetic `price` stays +100/+100 because its payout depends on parlay correct-count, not per-pick odds. `dfs_odds_type` is present with the same semantics on `/odds`, `/results` (for DFS calibration audits), `/odds/history`, `/odds/closing`, and `/movement`. On `/odds`, PrizePicks goblin/demon outcomes additionally carry `line_gap` — the signed difference between that outcome's line and the player+stat's `standard` line (`point - standard_point`): positive on a harder demon line, negative on an easier goblin line, and `null` when the outcome is not a PrizePicks goblin/demon or when no standard line exists for that player+stat (PrizePicks frequently posts a player goblin/demon-only). Because PrizePicks publishes no numeric per-pick payout multiplier, the flavor plus `line_gap` are the modelable primitives for fitting per-pick payout adjustments (the parlay multiplier compounds per pick by line difficulty, not by leg-type count alone). Every `/odds` outcome also carries `last_change_at` — PropLine's observed timestamp of the last time that outcome's price actually changed. This is distinct from `book_updated_at` (the book's OWN publish-time, which only Bovada exposes; `null` for every other book): `last_change_at` is derived by PropLine and is populated for EVERY book, including Pinnacle and PrizePicks. We only advance it when `price_american` actually moves, so it is a true "this line last changed at T" signal. Compare it across books in a single `/odds` call to detect repricing lag — e.g. Pinnacle just moved but a slower book's `last_change_at` is older, meaning it has not caught up yet — without a separate `/odds/history` call per event. ### DFS payout schedule + breakeven `GET /v1/dfs/payouts` returns PrizePicks's Power Play (all legs must hit) and Flex Play (partial payouts) entry payout schedule for 2–6 legs, plus the per-leg breakeven win probability for each play type and leg count. Pass `?leg_win_prob=0.58` to also get `expected_return` (per $1) and `is_plus_ev` at that assumed per-leg hit rate. This turns a slip into the hit rate it actually needs to clear. It's pure reference math (free tier, no live data). Caveats in the `disclaimer` field: these are standard published payouts that PrizePicks can change; demon/goblin per-pick modifiers are NOT in PrizePicks's feed so they're not reflected; and breakeven assumes independent legs (real legs are correlated, so treat it as a floor). ### Historical line movement `GET /v1/sports/{sport_key}/events/{event_id}/odds/history?markets=batter_hits` Returns time-series snapshots of every odds change. Paid tiers (Hobby+) return full snapshots; Free tier returns market structure with snapshot counts only. Depth is tier-gated by event age: Hobby = 30 days, Pro = 90 days, Streaming Lite = 180 days, Streaming = 365 days, Enterprise = unlimited. Events older than the tier window come back in the redacted shape (empty snapshots, `redacted: true`, `upgrade_url`). Optional **period-historical** query params (all combine; all Hobby+): - `from` / `to` — absolute ISO timestamps to bound the snapshot window. - `relative_from` / `relative_to` — offsets relative to `commence_time`. Forms like `-3h`, `-30m`, `-90s`, or `0` (commence_time itself). Mutually exclusive with the absolute counterpart. - `interval` — downsample to one snapshot per bucket, latest snapshot wins. One of `30s`, `1m`, `5m`, `15m`, `30m`, `1h`. - `changes_only=true` — drop snapshots whose `(price, point)` match the previous row. The opening line is always kept. Example: `…/odds/history?markets=pitcher_strikeouts&relative_from=-30m&relative_to=0&changes_only=true` returns only the moments where the line actually moved in the half-hour before first pitch. ```json { "bookmakers": [{ "key": "fanduel", "markets": [{ "key": "batter_hits", "outcomes": [{ "name": "Over", "description": "Yordan Alvarez", "snapshots": [ {"price": -278, "point": 0.5, "recorded_at": "2026-04-29T14:07:46Z"}, {"price": -286, "point": 0.5, "recorded_at": "2026-04-29T15:02:28Z"}, {"price": -305, "point": 0.5, "recorded_at": "2026-04-30T19:49:32Z"} ] }] }] }] } ``` ### Period markets (`?period=`) Every odds endpoint (`/odds`, `/odds/{id}/odds`, `/odds/history`, `/odds/closing`) accepts an optional `period` query param that filters by **game-period bucket** (1st quarter, 1st half, 1st period, 6th inning, first-N-innings) — distinct from `/odds/history`'s `from`/`relative_from` time-window filters. - Omitted = full-game markets only (backwards-compatible default for every existing caller). - Single code: `?period=q1` - Multiple: `?period=q1,q2,h1` - Every period including full game: `?period=all` Canonical codes: - `q1` `q2` `q3` `q4` — quarters (NBA, WNBA, NCAAB, NFL, NCAAF) - `h1` `h2` — halves (basketball, football, soccer) - `p1` `p2` `p3` — hockey periods (NHL) - `i1` … `i9` — innings (MLB) - `f3` `f5` `f7` — first N innings (MLB) - `map1` … `map7` — esports maps (per-map game lines + `total_kills` / `kills_handicap` markets) Each market row in the response carries a `period` field (string or `null` for full-game) so consumers can branch on it without re-parsing the URL. Example: closing line for the 1st-quarter total on an NBA event: ``` GET /v1/sports/basketball_nba/events/{event_id}/odds/closing?markets=totals&period=q1 ``` Coverage today: Bovada across NBA / NHL / MLB / soccer. DraftKings / FanDuel / Pinnacle period markets are rolling out — until those are wired, period-specific responses will be Bovada-only. ### Closing lines (CLV helper) `GET /v1/sports/{sport_key}/events/{event_id}/odds/closing?markets=h2h,spreads,totals` Returns the last snapshot per `(book, market, outcome)` at or before `commence_time` — the canonical closing line for CLV tracking. Replaces "fetch full history → grep for the latest pre-game row" with a single call. Each outcome carries `closing_at` so you know exactly when the captured snapshot was recorded. Hobby+: full data. Free tier: structure with `redacted: true` + `upgrade_url`. Same per-tier event-age cap as `/odds/history`. ```json { "id": "5885", "sport_key": "baseball_mlb", "home_team": "Seattle Mariners", "away_team": "Texas Rangers", "commence_time": "2026-04-19T20:10:00Z", "bookmakers": [{ "key": "draftkings", "title": "DraftKings", "markets": [{ "key": "pitcher_strikeouts", "outcomes": [ {"name": "Over", "description": "Bryan Woo", "price": 116, "point": 6.5, "closing_at": "2026-04-19T20:08:14Z"}, {"name": "Under", "description": "Bryan Woo", "price": -148, "point": 6.5, "closing_at": "2026-04-19T20:08:14Z"} ] }] }] } ``` ### Line movement & steam (Hobby+) `GET /v1/sports/{sport_key}/events/{event_id}/movement?markets=h2h,spreads,totals` Line movement derived entirely from our snapshot tick history. For each `(book, market, outcome)` it returns the opening line, the latest line, the signed implied-probability shift (`prob_shift`; positive = the book shortened the outcome), the `point_shift`, and a `direction` (`shortening`/`lengthening`/`flat`). The top-level `steam[]` array flags outcomes that **multiple books moved in the same direction** — the classic sharp-money signal — with `books_quoting`, `books_moved`, `consensus_direction`, `avg_prob_shift`, `consensus_point_shift`, and a 0–100 `steam_score` (composite of how many books agreed and how far they moved). Two disjoint kinds: **price steam** (`consensus_direction` = `shortening`/`lengthening`) when books move the price at a stable line, and **point steam** (`point_up`/`point_down`) when books move the line/number itself the same way (e.g. a total 8.5→9 or run line +1.5→−1.5) — common on totals/spreads where sharp money moves the number, not the price; for point steam `avg_prob_shift` is 0 and the magnitude is in `consensus_point_shift`. PrizePicks is excluded (synthetic DFS pricing). No pull-only odds API can produce this — it requires owning the full tick history across every book. Accepts the same `period` filter as the other odds endpoints. Hobby+: full data. Free tier: redacted structure with `upgrade_url`. Same per-tier event-age cap as `/odds/history`. ```json { "id": "37464", "sport_key": "baseball_mlb", "steam": [ {"market": "totals", "name": "Over", "books_quoting": 9, "books_moved": 6, "consensus_direction": "shortening", "avg_prob_shift": 0.041, "consensus_point_shift": 0.5, "steam_score": 24.6} ], "bookmakers": [{ "key": "pinnacle", "title": "Pinnacle", "markets": [{ "key": "totals", "outcomes": [{ "name": "Over", "open_price": -105, "open_point": 8.5, "latest_price": -130, "latest_point": 9.0, "prob_shift": 0.054, "point_shift": 0.5, "direction": "shortening", "num_snapshots": 42 }] }] }] } ``` ### Cross-book +EV plays (Hobby+) `GET /v1/sports/{sport_key}/events/{event_id}/ev` Computes no-vig fair lines from a sharp anchor (Pinnacle preferred, Bovada fallback) and reports EV% per book at the same line. ```json { "ev_plays": [ { "market_key": "batter_hits", "player": "Yordan Alvarez", "line": 0.5, "side": "Over", "fair_price_american": -260, "best_book": "draftkings", "best_price_american": -240, "ev_pct": 5.71 } ] } ``` ### Futures markets (free) `GET /v1/sports/{sport_key}/futures` Season-long outright markets — championship / Super Bowl / division / conference winners, MVP and award winners (incl. MLB MVP/Cy Young), season win totals, Stanley Cup / World Series / NBA / MLS Cup champions — aggregated across Bovada, FanDuel, DraftKings, Pinnacle, and Kalshi. One row per (futures event, book, market). Winner-style markets put the team/player in `outcomes[].name` (blank `description`/`point`); Over/Under-style markets (season win totals) use `name` "Over"/"Under" with the subject in `description` and the line in `point`. Marquee markets (Super Bowl winner, MVP, division/conference winners) are quoted by multiple books for cross-book comparison; exotic markets are often single-book. Futures stay unresolved (no settlement grade). Free tier. ```json [ { "id": "41655", "sport_key": "football_nfl", "title": "Super Bowl LX Winner", "commence_time": "2027-02-07T23:30:00+00:00", "markets": [ { "key": "super_bowl_winner", "description": "Super Bowl LX Winner", "bookmaker": "draftkings", "outcomes": [ {"name": "Kansas City Chiefs", "price": 650, "price_decimal": 7.5}, {"name": "Buffalo Bills", "price": 700, "price_decimal": 8.0} ] } ] } ] ``` ### Prop resolution / results (Pro full, Free redacted) `GET /v1/sports/{sport_key}/events/{event_id}/results` Returns each prop outcome graded against actual box-score stats. Free tier returns market structure but nulls resolution/actual_value with `redacted: true` and an `upgrade_url`. ```json { "bookmakers": [{ "key": "fanduel", "markets": [{ "key": "batter_hits", "outcomes": [ { "name": "Over", "description": "Yordan Alvarez", "point": 0.5, "price": -278, "resolution": "won", "actual_value": 2.0, "resolved_at": "2026-04-30T23:42:11Z" } ] }] }] } ``` Resolution values: `won`, `lost`, `push`, `void` (player not in box score). ### MLB Grand Salami (free) `GET /v1/sports/baseball_mlb/grand-salami?date=YYYY-MM-DD` Synthetic daily Grand Salami — total runs scored across every MLB game on a given UTC date, plus each book's implied Grand Salami line (median of per-game primary totals across our 10 MLB books). Defaults to today (UTC). No retail book quotes this as a single market, so cross-book historical data isn't available elsewhere. ```json { "sport_key": "baseball_mlb", "date": "2026-05-21", "games_total": 8, "games_completed": 8, "games_in_progress": 0, "games_upcoming": 0, "actual_total_runs": 47, "bookmakers": [ { "key": "pinnacle", "title": "Pinnacle", "games_priced": 8, "line": 62.8, "result": "under" }, { "key": "draftkings", "title": "DraftKings", "games_priced": 8, "line": 53.0, "result": "under" }, { "key": "bovada", "title": "Bovada", "games_priced": 8, "line": 41.5, "result": "over" } ] } ``` `result` is `over` / `under` / `push` once the slate is final (no games_in_progress and at least one completed); `null` until then. ### NHL Daily Goals Total (free) `GET /v1/sports/hockey_nhl/daily-goals-total?date=YYYY-MM-DD` Hockey's equivalent of the MLB Grand Salami — total goals scored across every NHL game on a given UTC date (including OT/SO), plus each book's implied Daily Goals Total line (median of per-game primary totals across our NHL books). Defaults to today (UTC). No retail book quotes this as a single market. ```json { "sport_key": "hockey_nhl", "date": "2026-05-24", "games_total": 4, "games_completed": 4, "games_in_progress": 0, "games_upcoming": 0, "actual_total_goals": 23, "bookmakers": [ { "key": "pinnacle", "title": "Pinnacle", "games_priced": 4, "line": 24.5, "result": "under" }, { "key": "draftkings", "title": "DraftKings", "games_priced": 4, "line": 24.0, "result": "under" }, { "key": "bovada", "title": "Bovada", "games_priced": 4, "line": 24.5, "result": "under" } ] } ``` Same `result` semantics as MLB Grand Salami — `over` / `under` / `push` once the slate is final; `null` while games are in progress. ### Game scores (free) `GET /v1/sports/{sport_key}/scores` ```json [ { "id": "12649", "home_team": "Baltimore Orioles", "home_score": 5, "away_team": "Houston Astros", "away_score": 3, "completed": true, "commence_time": "2026-04-30T20:05:00Z" } ] ``` ### Player stats (free, book-agnostic) `GET /v1/sports/{sport_key}/events/{event_id}/stats` Raw box-score stats per player, decoupled from any bookmaker's lines. Useful for grading your own custom prop types. ### Game context (free) `GET /v1/sports/{sport_key}/events/{event_id}/context` The conditions a game is played under. For MLB: probable starting pitchers and their throwing hand (`home_probable_pitcher_hand` / `away_probable_pitcher_hand`, "L"/"R"/"S" — platoon-split context for every batter prop), a confirmed-lineup flag, the home-plate umpire, and first-pitch weather (temperature, wind speed + direction, precipitation probability, conditions) at outdoor or open-roof venues. For NFL & NCAAF: the venue and kickoff weather (wind and cold drive passing, kicking, totals); the pitcher/umpire/lineup fields are null for football. Indoor/domed venues return `weather: null` with `is_indoor: true`. The same object is embedded as `context` in the `/results` response, so every graded prop carries the conditions it settled against — context joined to graded outcomes, which no other odds API offers. Free tier. Returns 404 when no context is on file yet (before the context loop reaches the event, or for sports without a context source). ### Player prop history `GET /v1/sports/{sport_key}/players/{player_name}/history` Per-player prop history with line/prices/resolution/actual. Hobby+ (all paid tiers) full data; Free tier redacted. ### Player hit-rate trends (Hobby+) `GET /v1/sports/{sport_key}/players/{player_name}/trends` The "did X go over in N of his last M games?" surface. For every market the player has graded history in, returns over/under/push splits across the last 5 / 10 / 20 / 50 graded games (`last_5`/`last_10`/`last_20`/`last_50`, each with `over`/`under`/`push`/`over_pct`), the `current_streak` (`{result, length}`), `avg_actual`, `recent_line`, `reference_bookmaker` (whose posted line anchored the splits), and `last_game`. The over/under verdict each game compares the player's real stat (book-agnostic) against one reference book's posted line; pushes are excluded from the `over_pct` denominator. A window is only returned once enough games exist (e.g. `last_20` needs 11+ graded games), else `null`. Optional `?market=` filter limits to one market. Optional `?dfs_odds_type=standard|goblin|demon` computes the trend against that PrizePicks flavor's line only (e.g. compare a player's goblin-line hit-rate vs his standard-line trend); omit it for the default cross-book behavior. The response echoes the filter back as a top-level `dfs_odds_type`. PrizePicks flavor tagging began 2026-06-16, so per-flavor trends only have depth from that date forward. Built entirely on PropLine's prop resolution — no other odds API offers this. Hobby+ (all paid tiers) full; Free tier sees the market list + `games_graded` counts with the rates redacted (`redacted: true`). ### Hit rates aggregate (free) `GET /v1/markets/hit-rates?days=28` Per-market daily {date, total, won} aggregates over graded Over outcomes for the last N days. Powers the `/today` accuracy panel. ### Resolution coverage summary (free) `GET /v1/markets/resolution-summary?days=30` Factual volume of graded player props over the last N days (1-90, default 30), aggregated counts only. Returns `total_graded` (incl. void), `total_settled` (won/lost/push), `events_graded`, `sports_covered`, a per-sport breakdown (`by_sport`: sport_key/title/graded/events) and the top 12 markets by volume (`top_markets`). A coverage proof — every outcome counted was graded against the real box score, which the-odds-api and OddsJam do not do at any tier. This is a volume statement, never a profitability one. ### Bulk CSV export (Pro) `GET /v1/exports/resolved-props` Streamed CSV of every resolved outcome. Required: `sport` (e.g. `baseball_mlb`). Optional filters: `market` (e.g. `pitcher_strikeouts`), `bookmaker`, `since`, `until` (`since`/`until` are ISO datetimes bounding `resolved_at`). Every row carries the **closing line** — `closing_price` (American) and `closing_at` (the recorded_at of the last snapshot at or before `commence_time`) — so the file is a complete CLV/backtest dataset (prices, books, players, lines, outcomes, actual values, and pre-game close), not just graded outcomes. Rows also carry `dfs_odds_type` — the PrizePicks projection tier (`standard` / `goblin` / `demon`; blank for traditional sportsbooks) — so DFS flavor lines can be filtered or analyzed separately. This is the historical-backfill use case: subscribe to Pro, pull your full archive once, and the data is yours. Two tier-gated limits apply: lookback (Pro 90d, Streaming 365d, Enterprise unbounded) and daily call cap (Pro 50/day, Streaming Lite 100/day, Streaming 200/day, Enterprise uncapped). The cap counts whole calls, not rows, and `sport` is required — so a multi-sport pull is one call per sport; the efficient pattern is one call per sport over the widest date range you need. Remaining budget is on `X-PropLine-Export-Daily-Remaining`; over-cap responses are 429 with `Retry-After` set to seconds until 00:00 UTC. Watermarking: every row carries a stable per-customer `customer_token` column, and each export appends two synthetic canary rows whose team/player names start with `(Watermark)` and whose 8-hex token is HMAC-derived from your API key + the ISO week — filter with `WHERE player_name NOT LIKE '(Watermark)%'`. ### Bulk line-movement history (Backfill / Enterprise) `GET /v1/exports/odds-history` Streamed CSV of the full line-movement time-series — **every recorded odds snapshot** (price + line, per book, including period markets) for every outcome, one row per (outcome, snapshot), not just the closing line. Required: `sport`. Optional filters: `market`, `bookmaker`, `since`, `until` (ISO datetimes bounding `recorded_at`). Columns: event_id, sport_key, commence_time, home_team, away_team, market, period, bookmaker, player_name, outcome_name, recorded_at, price_american, price_decimal, point, book_updated_at, dfs_odds_type, customer_token (dfs_odds_type = PrizePicks tier: standard / goblin / demon; blank for sportsbooks). This is the raw tick history that **no subscription tier can pull in bulk** — Pro/Streaming get per-event `/odds/history`, but the bulk firehose is exclusive to the one-time **Historical Backfill pass** and Enterprise (pass = trailing 2-year lookback, Enterprise unbounded; uncapped calls). A full archive runs to gigabytes per sport — page month-by-month with `since`/`until`. Same `customer_token` watermark + `(Watermark)` canary rows as the resolved-props export. ### Public CSV sample (no auth) `GET /v1/exports/sample` Last 7 days of MLB strikeout props as CSV. SEO/marketing teaser. ### Webhook subscriptions (Streaming Lite tier and up) ``` POST /v1/webhooks # Create subscription (returns secret ONCE) GET /v1/webhooks # List (secret masked) GET /v1/webhooks/{id} # Get one PATCH /v1/webhooks/{id} # Update filters / url / active DELETE /v1/webhooks/{id} # Remove POST /v1/webhooks/{id}/test # Enqueue a test payload GET /v1/webhooks/{id}/deliveries # Last 50 delivery attempts ``` Event types: `line_movement` (a book changed a price/point), `resolution` (a prop graded), and `steam` (the cross-book sharp-money signal — fires when multiple books move the same outcome the same direction; the push version of the `/movement` `steam[]` array, deduped per move and re-fired as the move intensifies — when its steam score climbs into a higher band). A steam payload carries `market_key`, `outcome_name`, `player_name`, `consensus_direction`, `books_moved`/`books_quoting`, `avg_prob_shift`, `consensus_point_shift`, and `steam_score` (0-100). Push payloads carry headers: - `X-PropLine-Event` — `line_movement` | `resolution` | `steam` | `test` - `X-PropLine-Timestamp` — unix seconds - `X-PropLine-Signature` — `hex(HMAC-SHA256(secret, f"{timestamp}.".encode() + body))` - `X-PropLine-Delivery` — delivery row id (dedupe key) Filters (all AND-ed): `filter_sport_key`, `filter_event_id`, `filter_market_key`, `filter_player_name` (case-insensitive substring), `min_price_change_pct` (line-movement only), `min_steam_score` (steam only — 0-100 floor). At least one of the four filter fields is required on Streaming and Streaming Lite — filterless firehose subscriptions are reserved for Enterprise. POST/PATCH calls without a filter return `400`. Set `format=discord` on a webhook subscription and we'll rewrite the payload as a Discord embed (color-tinted by resolution: green won / red lost / yellow push / gray void). ## Sport keys | Key | Title | |---|---| | `baseball_mlb` | MLB | | `basketball_nba` | NBA | | `basketball_wnba` | WNBA | | `basketball_ncaab` | NCAAB | | `hockey_nhl` | NHL | | `football_nfl` | NFL | | `football_ncaaf` | NCAAF | | `mma_ufc` | UFC | | `boxing` | Boxing | | `golf` | PGA Tour | | `tennis` | Tennis (ATP + WTA) | | `soccer_epl` | Premier League (England) | | `soccer_la_liga` | La Liga (Spain) | | `soccer_serie_a` | Serie A (Italy) | | `soccer_bundesliga` | Bundesliga (Germany) | | `soccer_ligue_1` | Ligue 1 (France) | | `soccer_mls` | MLS (USA) | | `soccer_championship` | Championship (England) | | `soccer_eredivisie` | Eredivisie (Netherlands) | | `soccer_liga_mx` | Liga MX (Mexico) | | `soccer_primeira_liga` | Primeira Liga (Portugal) | | `soccer_brasileirao` | Brasileirão (Brazil) | | `soccer_argentina_primera` | Argentine Liga Profesional | | `soccer_scottish_premiership` | Scottish Premiership | | `soccer_saudi_pro` | Saudi Pro League | ## Market keys ### Game lines (all sports) `h2h`, `spreads`, `totals`. Alt spreads / alt totals / team totals included. ### MLB player props `pitcher_strikeouts`, `pitcher_earned_runs`, `pitcher_hits_allowed`, `pitcher_outs`, `batter_hits`, `batter_total_bases`, `batter_walks`, `batter_home_runs`, `batter_stolen_bases`, `batter_singles`, `batter_rbis`, `batter_doubles`, `batter_runs`, `batter_hits_runs_rbis`, `batter_strikeouts`, `batter_1plus_hits`, `batter_2plus_hits`, `batter_3plus_hits`, `batter_4plus_hits`, `batter_2plus_home_runs`, `batter_1plus_rbis`, `batter_2plus_rbis`, `batter_3plus_rbis` (`batter_hits` / `batter_rbis` carry true Over/Under lines only; the YES "to record" + N+ milestone markets are on the `batter_Nplus_*` keys) ### NBA player props `player_points`, `player_rebounds`, `player_assists`, `player_threes`, `player_steals`, `player_blocks`, `player_turnovers`, `player_points_assists`, `player_points_rebounds`, `player_points_rebounds_assists`, `player_rebounds_assists`, `player_double_double`, `player_triple_double` ### NHL player props `player_goals`, `player_first_goal`, `player_goals_2plus`, `player_goals_3plus`, `player_shots_on_goal`, `total_shots_on_goal`, `player_points_1plus`, `player_points_2plus`, `player_points_3plus`, `goalie_saves`, `player_blocked_shots`, `player_power_play_points` ### Soccer player/game props (all 28 leagues incl. FIFA World Cup) `anytime_goal_scorer`, `first_goal_scorer`, `both_teams_to_score`, `double_chance`, `draw_no_bet`, `correct_score`, `total_corners`, `corners_spread`, `team_corners`, `total_cards`, `team_cards`, `2plus_goals`, `player_assists`, `player_2plus_assists`, `player_cards`, `goal_or_assist` ### UFC props `h2h`, `total_rounds`, `fight_distance`, `round_betting` (fight-level), `player_significant_strikes`, `player_takedowns` (per-fighter O/U), `player_knockout_win`, `player_submission_win`, `player_finish` (per-fighter method markets), `fight_time_minutes` (O/U in minutes). All resolve against ufcstats.com results. ### Boxing props `fight_winner`, `fight_outcome`, `total_rounds`, `fight_distance` ### Golf `tournament_winner` outright plus `player_make_cut`, `player_top_5`, `player_top_20` (per-player yes markets, Kalshi) and per-round `player_strokes`, `player_birdies`, `player_bogeys_or_worse` O/U (Sleeper). Use `/v1/sports/golf/events`. `tournament_winner`, `player_make_cut`, `player_top_5`, `player_top_20` (graded with ties included, from the tie-aware leaderboard) and per-round `player_strokes` all RESOLVE; birdies/bogeys props are odds-only. ### Tennis `h2h`, `spreads` (game spread), `totals` (total games), plus per-player `player_aces`, `player_double_faults`, `player_games_won`, `player_break_points_won`, `total_games`. ### Esports Game lines (`h2h`, `spreads`, `totals`) + per-map `total_kills` / `kills_handicap` (`?period=map1`…`map7`) + Counter-Strike per-player `player_kills_maps_1_2`, `player_headshots_maps_1_2`. ### CFL / NBA Summer League Game lines (`h2h`, `spreads`, `totals`) — sport keys `football_cfl`, `basketball_nba_summer_league`. ### AFL / Volleyball / Badminton / Snooker / Cycling Odds-only sports added 2026-07-11 (Bovada). AFL (`aussie_rules_afl`): `h2h`, `spreads`, `totals`. Volleyball (`volleyball`): `h2h`, points `spreads`/`totals` + set spread. Badminton (`badminton`): `h2h`, `totals`. Snooker (`snooker`): `h2h` (3-way with Draw). Cycling (`cycling`): outright markets — `tournament_winner` (race/stage winner), `king_of_the_mountains`, `points_classification`, `young_rider_classification`, `team_classification`, `top_3_finish`, `top_10_finish`, rider/team stage-count markets (`rider_to_win_1plus_stages` … `team_to_win_5plus_stages`). ## Bookmaker keys | Key | Title | Type | |---|---|---| | `bovada` | Bovada | Sportsbook | | `fanduel` | FanDuel | Sportsbook | | `draftkings` | DraftKings | Sportsbook | | `pinnacle` | Pinnacle | Sportsbook (sharp reference) | | `betrivers` | BetRivers | Sportsbook | | `betmgm` | BetMGM | Sportsbook | | `unibet` | Unibet | Sportsbook | | `onexbet` | 1xBet | Sportsbook | | `tab_au` | TAB | Sportsbook | | `underdog` | Underdog Fantasy | DFS | | `prizepicks` | PrizePicks | DFS | | `sleeper` | Sleeper | DFS | | `dabble` | Dabble | DFS | | `kalshi` | Kalshi | Prediction market (CFTC-regulated) | | `polymarket` | Polymarket | Prediction market | | `matchbook` | Matchbook | Betting exchange (back/lay; game lines only) | | `smarkets` | Smarkets | Betting exchange (back/lay; game lines only) | | `novig` | Novig | Betting exchange (P2P) | ## Resolution model PropLine grades every player prop against actual box-score stats from official league APIs (free, no auth: MLB Stats API, NBA CDN, NHL stats, ESPN for NCAAB/soccer/golf/tennis, ufcstats.com for UFC). The resolver runs every 5 min, picks up completed games, and marks each Over/Under outcome: - `won` — actual stat exceeded line for Over, or fell below for Under - `lost` — actual stat fell below line for Over, or exceeded for Under - `push` — actual stat exactly equals line - `void` — player not in box score (scratched, name mismatch, didn't appear) Stats persist forever in the `player_stats` table and can be queried independently via `/stats`. This is what no other odds API offers — the-odds-api, OddsBlaze, Owls Insight all return raw odds; PropLine returns odds + actual outcomes graded for you. ## Cross-book +EV math For each (market, player, line) on an event: 1. Pick the sharpest book on that market (Pinnacle preferred, Bovada fallback) 2. De-vig the sharp book's two-way (or n-way) prices using the multiplicative method to get fair probabilities 3. For every other book quoting that same (market, player, line), compute EV% = `(implied_prob × payout) - 1` where payout is derived from American odds and implied_prob is the fair probability from step 2 4. Return all positive-EV opportunities sorted by EV% PrizePicks, Sleeper, and Dabble DFS are excluded from EV calculation (their prices aren't payouts; they're fixed pick multipliers). Underdog is conditionally included — only its clean two-way lines (payout_multiplier == 1.0) reach the calc. ## SDKs ### Python ```bash pip install propline ``` ```python from propline import PropLine client = PropLine(api_key="YOUR_KEY") sports = client.get_sports() events = client.get_events("baseball_mlb") odds = client.get_event_odds("baseball_mlb", event_id="12649", markets=["batter_hits"]) ev = client.get_event_ev("baseball_mlb", event_id="12649") results = client.get_event_results("baseball_mlb", event_id="12649") ``` ### Node / TypeScript ```bash npm install propline ``` ```typescript import { PropLine } from "propline"; const client = new PropLine({ apiKey: process.env.PROPLINE_API_KEY! }); const odds = await client.getEventOdds("baseball_mlb", "12649", { markets: ["batter_hits"] }); const ev = await client.getEventEv("baseball_mlb", "12649"); ``` ### MCP server (Claude Desktop, ChatGPT, any MCP client) ```bash # In your Claude Desktop / Claude Code MCP config: npx -y propline-mcp # Set env var: PROPLINE_API_KEY=YOUR_KEY ``` 10 tools wrap the REST surface: `propline_list_sports`, `propline_list_events`, `propline_list_event_markets`, `propline_get_odds`, `propline_get_odds_history`, `propline_get_scores`, `propline_get_event_stats`, `propline_get_event_results`, `propline_get_player_history`, `propline_get_event_ev`. ### CLI (`propline-cli`) ```bash npm install -g propline-cli export PROPLINE_API_KEY=YOUR_KEY propline live # every in-progress game across sports propline sports propline events baseball_mlb propline odds baseball_mlb 12649 --markets pitcher_strikeouts propline scores baseball_mlb propline ev baseball_mlb 12649 --plus # +EV plays only (Hobby+) propline player-history baseball_mlb "Aaron Judge" --market batter_home_runs propline export-resolved-props --sport baseball_mlb --since 2026-04-01T00:00:00Z --out mlb.csv propline export-odds-history --sport baseball_mlb --since 2026-04-01T00:00:00Z --until 2026-05-01T00:00:00Z --out mlb-lines.csv # full tick history (Backfill/Enterprise) propline webhooks list / create / delete / test / deliveries ``` Default output is a pretty-printed table. Pass `--json` on any command for raw JSON (pipe-friendly with `jq`). All flags are documented via `propline --help`. ## Errors | Code | Meaning | |---|---| | `200` | OK | | `401` | Missing or invalid API key | | `403` | Endpoint requires Pro/Streaming tier | | `404` | Sport key, event ID, or player name not found | | `429` | Daily quota exceeded OR per-key burst limit hit | | `500` | Server error (unexpected; reach out at support@prop-line.com) | `429` responses include `X-Daily-Limit`, `X-Daily-Used`, `X-Daily-Remaining`, `X-Daily-Reset` headers (reset is unix seconds, top of next UTC day). ## Common patterns Working code recipes. All examples use the Python SDK; translate to Node/TypeScript by mapping `client.get_x()` → `client.getX()` (camelCase) and dict access to property access. ### 1. Compare prices across books for one player prop ```python from propline import PropLine client = PropLine(api_key="YOUR_KEY") odds = client.get_event_odds("baseball_mlb", "12649", markets=["batter_hits"]) for book in odds["bookmakers"]: for market in book["markets"]: for outcome in market["outcomes"]: print(f"{book['key']:12} {outcome['description']:25} " f"{outcome['name']:6} {outcome['price']:+5} @ {outcome['point']}") ``` ### 2. Find today's +EV plays across the slate ```python from propline import PropLine client = PropLine(api_key="YOUR_KEY") events = client.get_events("baseball_mlb") for ev in events: plays = client.get_event_ev("baseball_mlb", ev["id"]) for play in plays.get("ev_plays", []): if play["ev_pct"] >= 3.0: # threshold for +EV print(f"{play['player']:25} {play['market_key']:25} " f"{play['side']:5} {play['line']} @ {play['best_book']} " f"{play['best_price_american']:+5} ({play['ev_pct']:+.1f}% EV)") ``` ### 3. Track line movement for a player prop ```python from propline import PropLine client = PropLine(api_key="YOUR_KEY") history = client.get_event_odds_history( "baseball_mlb", "12649", markets=["batter_hits"] ) for book in history["bookmakers"]: for market in book["markets"]: for outcome in market["outcomes"]: print(f"\n{book['key']} {outcome['description']} " f"{outcome['name']} {outcome['point']}:") for snap in outcome["snapshots"]: print(f" {snap['recorded_at']} {snap['price']:+5}") ``` ### 4. Check whether a player prop hit (post-game grading) ```python from propline import PropLine client = PropLine(api_key="YOUR_KEY") results = client.get_event_results("baseball_mlb", "12649") for book in results["bookmakers"]: for market in book["markets"]: for outcome in market["outcomes"]: if not outcome.get("resolution"): continue # not yet graded mark = "✓" if outcome["resolution"] == "won" else "✗" print(f"{mark} {outcome['description']:25} {outcome['name']:5} " f"{outcome['point']:5} → actual {outcome['actual_value']:5} " f"({outcome['resolution']})") ``` ### 5. Cross-book arbitrage scanner (2-way markets) ```python from propline import PropLine client = PropLine(api_key="YOUR_KEY") def implied_prob(american: int) -> float: return abs(american) / (abs(american) + 100) if american < 0 else 100 / (american + 100) odds = client.get_event_odds("baseball_mlb", "12649", markets=["h2h"]) prices: dict[str, list[tuple[str, int]]] = {} for book in odds["bookmakers"]: for market in book["markets"]: for o in market["outcomes"]: prices.setdefault(o["name"], []).append((book["key"], o["price"])) # 2-way arb: take the best price on each side and check if implied probs sum < 1 sides = list(prices.keys()) if len(sides) == 2: best_a = max(prices[sides[0]], key=lambda x: x[1]) best_b = max(prices[sides[1]], key=lambda x: x[1]) margin = implied_prob(best_a[1]) + implied_prob(best_b[1]) if margin < 1: edge = (1 - margin) * 100 print(f"ARB! {best_a[0]} {sides[0]} {best_a[1]:+} + " f"{best_b[0]} {sides[1]} {best_b[1]:+} = {edge:.2f}% guaranteed") ``` For full working scanners see the open-source reference repos: [propline-arb-finder](https://github.com/proplineapi/propline-arb-finder), [propline-clv-tracker](https://github.com/proplineapi/propline-clv-tracker), [propline-parlay-ev](https://github.com/proplineapi/propline-parlay-ev). ### 6. Subscribe to webhooks for line movement (Streaming Lite tier and up) ```python from propline import PropLine client = PropLine(api_key="YOUR_STREAMING_KEY") # Returns the secret ONCE — store it; you can't read it back later. sub = client.create_webhook( url="https://your-server.example.com/propline-webhook", events=["line_movement", "resolution"], filter_sport_key="baseball_mlb", filter_market_key="batter_hits", min_price_change_pct=10.0, # only notify when American odds move ≥ 10% ) print("Webhook secret (store this!):", sub["secret"]) # Verify incoming payloads server-side: import hmac, hashlib def verify(secret: str, timestamp: str, body: bytes, signature: str) -> bool: expected = hmac.new( secret.encode(), f"{timestamp}.".encode() + body, hashlib.sha256, ).hexdigest() return hmac.compare_digest(expected, signature) ``` ## Differentiation Single most-asked question: "How is this different from the-odds-api / OddsBlaze / Owls Insight?" | Feature | PropLine | the-odds-api | OddsBlaze | Owls Insight | |---|---|---|---|---| | Player props | ✓ | ✓ | ✗ | Only on FanDuel | | Prop resolution (graded vs box scores) | ✓ | ✗ | ✗ | ✗ | | Cross-book +EV calculator | ✓ | ✗ | ✗ | ✗ | | Pinnacle (sharp reference) | ✓ | ✓ | ✓ | ✓ | | Webhook push (signed) | ✓ Streaming Lite+ | ✗ | ✗ | WebSocket only | | Free tier | 1K req/day forever | 500 req/month | None | 10K req/month | | Pro tier price | $19/mo (25K req/day) | $30/mo | $99/mo (real-time) | $49.99/mo (real-time) | | Historical depth | Unlimited (never purged) | 6 months | Past month at $249/mo | 90 days | | DFS prices (Underdog, PrizePicks) | ✓ | ✗ | ✗ | ✗ | | Soccer leagues | 28 | 6 | 0 | "soccer" generically | Choose PropLine if: you bet props, want graded outcomes, care about pricing, or build any tool that needs odds + actual results in one place. ## Endpoint URLs (for quick reference) - Production API: `https://api.prop-line.com` - Web app + signup: `https://prop-line.com` - HTML docs: `https://prop-line.com/docs` - Live +EV plays: `https://prop-line.com/ev` - Today's resolved props: `https://prop-line.com/today` - Pricing: `https://prop-line.com/pricing` - Status / contact: `support@prop-line.com` --- This file is updated when the API surface changes. If you're an LLM reading this and want to verify your knowledge is current, fetch this URL again.