Skip to main content
Returns every market and every outcome we currently hold for one game. The response is flattened: one top-level game object plus a markets dict keyed by pn_market_type, where each value is a flat list of outcomes.
curl -s "https://books.polynode.dev/v1/games/nba-por-phx-2026-04-15/markets?key=pn_live_YOUR_KEY"
id
string
required
Polynode slug or raw game_id.
key
string
required
Your API key.
market
string
Comma-separated list of pn_market_type values to include. Example: market=moneyline,spreads,totals. If omitted, all markets are returned.
book
string
Comma-separated list of book names to include. Example: book=DraftKings,Polymarket,BetMGM. If omitted, all books are returned.

Response

Live NBA game (Portland @ Phoenix, Q3 03:42, 79-73) filtered to moneyline only, three books:
curl "https://books.polynode.dev/v1/games/nba-por-phx-2026-04-15/markets?market=moneyline&book=DraftKings,Polymarket,BetMGM&key=pn_live_YOUR_KEY"
{
  "game": {
    "game_id": "24860-38874-2026-04-14",
    "pn_slug": "nba-por-phx-2026-04-15",
    "pn_league_code": "nba",
    "sport_category": "basketball",
    "start_date": "2026-04-15T02:00:00Z",
    "status": "live",
    "venue": "Mortgage Matchup Center",
    "broadcast": "Amazon",
    "home_team": {
      "name": "Phoenix Suns",
      "city": "Phoenix",
      "mascot": "Suns",
      "abbreviation_pn": "phx"
    },
    "away_team": {
      "name": "Portland Trail Blazers",
      "city": "Portland",
      "mascot": "Trail Blazers",
      "abbreviation_pn": "por"
    },
    "scores": {
      "status": "Live",
      "score_home": 73,
      "score_away": 79,
      "period": "3",
      "clock": "03:42",
      "is_live": true
    }
  },
  "market_count": 1,
  "outcome_count": 6,
  "markets": {
    "moneyline": [
      { "book": "BetMGM",     "outcome": "Portland Trail Blazers", "price": -225.0, "points": null, "player_id": null },
      { "book": "DraftKings", "outcome": "Portland Trail Blazers", "price": -188.0, "points": null, "player_id": null },
      { "book": "Polymarket", "outcome": "Portland Trail Blazers", "price": -184.0, "points": null, "player_id": null },
      { "book": "BetMGM",     "outcome": "Phoenix Suns",            "price":  175.0, "points": null, "player_id": null },
      { "book": "DraftKings", "outcome": "Phoenix Suns",            "price":  145.0, "points": null, "player_id": null },
      { "book": "Polymarket", "outcome": "Phoenix Suns",            "price":  165.0, "points": null, "player_id": null }
    ]
  }
}

Top-level shape

FieldTypeDescription
gameobjectFull game envelope — same as /v1/games/{id}. Includes teams, scores, venue, status.
market_countintegerNumber of distinct pn_market_type keys returned (after filters).
outcome_countintegerTotal number of outcomes across all markets (after filters).
marketsobjectDict keyed by pn_market_type. Each value is a flat list of outcomes. Markets with zero outcomes after filtering are omitted.

Outcome object

FieldTypeDescription
bookstringSportsbook name ("DraftKings", "BetMGM", "Polymarket", "bet365", etc.). Pass into ?book= to filter.
outcomestringOutcome label. Team name for moneyline ("Phoenix Suns"), "Over 4.5" / "Under 4.5" for totals, player name with line for player props ("LeBron James Over 24.5"). Same field name as the WS price_change event so REST and WS share vocabulary.
pricenumberAmerican odds. Negative = favorite (-188 = risk 188towin188 to win 100), positive = underdog (+165 = risk 100towin100 to win 165).
pointsnumber | nullSpread or total line when applicable. null for moneyline.
player_idstring | nullUpstream player id when the outcome is a player prop.

Filtering examples

All books, moneyline only:
curl ".../markets?market=moneyline&key=..."
DraftKings + Polymarket across all market types:
curl ".../markets?book=DraftKings,Polymarket&key=..."
Polymarket moneyline + totals only:
curl ".../markets?market=moneyline,totals&book=Polymarket&key=..."
Filtering happens server-side. An empty result set returns 200 with markets: {} and market_count: 0 — not a 404.

Post-restart warmup window

When the service restarts, the market poll loop backfills one market type at a time across all sports. For ~30 seconds after startup, you may see fewer pn_market_type keys than the steady-state total. Call the same endpoint again a few seconds later for a fuller view, or subscribe to /ws/odds to stream the fill as it happens.

Refresh semantics

  • Live games are re-polled every ~1 second. In-play markets reflect upstream within 1-2 seconds.
  • Idle games (unplayed or final) are re-polled every ~10 seconds.
  • Subscribe to /ws/odds to receive price_change deltas as soon as any book moves.

Market types available per sport

The exact set depends on sport and whether the game is pre-match or live.
  • NBA / WNBA / NCAAB: moneyline, spreads, totals, first_half_*, points, rebounds, assists, threes, double_doubles, assists_points_rebounds
  • NFL / NCAAF: moneyline, spreads, totals, first_half_*, passing/rushing/receiving yards, anytime TD
  • MLB: moneyline, run_line, totals, nrfi, hits, strikeouts, total bases
  • NHL: moneyline, puck_line, totals, shots on goal, player goalscorer
  • Soccer (all leagues): moneyline (1x2), double_chance, both_teams_to_score, correct_score, total_goals, total_corners, soccer_anytime_goalscorer, soccer_halftime_result
  • Tennis: moneyline, set_handicap, total_games
  • UFC / MMA: moneyline, method_of_victory, go_the_distance

Rate limits

The REST API enforces 600 requests per minute per API key, with a burst allowance of 60 requests. Requests beyond the limit return 429 Too Many Requests with retry-after: 60. WebSocket connections are not counted against this limit — stream consumers don’t need to poll.