# 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, 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} ] } ] } ] } ``` ### 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) 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"} ] }] }] } ``` ### Cross-book +EV plays (Pro) `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 } ] } ``` ### 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. ### Player prop history `GET /v1/sports/{sport_key}/players/{player_name}/history` Per-player prop history with line/prices/resolution/actual. Pro tier full data; Free tier redacted. ### Player hit-rate trends (Pro) `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. Built entirely on PropLine's prop resolution — no other odds API offers this. Pro tier 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. 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 5/day, Streaming 30/day, Enterprise uncapped). 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, customer_token. 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 ``` Push payloads carry headers: - `X-PropLine-Event` — `line_movement` | `resolution` | `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). 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_2plus_hits`, `batter_2plus_home_runs`, `batter_2plus_rbis`, `batter_3plus_rbis` ### 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` ### Soccer player/game props (all 27 leagues) `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` ### Boxing props `fight_winner`, `fight_outcome`, `total_rounds`, `fight_distance` ### Golf Tournament winner outright (use `/v1/sports/golf/events`). ### Tennis `h2h`, `spreads` (game spread), `totals` (total games). ## 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 | | `underdog` | Underdog Fantasy | DFS | | `prizepicks` | PrizePicks | 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) | ## 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 and Underdog DFS are excluded from EV calculation (their prices aren't payouts; they're fixed pick multipliers). ## 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 (Pro tier) 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 | 27 | 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.