{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://raw.githubusercontent.com/api-evangelist/sharpapi/main/json-schema/sharpapi-odds-schema.json",
  "title": "Odds",
  "x-generated": "2026-10-14",
  "x-method": "derived",
  "x-generator": "derive-json-schema.py",
  "x-source": "openapi/sharpapi-openapi.json#/components/schemas/Odds",
  "type": "object",
  "required": [
    "id",
    "sportsbook",
    "sportsbook_name",
    "event_id",
    "sport",
    "league",
    "home_team",
    "away_team",
    "market_type",
    "selection",
    "selection_type",
    "odds_american",
    "odds_decimal",
    "probability",
    "event_start_time",
    "is_live",
    "timestamp"
  ],
  "properties": {
    "id": {
      "type": "string"
    },
    "sportsbook": {
      "type": "string"
    },
    "sportsbook_name": {
      "type": "string"
    },
    "event_id": {
      "type": "string"
    },
    "sport": {
      "type": "string"
    },
    "league": {
      "type": "string"
    },
    "home_team": {
      "type": "string"
    },
    "away_team": {
      "type": "string"
    },
    "market_type": {
      "type": "string"
    },
    "selection": {
      "type": "string",
      "description": "The outcome this price is on — a team name on moneyline/spread markets, `Over`/`Under` on totals. On market types whose name *starts with* `player_` it is always the side (`Over`, `Under`, `Yes` or `No`), never the player; player identity is carried solely by `player_name`. `is_player_prop: true` is wider than that set — it marks every player-level market, including scorer markets (`anytime_goal_scorer`, `anytime_touchdown_scorer`), period- and segment-scoped player markets (`1st_quarter_player_points`) and `most_*_player`. On those rows `selection` is passed through as the book sent it, which normally names the player, and `selection_type` is `other` where the market has no Over/Under axis. Rare prop rows whose side is not one of those four (parity props with `selection_type` `odd`/`even`, and props the upstream book never attributed to a player) pass the book's own label through — read `selection_type` there."
    },
    "selection_type": {
      "type": "string",
      "description": "Canonical side identifier. Two-way markets emit one of the simple values listed in `enum`. Multi-axis markets (soccer double-chance, BTTS-combined, result × O/U, MMA round betting, tennis set betting, correct-score, etc.) emit compound forms joined by `_` — e.g. `home_over`, `away_under`, `home_yes`, `home_r1`. Long-tail markets such as `correct_score` and `set_betting` also encode score-level outcomes directly (e.g. `2_1`). Treat any unrecognised value as opaque rather than erroring. See the `/odds` page for the full table.",
      "enum": [
        "home",
        "away",
        "over",
        "under",
        "yes",
        "no",
        "even",
        "odd",
        "draw",
        "other",
        "home_over",
        "home_under",
        "away_over",
        "away_under",
        "home_yes",
        "home_no",
        "away_yes",
        "away_no",
        "draw_yes",
        "draw_no",
        "home_draw",
        "away_draw",
        "home_away"
      ]
    },
    "team_side": {
      "type": "string",
      "description": "Raw team-side hint from the adapter — useful when `selection_type` carries a compound value (e.g. `home_over`) and you want just the team axis without parsing. Absent when the adapter did not stamp it.",
      "enum": [
        "home",
        "away",
        "draw"
      ]
    },
    "odds_american": {
      "type": "number"
    },
    "odds_decimal": {
      "type": "number"
    },
    "probability": {
      "type": "number",
      "description": "Implied probability (0-1)"
    },
    "line": {
      "type": [
        "number",
        "null"
      ],
      "description": "Point spread or total line (null for moneyline)"
    },
    "is_alternate_line": {
      "type": "boolean",
      "description": "True when this row's `line` differs from the best-effort main line for its cohort. A cohort is (event, market_type, selection axis), resolved per sportsbook: the selection axis is the player for player-prop markets, the team side for spread and team-total markets, and the raw selection everywhere else. False for the main line, for markets with no line (moneyline, outright), and for rows whose cohort has not been published yet. Mutually exclusive with `is_main_line`: both true never coexists, both false is the cohort-pending state."
    },
    "is_main_line": {
      "type": "boolean",
      "description": "True when this row's `line` equals the best-effort main line for its cohort, and for markets with no line (moneyline, outright), which are always main by definition. A cohort is (event, market_type, selection axis), resolved per sportsbook: the selection axis is the player for player-prop markets, the team side for spread and team-total markets, and the raw selection everywhere else. Best-effort, not a uniqueness guarantee: several rows of one (event, market_type) are normally main at once, one per selection axis — Over and Under of a game total are separate cohorts, and so is each outcome of a combination market. False for alternate lines and for cohort-pending rows. Filter with `is_main_line=true` for a main-lines-only view that excludes cohort-pending rows."
    },
    "event_start_time": {
      "type": "string",
      "format": "date-time"
    },
    "is_live": {
      "type": "boolean"
    },
    "is_active": {
      "type": "boolean",
      "description": "true (default) = market open and bettable; false = market suspended/closed with the price frozen. Exposed as a queryable field you can also filter on. Absent is treated as true. An active->suspended transition is emitted on the odds stream (odds:update with is_active=false)."
    },
    "timestamp": {
      "type": "string",
      "format": "date-time",
      "description": "ISO 8601 time SharpAPI last refreshed/observed this odd through its pipeline — advances on every ingest cycle. A feed-freshness / liveness signal. NOTE: this is delivery freshness, not a price-last-changed time; SharpAPI does not expose a separate when-the-price-moved field."
    },
    "event_uuid": {
      "type": "string",
      "description": "Stable canonical event UUID from the SharpAPI atlas. Use for cross-feed joins; absent for unmapped events."
    },
    "external_event_id": {
      "type": "string",
      "description": "The sportsbook's own native event ID, when distinct from `event_id`."
    },
    "deep_link": {
      "type": "string",
      "description": "Resolver URL pointing to the sportsbook's event or bet-slip page. Pass `state=` on the request to route through state-specific subdomains for books that need them (BetMGM, Caesars, BetRivers)."
    },
    "market_id": {
      "type": "string",
      "description": "Sportsbook's own native market identifier. Absent when the book does not expose one."
    },
    "selection_id": {
      "type": "string",
      "description": "Sportsbook's own native selection/outcome identifier. Absent when the book does not expose one."
    },
    "volume": {
      "type": "number",
      "description": "Matched volume on this selection. Exchange books only (Betfair, Novig, ProphetX, Polymarket). Units follow the originating book."
    },
    "volume_24h": {
      "type": "number",
      "description": "Trailing 24-hour matched volume on this selection. Exchange books only."
    },
    "open_interest": {
      "type": "number",
      "description": "Outstanding open interest on this selection. Exchange books only."
    },
    "max_bet": {
      "type": "number",
      "description": "Largest size available on this row. On a traditional sportsbook (Pinnacle, Circa Sports, SBOBET) the maximum wager the book will accept, in USD. On an exchange-style book the money resting at the best price, denominated in `size_currency`. Absent rather than 0.0 when no size is published; read absence as \"not published\", never as zero. `has_limits` on /sportsbooks is the live per-book answer to whether a book publishes this field."
    },
    "size_currency": {
      "type": "string",
      "description": "ISO-4217 code the money figures on this row are denominated in: `max_bet`, `best_bid_liquidity` and `total_liquidity`. GBP on Betfair and Smarkets, USD on every other book that publishes a size. Never converted - the venue's own figure is passed through in the venue's own currency. Absent on rows carrying no size, and on traditional sportsbooks, whose `max_bet` is a USD wager cap rather than a resting size."
    },
    "best_bid_liquidity": {
      "type": "number",
      "description": "Money resting at the published price - what a taker can match without moving the price. Exchange books only, denominated in `size_currency`. 0.0 means nothing is resting there; absence means the venue does not publish the figure."
    },
    "total_liquidity": {
      "type": "number",
      "description": "Risk-able size summed across the whole book for this selection, not just the best price. Exchange books only, denominated in `size_currency`. 0.0 means an empty book; absence means the venue does not publish the figure. Not derivable from `best_bid_liquidity`: some books publish one without the other."
    },
    "player_name": {
      "type": [
        "string",
        "null"
      ],
      "description": "Player name (player prop markets only). The only field carrying player identity — `selection` holds the side. The canonical parse for a prop row is `player_name` + `selection_type` + `line`."
    },
    "stat_category": {
      "type": [
        "string",
        "null"
      ],
      "description": "Stat category (player prop markets only)"
    },
    "home_pitcher": {
      "type": "string",
      "description": "MLB only. Home-team starting pitcher when published by the book."
    },
    "away_pitcher": {
      "type": "string",
      "description": "MLB only. Away-team starting pitcher when published by the book."
    },
    "polymarket_resolution": {
      "type": "string",
      "description": "Polymarket only. UMA optimistic-oracle resolution status when the market has terminated. Absent for live (unresolved) markets and for non-Polymarket books.",
      "enum": [
        "settled_normal",
        "voided",
        "disputed",
        "proposed",
        "unknown"
      ]
    }
  }
}