-
Notifications
You must be signed in to change notification settings - Fork 1
API DOCUMENTATION
Comprehensive API reference for MCP tools, Backend HTTP endpoints, and Frontend architecture.
The MCP server exposes 30+ tools to Claude Desktop via stdio transport. All responses follow the pattern:
{
"success": true,
"data": { ... },
"error": "error message" // only on failure
}List all available sports from The Odds API.
Parameters:
-
all_sports(bool): If False (default), returns only in-season sports
Response:
{
"success": true,
"data": [
{
"key": "americanfootball_nfl",
"title": "NFL",
"group": "American Football",
"active": true,
"has_outrights": false
}
]
}Get current betting odds for upcoming games.
Parameters:
-
sport(str): Sport key (e.g.,americanfootball_nfl,basketball_nba) -
regions(str): Comma-separated bookmaker regions (us, us2, uk, au, eu) -
markets(str): Comma-separated markets (h2h, spreads, totals, player_points, etc.) -
odds_format(str): american, decimal, or fractional -
date_format(str): iso or unix
Markets:
-
Game Markets:
h2h(moneyline),spreads,totals,outrights -
Player Props: 70+ markets including
player_points,player_rebounds,player_assists,player_pass_tds,player_rush_yds,player_home_runs,player_shots_on_goal
Response:
{
"success": true,
"data": [
{
"id": "event_id",
"sport_key": "basketball_nba",
"commence_time": "2026-01-09T19:00:00Z",
"home_team": "Los Angeles Lakers",
"away_team": "Boston Celtics",
"bookmakers": [
{
"key": "draftkings",
"title": "DraftKings",
"markets": [
{
"key": "h2h",
"outcomes": [
{"name": "Los Angeles Lakers", "price": -150},
{"name": "Boston Celtics", "price": +130}
]
}
]
}
]
}
],
"usage": {
"remaining": "492",
"used": "8"
}
}Get live and recent scores.
Parameters:
-
sport(str): Sport key -
days_from(int): Number of days back to retrieve scores
Response:
{
"success": true,
"data": [
{
"id": "event_id",
"sport_key": "basketball_nba",
"commence_time": "2026-01-08T19:00:00Z",
"completed": true,
"home_team": "Los Angeles Lakers",
"away_team": "Boston Celtics",
"scores": [
{"name": "Los Angeles Lakers", "score": "112"},
{"name": "Boston Celtics", "score": "108"}
]
}
]
}Get detailed odds for a specific event.
Natural language search for odds by team name or matchup.
Parameters:
-
query(str): Team name or matchup (e.g., "Lakers", "Lakers vs Celtics") -
sport(str): Optional sport key to narrow search -
markets(str): Comma-separated markets to include
Get formatted scoreboard for any supported league — automatically resolves the ESPN sport type.
Supported leagues: nfl, nba, mlb, nhl, wnba, college-football, mens-college-basketball, womens-college-basketball
Parameters:
-
league(str): League code (e.g., "nfl", "nba", "mlb", "nhl") -
date(str): Optional date in YYYYMMDD format (default: today)
Response:
{
"success": true,
"league": "NBA",
"formatted_output": "| Away Team | Score | Home Team | Score | Status |\n...",
"game_count": 8
}Get raw ESPN scoreboard data (JSON).
Parameters:
-
sport(str): Sport type (football, basketball, baseball, hockey, soccer) -
league(str): League code (nfl, nba, mlb, nhl, etc.) -
date(str): Optional date in YYYYMMDD format -
limit(int): Max games (default 10, max 25)
Response:
{
"success": true,
"data": {
"leagues": [...],
"events": [
{
"id": "401547516",
"name": "Los Angeles Lakers at Boston Celtics",
"shortName": "LAL @ BOS",
"competitions": [...]
}
]
}
}Get formatted Markdown scoreboard table.
Prefer get_scoreboard(league) for simpler usage — it resolves the sport type automatically.
Returns: Markdown table with:
- Team names, scores
- Game status (🔴 LIVE, ✅ FINAL, 🕐 Scheduled)
- Time/broadcast info
Get interactive React artifact scoreboard.
Returns: Structured data for Claude to render as interactive cards with team colors, logos, expandable odds.
Get ASCII art matchup cards.
Returns: Text with box-drawing characters:
┌────────────────────────────────────────────────────────────────┐
│ │
│ MATCHUP │
│ │
├────────────────────────────────────────────────────────────────┤
│ │
│ Los Angeles Lakers vs Boston Celtics │
│ │
│ 112 - 108 │
│ │
│ Tue, Jan 08 @ 07:00 PM │
│ │
└────────────────────────────────────────────────────────────────┘
Get league standings.
List all teams in a league.
Get detailed team information.
Get team schedule and results.
Get latest news articles.
Search for teams/players/content.
Combines ESPN game data with betting odds.
Compare odds across bookmakers in Markdown table.
Get quick reference table of team IDs, abbreviations, and divisions.
Parameters:
-
sport(str): nfl, nba, or nhl
Returns: Markdown table with all teams.
Fuzzy match team name to ESPN ID.
Tools provided by dashboard_mcp_server.py. Require DASHBOARD_API_KEY (API key with sk_ prefix) and DASHBOARD_API_URL environment variables. All routes authenticate via Authorization: Bearer sk_<key>.
Create a single straight bet on the dashboard.
Parameters:
-
game_id(str): Game UUID fromget_active_games() -
selection_type(str):"moneyline","spread", or"total" -
selection(str):"home","away","over", or"under" -
stake(float): Dollar amount to wager (positive) -
odds(int): American odds — must be ≤ −100 or ≥ +100 (e.g. −110, +150) -
line(float, optional): Spread or total line (e.g. −3.5, 215.5) -
name(str, optional): Human-readable bet name (auto-generated if omitted)
Note: Parlay bets require the dashboard web UI — parlay MCP support is not yet implemented.
Get games available for betting.
Parameters:
-
sport(str, optional): Sport key filter (e.g."basketball_nba") -
status(str, optional): Status filter (e.g."scheduled","in_progress")
Returns: {status, data: {games: [...], count}}
Get the user's betting history. Scoped to the API key owner.
Parameters:
-
status(str):"all","pending","won","lost", or"push"(default:"all")
Returns: {status, data: {bets: [...], total, limit, offset}}
Get detailed information about a specific bet. Returns 404 if the bet does not belong to the API key owner.
Parameters:
-
bet_id(str): UUID of the bet
Returns: {status, data: {bet: {...legs, game details, timeline}}}
Get current bookmaker odds for a specific game.
Parameters:
-
game_id(str): UUID of the game
Returns: {status, data: {game: {...}, odds: [{bookmaker, marketType, homePrice, awayPrice, homeSpread, ...}]}}
Search for teams by name or abbreviation.
Parameters:
-
query(str): Search term (e.g."Lakers","LAL","Los Angeles") — min 1 character
Returns: {status, data: {teams: [{id, name, abbr, sport, logoUrl}], count}}
Get betting statistics and performance summary for the current user. Scoped to the API key owner.
Returns: {status, data: {active: {count, total_stake, potential_return}, today: {bets, won, lost, pnl}, this_week: {bets, win_rate, pnl}, all_time: {total_bets, win_rate, total_pnl, roi}}}
Get full betting context for AI advice generation. Returns up to 100 active bets, last 10 settled bets, all-time stats, sport breakdown, and a risk analysis. Scoped to the API key owner.
Returns: {status, data: {active_bets, recent_results, bankroll_exposure, sport_breakdown}, analysis: {risk_level, exposure_percentage, is_diversified, current_streak, suggestions}}
Get today's games annotated with the user's existing betting exposure. Game window spans midnight-to-midnight so earlier-today games are included.
Parameters:
-
sport(str, optional): Sport key filter (e.g."basketball_nba") -
only_with_bets(bool): If True, returns only games where the user has bets
Returns: {status, data: {games: [{id, matchup, sport, commence_time, status, my_bets: [...], total_exposure}], total_games, total_exposure}}
Express + TypeScript REST API for the web dashboard. Base URL: http://localhost:3001/api
Session routes (/api/auth, /api/bets, /api/games, etc.) use cookie-based session authentication via OAuth2 (Microsoft/Google).
MCP routes (/api/mcp/*) use API key authentication — pass the sk_-prefixed key as a Bearer token:
Authorization: Bearer sk_<your-api-key>API keys are created via the dashboard under Settings → API Keys and scoped with per-resource permissions (bets, stats, write).
All endpoints return:
{
"status": "success" | "error",
"data": { ... }, // on success
"message": "...", // on error
"error": "..." // detailed error message
}OAuth2 authentication with Microsoft and Google providers.
Get authentication status and available providers.
Response:
{
"authEnabled": true,
"authMode": "oauth2",
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "John Doe",
"provider": "microsoft"
},
"providers": {
"microsoft": true,
"google": true
}
}Initiate Microsoft/Azure AD OAuth2 flow.
Redirects to Microsoft login page with configured scopes (openid, profile, email).
OAuth2 callback endpoint (handled automatically).
Initiate Google OAuth2 flow.
Redirects to Google login page with configured scopes.
Google OAuth2 callback endpoint (handled automatically).
Log out current user and destroy session.
Response:
{
"success": true
}Get current authenticated user.
Response:
{
"user": {
"id": "uuid",
"email": "user@example.com",
"name": "John Doe",
"provider": "microsoft"
},
"authEnabled": true
}401 Response if not authenticated:
{
"error": "Not authenticated"
}Manage API keys for programmatic access (alternative to OAuth2).
List all API keys for current user.
Response:
{
"success": true,
"data": {
"keys": [
{
"id": "uuid",
"name": "Production Bot",
"keyPrefix": "btk_abc123",
"permissions": {
"read": true,
"write": true,
"bets": true,
"stats": true
},
"lastUsedAt": "2026-01-13T10:30:00Z",
"expiresAt": "2027-01-13T00:00:00Z",
"revoked": false,
"createdAt": "2026-01-01T00:00:00Z",
"updatedAt": "2026-01-13T10:30:00Z"
}
]
}
}Note: Full API key is never returned in list endpoints. keyPrefix shows first 10 characters only.
Create a new API key.
Request Body:
{
"name": "Production Bot",
"permissions": {
"read": true,
"write": true,
"bets": true,
"stats": true
},
"expiresAt": "2027-01-13T00:00:00Z" // optional
}Response:
{
"success": true,
"data": {
"key": "btk_abc123xyz789def456ghi...", // Full key - only shown ONCE!
"id": "uuid",
"name": "Production Bot",
"keyPrefix": "btk_abc123",
"permissions": { ... },
"expiresAt": "2027-01-13T00:00:00Z",
"createdAt": "2026-01-13T10:30:00Z"
},
"message": "API key created successfully. Save this key - it will not be shown again."
}Update API key name or permissions.
Request Body:
{
"name": "Updated Name",
"permissions": {
"read": true,
"write": false,
"bets": true,
"stats": true
}
}Response:
{
"success": true,
"data": {
"id": "uuid",
"name": "Updated Name",
"permissions": { ... },
"updatedAt": "2026-01-13T11:00:00Z"
}
}Note: Cannot update keyHash, keyPrefix, expiresAt, or revoked via this endpoint.
Revoke (soft delete) an API key.
Response:
{
"success": true,
"message": "API key revoked successfully"
}Note: Revoked keys are not deleted but marked as revoked: true. They can no longer be used for authentication.
Include API key in request header:
Authorization: Bearer btk_abc123xyz789def456ghi...Or as query parameter (not recommended for production):
GET /api/games?apiKey=btk_abc123xyz789def456ghi...
Authentication Order:
- Check for API key in
Authorizationheader - Check for API key in query parameter
- Check for session authentication (OAuth2)
List bets with filters.
Query Parameters:
-
status(string): Filter by status (pending, won, lost, push, cancelled) -
betType(string): Filter by type (single, parlay, teaser) -
sportKey(string): Filter by sport -
startDate(ISO string): Filter by date range start -
endDate(ISO string): Filter by date range end -
limit(number): Max results (default: 50) -
offset(number): Pagination offset
Response:
{
"status": "success",
"data": {
"bets": [
{
"id": "uuid",
"name": "Lakers ML",
"betType": "single",
"stake": 100.00,
"status": "pending",
"oddsAtPlacement": -150,
"potentialPayout": 166.67,
"createdAt": "2026-01-08T10:00:00Z",
"legs": [
{
"id": "uuid",
"gameId": "uuid",
"selectionType": "moneyline",
"selection": "home",
"teamName": "Los Angeles Lakers",
"odds": -150,
"status": "pending",
"game": {
"awayTeamName": "Boston Celtics",
"homeTeamName": "Los Angeles Lakers",
"commenceTime": "2026-01-09T19:00:00Z"
}
}
]
}
],
"total": 42,
"limit": 50,
"offset": 0
}
}Create a new bet.
Request Body:
{
"name": "Lakers ML + Celtics Spread",
"betType": "parlay",
"stake": 50.00,
"legs": [
{
"gameId": "uuid",
"selectionType": "moneyline",
"selection": "home",
"teamName": "Los Angeles Lakers",
"odds": -150
},
{
"gameId": "uuid",
"selectionType": "spread",
"selection": "away",
"teamName": "Boston Celtics",
"line": -5.5,
"odds": -110
}
],
"notes": "Feeling confident about both teams"
}Response:
{
"status": "success",
"data": {
"id": "uuid",
"name": "Lakers ML + Celtics Spread",
"betType": "parlay",
"stake": 50.00,
"status": "pending",
"oddsAtPlacement": 179.55,
"potentialPayout": 139.77,
"legs": [...]
}
}Get single bet with full details.
Update bet (name, stake, notes).
Request Body:
{
"name": "Updated Name",
"stake": 75.00,
"notes": "Updated notes"
}Delete a bet.
Manually settle a bet.
Request Body:
{
"status": "won",
"actualPayout": 166.67
}Get betting statistics.
Query Parameters:
-
sportKey(string): Filter by sport -
betType(string): Filter by bet type -
startDate(ISO string): Date range start -
endDate(ISO string): Date range end
Response:
{
"status": "success",
"data": {
"totalBets": 142,
"totalStake": 7150.00,
"totalPayout": 8234.50,
"netProfit": 1084.50,
"roi": 15.17,
"winRate": 54.23,
"avgOdds": -114.5,
"byStatus": {
"pending": 12,
"won": 77,
"lost": 48,
"push": 5
},
"byType": {
"single": 89,
"parlay": 53
},
"bySport": {
"basketball_nba": { "bets": 45, "netProfit": 523.50 },
"americanfootball_nfl": { "bets": 62, "netProfit": 561.00 }
}
}
}List games with filters (timezone-aware).
Query Parameters:
-
date(YYYY-MM-DD): Filter by specific date in user's local timezone -
sport(string): Sport key (e.g.,basketball_nba,americanfootball_nfl, orall) -
status(string): Game status (scheduled, in_progress, completed) -
timezoneOffset(number): User's timezone offset in minutes (e.g., 420 for MST/UTC-7)
Timezone Handling: The API accepts the user's timezone offset to correctly filter games by date. For example:
- User in MST (UTC-7) requests
date=2026-01-09&timezoneOffset=420 - API converts to UTC range:
2026-01-09 07:00:00Zto2026-01-10 06:59:59Z - Returns all games occurring during Jan 9 in MST
Response:
{
"status": "success",
"data": {
"games": [
{
"id": "uuid",
"exterKey": "basketball_nba",
"sportName": "NBA Basketball",
"sport": {
"id": "uuid",
"key": "basketball_nba",
"name": "NBA Basketball",
"groupName": "Basketball",
"isActive": trueltics",
"homeTeamName": "Los Angeles Lakers",
"commenceTime": "2026-01-09T19:00:00Z",
"status": "scheduled",
"completed": false,
"sport": {
"key": "basketball_nba",
"name": "NBA Basketball"
},
"currentOdds": [
{
"id": "uuid",
"bookmaker": "draftkings",
"marketType": "h2h",
"selection": "home",
"price": -150,
"lastUpdated": "2026-01-08T15:30:00Z"
}
]
}
],
"count": 15
}
}
**Response:**
```json
{
"status": "success",
"data": {
"id": "uuid",
"externalId": "odds_api_event_id",
"sportId": "uuid",
"awayTeamName": "Boston Celtics",
"homeTeamName": "Los Angeles Lakers",
"commenceTime": "2026-01-09T19:00:00Z",
"statgameId": "uuid",
"bookmaker": "draftkings",
"marketType": "spread",
"selection": "home",
"price": -110,
"point": -5.5
}
],
"count": 245
}
}Manage futures bets (championship winners, season awards, long-term props).
Get all active futures with current odds.
Query Parameters:
-
sportKey(string): Filter by sport (e.g.,basketball_nba,americanfootball_nfl) -
status(string): Filter by status (active,completed,settled) - default:active
Response:
{
"futures": [
{
"id": "uuid",
"externalId": "odds_api_future_id",
"sportId": "uuid",
"sport": {
"key": "basketball_nba",
"name": "NBA Basketball"
},
"title": "NBA Championship Winner 2025-26",
"description": "Which team will win the 2026 NBA Championship?",
"status": "active",
"commenceTime": "2025-10-22T00:00:00Z",
"closeTime": "2026-06-01T00:00:00Z",
"settleTime": null,
"currentOdds": [
{
"id": "uuid",
"outcome": "Boston Celtics",
"bookmaker": "draftkings",
"price": 400,
"lastUpdated": "2026-01-13T10:00:00Z"
},
{
"outcome": "Los Angeles Lakers",
"bookmaker": "draftkings",
"price": 650,
"lastUpdated": "2026-01-13T10:00:00Z"
}
],
"groupedOutcomes": [
{
"outcome": "Boston Celtics",
"bookmakers": [
{ "bookmaker": "draftkings", "price": 400 },
{ "bookmaker": "fanduel", "price": 380 },
{ "bookmaker": "betmgm", "price": 425 }
],
"bestOdds": 425
}
],
"createdAt": "2025-10-01T00:00:00Z",
"updatedAt": "2026-01-13T10:00:00Z"
}
]
}Note: groupedOutcomes consolidates all bookmaker odds for each outcome, making it easier to compare odds across sportsbooks.
Get a specific future with all odds and historical snapshots.
Response:
{
"id": "uuid",
"externalId": "odds_api_future_id",
"sportId": "uuid",
"sport": {
"key": "basketball_nba",
"name": "NBA Basketball"
},
"title": "NBA Championship Winner 2025-26",
"description": "Which team will win the 2026 NBA Championship?",
"status": "active",
"commenceTime": "2025-10-22T00:00:00Z",
"closeTime": "2026-06-01T00:00:00Z",
"currentOdds": [ ... ],
"groupedOutcomes": [
{
"outcome": "Boston Celtics",
"bookmakers": [
{
"bookmaker": "draftkings",
"price": 400,
"lastUpdated": "2026-01-13T10:00:00Z"
}
],
"bestOdds": 425,
"averageOdds": 402
}
],
"oddsSnapshots": [
{
"id": "uuid",
"outcome": "Boston Celtics",
"bookmaker": "draftkings",
"price": 380,
"capturedAt": "2026-01-12T10:00:00Z"
},
{
"outcome": "Boston Celtics",
"bookmaker": "draftkings",
"price": 400,
"capturedAt": "2026-01-13T10:00:00Z"
}
],
"outcomes": [
{
"id": "uuid",
"outcome": "Boston Celtics",
"status": "pending",
"settledAt": null
}
]
}Line Movement Data: The oddsSnapshots array contains the last 100 historical snapshots for tracking odds movement over time. Use this data to:
- Display line movement charts
- Identify best odds entry points
- Track market sentiment shifts
Simplified bet creation endpoint for AI/MCP integration using game IDs.
Authentication: Requires API key via apiKeyAuth middleware.
Create a bet from AI-extracted data.
Request Body:
{
"selections": [
{
"gameId": "uuid",
"type": "moneyline",
"selection": "home",
"odds": -150,
"teamName": "Los Angeles Lakers"
},
{
"gameId": "uuid2",
"type": "spread",
"selection": "away",
"odds": -110,
"line": 5.5,
"teamName": "Boston Celtics"
}
],
"betType": "parlay",
"stake": 100.00,
"notes": "AI recommendation from Claude",
"source": "mcp"
}Field Definitions:
-
selections(array, required): Array of bet selections-
gameId(string, required): UUID of the game from database -
type(string, required):moneyline,spread, ortotal -
selection(string, required):home,away,over, orunder -
odds(number, required): American odds (e.g., -150, +200) -
line(number, required for spread/total): The line/point value -
teamName(string, optional): Display name for the selection
-
-
betType(string, required):single,parlay, orteaser -
stake(number, required): Amount to wager -
teaserPoints(number, optional): Points for teaser (6, 6.5, or 7) -
notes(string, optional): Additional notes about the bet -
source(string, optional): Source of bet (mcp,image,text,conversation) - default:mcp
Response:
{
"success": true,
"data": {
"bet": {
"id": "uuid",
"name": "2-Leg Parlay",
"betType": "parlay",
"stake": 100.00,
"status": "pending",
"oddsAtPlacement": 264,
"potentialPayout": 364.00,
"notes": "AI recommendation from Claude",
"source": "mcp",
"createdAt": "2026-01-13T10:30:00Z",
"legs": [
{
"id": "uuid1",
"gameId": "uuid",
"selectionType": "moneyline",
"selection": "home",
"teamName": "Los Angeles Lakers",
"odds": -150,
"status": "pending"
},
{
"id": "uuid2",
"gameId": "uuid2",
"selectionType": "spread",
"selection": "away",
"teamName": "Boston Celtics",
"line": 5.5,
"odds": -110,
"status": "pending"
}
]
},
"isSameGameParlay": false,
"parlayCalculation": {
"decimalOdds": [1.67, 1.91],
"combinedDecimal": 3.19,
"americanOdds": 264,
"potentialPayout": 364.00
}
}
}Same Game Parlay (SGP) Detection: The endpoint automatically detects when multiple selections are from the same game:
- Groups selections by
gameId - For SGP games, multiplies all leg odds together
- Sets
isSameGameParlay: truein response - Provides detailed parlay calculation breakdown
Validation:
- All
gameIdvalues must exist in database (404 if not found) -
typemust be valid:moneyline,spread,total -
selectionmust be valid:home,away,over,under -
oddsis required for all selections -
lineis required forspreadandtotalbets -
stakemust be positive number -
betTypemust besingle,parlay, orteaser
Error Responses:
400 - Missing or invalid fields:
{
"success": false,
"error": "Selection 1 missing required odds value"
}400 - Game IDs not found:
{
"success": false,
"error": "Some game IDs not found",
"details": {
"requestedGames": 2,
"foundGames": 1,
"missingGameIds": ["uuid2"]
}
}Retrieve live game statistics, player performance, and historical team data from API-Sports integration.
Get comprehensive game statistics including live scores, team stats, player stats, and season averages.
Path Parameters:
-
gameId(string): The game's unique identifier
Response:
{
"teamStats": [
{
"id": "uuid",
"gameId": "uuid",
"teamId": "uuid",
"isHome": true,
"homeScore": 105,
"awayScore": 98,
"quarterScores": [28, 25, 27, 25],
"stats": {
"field_goals": 42,
"field_goal_percentage": 48.3,
"three_pointers": 12,
"rebounds": 45,
"assists": 24,
"steals": 8,
"blocks": 5,
"turnovers": 12
},
"team": {
"id": "uuid",
"name": "Los Angeles Lakers",
"abbreviation": "LAL"
}
}
],
"playerStats": [
{
"id": "uuid",
"gameId": "uuid",
"playerId": "uuid",
"stats": {
"minutes": "35:24",
"points": 28,
"rebounds": 8,
"assists": 6,
"field_goals_made": 10,
"field_goals_attempts": 18,
"three_pointers_made": 4,
"free_throws_made": 4
},
"player": {
"id": "uuid",
"name": "LeBron James",
"externalId": "237"
}
}
],
"seasonAverages": [
{
"teamId": "uuid",
"totalGames": 45,
"homeGames": 23,
"awayGames": 22,
"avgStats": {
"points": 112.5,
"field_goal_percentage": 46.8,
"three_pointers": 13.2,
"rebounds": 44.3,
"assists": 26.1,
"steals": 7.8,
"blocks": 5.2
}
}
]
}Features:
- Live Updates: Real-time scores and stats during games
- Quarter Breakdown: Period-by-period scoring (NBA: 4 quarters, NFL: 4 quarters, NHL: 3 periods, Soccer: final score)
- Season Averages: Calculated averages across all games this season for both teams
- Home/Away Splits: Separate statistics for home and away performances
Get team season statistics with home/away filtering and historical game log.
Path Parameters:
-
teamId(string): The team's unique identifier
Query Parameters:
-
season(number, optional): Specific season year (defaults to current season) -
location(string, optional): Filter by location -home,away, orall(default:all)
Response:
{
"seasonStats": {
"teamId": "uuid",
"season": 2026,
"gamesPlayed": 45,
"wins": 32,
"losses": 13,
"avgPoints": 112.5,
"avgOpponentPoints": 106.8
},
"gameHistory": [
{
"id": "uuid",
"gameId": "uuid",
"teamId": "uuid",
"isHome": true,
"homeScore": 105,
"awayScore": 98,
"stats": { "..." },
"game": {
"id": "uuid",
"commenceTime": "2026-01-28T19:00:00Z",
"homeTeam": { "name": "Lakers" },
"awayTeam": { "name": "Celtics" }
}
}
],
"splits": {
"home": {
"gamesPlayed": 23,
"avgPoints": 115.2,
"avgRebounds": 46.1,
"avgAssists": 27.3,
"field_goal_percentage": 48.5
},
"away": {
"gamesPlayed": 22,
"avgPoints": 109.7,
"avgRebounds": 42.5,
"avgAssists": 24.9,
"field_goal_percentage": 45.1
},
"overall": {
"gamesPlayed": 45,
"avgPoints": 112.5,
"avgRebounds": 44.3,
"avgAssists": 26.1,
"field_goal_percentage": 46.8
}
}
}Features:
- Location Filtering: View stats for home games only, away games only, or all games
- Split Statistics: Compare home vs away performance automatically
- Historical Averages: Season-long averages for all numeric stats
- Game Log: Up to 20 most recent games with full stats
Get individual player statistics and game log.
Path Parameters:
-
playerId(string): The player's unique identifier
Query Parameters:
-
season(number, optional): Specific season year (defaults to current season) -
limit(number, optional): Number of recent games to return (default: 10, max: 50)
Response:
{
"player": {
"id": "uuid",
"externalId": "237",
"name": "LeBron James",
"sport": "basketball_nba",
"team": {
"id": "uuid",
"name": "Los Angeles Lakers",
"abbreviation": "LAL"
}
},
"seasonStats": {
"gamesPlayed": 42,
"avgPoints": 27.8,
"avgRebounds": 7.2,
"avgAssists": 8.5,
"field_goal_percentage": 52.3,
"three_point_percentage": 38.9,
"free_throw_percentage": 73.1
},
"gameLog": [
{
"id": "uuid",
"gameId": "uuid",
"stats": {
"minutes": "35:24",
"points": 28,
"rebounds": 8,
"assists": 6
},
"game": {
"id": "uuid",
"commenceTime": "2026-01-28T19:00:00Z",
"homeTeam": { "name": "Lakers" },
"awayTeam": { "name": "Celtics" }
}
}
]
}Supported Sports:
- Basketball (NBA, NCAAB): Points, rebounds, assists, shooting percentages, steals, blocks, turnovers
- Football (NFL, NCAAF): Passing yards/TDs, rushing yards/TDs, receptions, receiving yards, tackles, sacks
- Hockey (NHL): Goals, assists, points, shots on goal, plus/minus, penalty minutes
- Soccer (EPL, MLS, etc.): Goals, assists, shots, passes, tackles, interceptions, cards, goalkeeper saves
Notes:
- Stats sync runs every 15 seconds during game hours (10 AM - 2 AM ET)
- Requires
API_SPORTS_KEYenvironment variable to be configured - Free tier API-Sports accounts limited to 10 requests/day
- Rate limiting: 5 requests/second for Pro tier
Closing Line Value (CLV) analytics endpoints. CLV measures the difference between odds at bet placement vs odds at game start — the #1 indicator of long-term betting profitability.
All endpoints require session authentication (or bypass in standalone mode with AUTH_MODE=none).
Get user's overall CLV summary statistics.
Response:
{
"success": true,
"data": {
"totalBets": 142,
"averageCLV": 1.85,
"positiveCLVCount": 78,
"negativeCLVCount": 52,
"neutralCLVCount": 12,
"clvWinRate": 58.3
}
}Get CLV breakdown by sport.
Response:
{
"success": true,
"data": [
{ "sportKey": "americanfootball_nfl", "averageCLV": 2.3, "count": 65 },
{ "sportKey": "basketball_nba", "averageCLV": 1.1, "count": 44 }
]
}Get CLV breakdown by bookmaker (extracted from bet names).
Response:
{
"success": true,
"data": [
{ "bookmaker": "DraftKings", "averageCLV": 2.1, "count": 50 },
{ "bookmaker": "FanDuel", "averageCLV": 1.5, "count": 38 }
]
}Get CLV trends over time with optional filtering.
Query Parameters:
| Parameter | Type | Description |
|---|---|---|
sportKey |
string | Filter by sport (e.g., basketball_nba) |
betType |
string | Filter by bet type (single, parlay, teaser) |
startDate |
ISO date | Start of date range |
endDate |
ISO date | End of date range |
Response:
{
"success": true,
"data": {
"summary": { "totalBets": 42, "averageCLV": 1.5, "..." : "..." },
"topBets": [{ "betId": "uuid", "betName": "NFL ML", "clv": 12.5, "createdAt": "..." }],
"worstBets": [{ "betId": "uuid", "betName": "NBA Spread", "clv": -8.2, "createdAt": "..." }],
"bySport": [{ "sportKey": "...", "averageCLV": 2.1, "count": 20 }],
"byBookmaker": [{ "bookmaker": "...", "averageCLV": 1.8, "count": 15 }]
}
}Get full CLV report with all breakdowns. Accepts same query parameters as /trends.
Response: Same structure as /trends but as a flat report object.
Calculate CLV for a specific bet's legs.
Parameters:
-
betId(path): UUID of the bet
Response:
{
"success": true,
"message": "CLV calculated successfully"
}Update aggregated CLV stats for the current user. Recalculates UserCLVStats records.
Response:
{
"success": true,
"message": "CLV stats updated"
}Notes:
- CLV is captured automatically via a cron job running every 5 minutes
- Closing lines are captured ~5 minutes before game start
- Requires
ENABLE_CLOSING_LINE_CAPTURE=trueenvironment variable - CLV formula:
((Closing Implied Prob - Opening Implied Prob) / Opening Implied Prob) × 100 - Categories: positive (>1%), negative (<-1%), neutral (between)
Sharp vs Public Money indicator endpoints. These detect which side professional ("sharp") bettors are backing vs recreational ("public") money by analysing line movement data. Indicators are recalculated every 15 minutes.
All endpoints require session authentication (or bypass in standalone mode with AUTH_MODE=none).
Current sharp-money indicators for upcoming scheduled games.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
limit |
number | 20 | Max indicators returned (1-100) |
Response:
{
"success": true,
"data": {
"indicators": [
{
"id": "uuid",
"gameId": "uuid",
"marketType": "h2h",
"calculatedAt": "2026-05-13T17:00:00Z",
"lineMovement": "steam",
"sharpSide": "away",
"publicSide": "home",
"sharpConfidence": 8,
"contraindicators": [],
"publicBettingPct": null,
"publicMoneyPct": null,
"game": {
"id": "uuid",
"homeTeamName": "Los Angeles Lakers",
"awayTeamName": "Boston Celtics",
"commenceTime": "2026-05-13T20:00:00Z",
"sport": { "key": "basketball_nba", "name": "NBA" }
}
}
],
"count": 12
}
}Sharp Confidence Scale:
- 8-10: High confidence — multiple steam moves, 5+ books, recent signal
- 6-7: Medium confidence — single steam move or reverse line
- 1-5: Low confidence — gradual movement only, stale data, or mixed signals
All sharp indicators for a specific game (latest per market type).
Path Parameters:
-
gameId(UUID): Game ID
Response:
{
"success": true,
"data": {
"game": {
"id": "uuid",
"matchup": "Boston Celtics vs Los Angeles Lakers",
"sport": "NBA",
"sportKey": "basketball_nba",
"commenceTime": "2026-05-13T20:00:00Z",
"status": "scheduled"
},
"indicators": [
{
"id": "uuid",
"marketType": "h2h",
"lineMovement": "steam",
"sharpSide": "away",
"publicSide": "home",
"sharpConfidence": 8,
"contraindicators": []
},
{
"id": "uuid",
"marketType": "spreads",
"lineMovement": "reverse",
"sharpSide": "away",
"publicSide": "home",
"sharpConfidence": 6,
"contraindicators": ["single_market_only"]
}
]
}
}Games where sharp money opposes the likely public side (fade-the-public picks). Returns upcoming scheduled games with sharpConfidence ≥ minConfidence.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
minConfidence |
number | 6 | Minimum sharp confidence score (1-10) |
limit |
number | 20 | Max games returned (1-100) |
Response:
{
"success": true,
"data": {
"opportunities": [
{
"gameId": "uuid",
"homeTeamName": "Los Angeles Lakers",
"awayTeamName": "Boston Celtics",
"commenceTime": "2026-05-13T20:00:00Z",
"sportKey": "basketball_nba",
"maxConfidence": 8,
"indicators": [
{
"gameId": "uuid",
"marketType": "h2h",
"lineMovement": "steam",
"sharpSide": "away",
"publicSide": "home",
"sharpConfidence": 8,
"contraindicators": []
}
]
}
],
"count": 5,
"minConfidence": 6
}
}Summary statistics for sharp indicators over a time window.
Query Parameters:
| Parameter | Type | Default | Description |
|---|---|---|---|
hoursBack |
number | 24 | Look-back window in hours (1-168) |
Response:
{
"success": true,
"data": {
"byMarket": { "h2h": 34, "spreads": 28, "totals": 21 },
"bySharpSide": { "home": 45, "away": 38 },
"byLineMovement": { "steam": 42, "reverse": 25, "gradual": 16, "no_movement": 0 },
"avgConfidence": 6.8,
"totalIndicators": 83,
"period": "Last 24 hours"
}
}Line Movement Categories:
| Value | Description |
|---|---|
steam |
3+ books moved quickly in same direction — classic sharp action |
reverse |
Line moved against the public betting side |
gradual |
Slow drift over several hours — moderate signal |
no_movement |
No detectable line movement |
Contraindicator Values:
| Value | Description |
|---|---|
no_signal |
No movement or sharp side detected |
low_bookmaker_count |
Fewer than 3 bookmakers moved — weak signal |
mixed_signals |
Steam moves pointing in different directions |
gradual_movement_only |
Only gradual movement, no steam or reverse |
stale_data |
No line movements in the last 4 hours |
Administrative endpoints for system management and data initialization.
Initialize sports data in database.
Response:
{
"status": "success",
"message": "Initialized 7 sports",
"data": [
{
"id": "uuid",
"key": "americanfootball_nfl",
"name": "NFL",
"groupName": "American Football",
"isActive": true
}
]
}Sports Initialized:
- NFL (americanfootball_nfl)
- NBA (basketball_nba)
- NCAAB (basketball_ncaab)
- NHL (icehockey_nhl)
- MLB (baseball_mlb)
- EPL (soccer_epl) - inactive by default
- UEFA Champions League (soccer_uefa_champs_league) - inactive by default
Manually trigger odds synchronization from The Odds API.
Request Body:
{
"sportKey": "basketball_nba" // optional, omit to sync all active sports
}Response:
{
"status": "success",
"message": "Odds sync started in background",
"data": {
"sportKey": "basketball_nba"
}
}Note: Sync runs asynchronously. Check logs for progress and completion.
Manually trigger bet outcome resolution.
Checks completed games and automatically settles bets based on results.
Response:
{
"status": "success",
"message": "Outcome resolution started in background"
}Get database statistics and overview.
Response:
{
"status": "success",
"data": {
"database": {
"sports": 7,
"teams": 124,
"games": 458,
"currentOdds": 6847,
"oddsSnapshots": 45283,
"bets": 142,
"betLegs": 267
},
"activeSports": [
{ "key": "americanfootball_nfl", "name": "NFL" },
{ "key": "basketball_nba", "name": "NBA" }
],
"recentGames": 127
}
}Detailed health check with database connectivity test.
Response:
{
"status": "success",
"data": {
"database": "connected",
"dataInitialized": true,
"hasGames": true,
"timestamp": "2026-01-09T15:30:00Z"
}Get deployment-wide MCP risk thresholds. Creates the singleton settings row with defaults if it does not yet exist.
Response:
{
"status": "success",
"data": {
"id": "singleton",
"riskHighThreshold": 1000,
"riskModerateThreshold": 500,
"winRateLow": 45,
"winRateHigh": 55,
"updatedAt": "2026-01-09T15:30:00Z"
}
}Update one or more deployment-wide MCP risk thresholds. All fields are optional; omitted fields retain their current values. These thresholds control the risk analysis returned by GET /api/mcp/bets/advice-context.
Request Body (all fields optional):
{
"riskHighThreshold": 2000,
"riskModerateThreshold": 750,
"winRateLow": 40,
"winRateHigh": 60
}Field constraints: All values must be positive numbers. winRateLow and winRateHigh must be between 0 and 100.
Response: Same shape as GET /api/admin/settings.
Get current odds for a specific game.
Query Parameters:
-
bookmaker(string): Filter by bookmaker key (e.g.,draftkings) -
marketType(string): Filter by market type (h2h,spreads,totals)
Response:
{
#### `GET /api/games/:id/odds/history`
Get historical odds snapshots for line movement tracking.
**Query Parameters:**
- `bookmaker` (string): Filter by bookmaker key
- `marketType` (string): Filter by market type
- `hours` (number): Number of hours back to retrieve (default: 24)
#### `GET /api/games/:id`
Get single game with all odds.
#### `GET /api/games/:id/odds-history`
Get historical odds snapshots for line movement tracking.
**Response:**
```json
{
"status": "success",
"data": {
"snapshots": [
{
"timestamp": "2026-01-08T10:00:00Z",
"bookmaker": "draftkings",
"marketType": "spread",
"selection": "home",
"price": -110,
"point": -5.5
}
]
}
}Authentication Required - All routes require Bearer token.
Get all pending bets for MCP integration.
Response:
{
"status": "success",
"data": {
"activeBets": [
{
"id": "uuid",
"name": "Lakers ML",
"betType": "single",
"stake": 100.00,
"potentialPayout": 166.67,
"legs": [...]
}
],
"count": 12,
"totalExposure": 1200.00
}
}Get quick betting summary for MCP context.
Response:
{
"status": "success",
"data": {
"pending": {
"count": 12,
"totalStake": 1200.00,
"potentialWin": 1650.00
},
"recentResults": {
"last7Days": {
"won": 8,
"lost": 5,
"netProfit": 345.50
}
},
"favorites": {
"bestSport": "basketball_nba",
"bestBetType": "single"
}
}
}Get full context for AI betting advice.
Response: Extended summary with:
- All active bets
- Recent performance by sport
- Betting patterns and tendencies
- Risk exposure by game/sport
Get games with user's betting positions.
Query Parameters:
-
sport(string): Filter by sport -
onlyWithBets(boolean): Show only games with active bets
Response:
{
"status": "success",
"data": {
"games": [
{
"game": {
"id": "uuid",
"awayTeamName": "Boston Celtics",
"homeTeamName": "Los Angeles Lakers",
"commenceTime": "2026-01-09T19:00:00Z"
},
"userBets": [
{
"betId": "uuid",
"name": "Lakers ML",
"stake": 100.00,
"selection": "home",
"selectionType": "moneyline"
}
],
"totalExposure": 100.00,
"potentialWin": 166.67
}
]
}
}Simplified bet creation for MCP.
Request Body:
{
"game_id": "uuid",
"selection_type": "moneyline",
"selection": "home",
"stake": 50.00,
"odds": -150,
"name": "Lakers ML"
}Validation: odds must satisfy Math.abs(odds) >= 100 (valid American odds format). selection_type must be moneyline, spread, or total. selection must be home, away, over, or under.
List upcoming games (general-purpose replacement for /api/games for MCP callers).
Permission required: bets
Query Parameters:
-
sport(string, optional): Filter by sport key (e.g.basketball_nba) -
status(string, optional): Filter by status (e.g.scheduled,in_progress)
Response:
{
"status": "success",
"data": {
"games": [
{
"id": "uuid",
"matchup": "Boston Celtics @ Los Angeles Lakers",
"homeTeam": "Los Angeles Lakers",
"awayTeam": "Boston Celtics",
"sportKey": "basketball_nba",
"sport": "basketball_nba",
"commenceTime": "2026-01-09T19:00:00Z",
"status": "scheduled",
"homeScore": null,
"awayScore": null
}
],
"count": 12
}
}Get current bookmaker odds for a specific game.
Response:
{
"status": "success",
"data": {
"game": { "id": "uuid", "matchup": "...", "commenceTime": "..." },
"odds": [
{
"bookmaker": "draftkings",
"marketType": "h2h",
"homePrice": -150,
"awayPrice": 130,
"homeSpread": null,
"awaySpread": null,
"total": null,
"overPrice": null,
"underPrice": null
}
]
}
}List bets with optional filters. Scoped to the API key owner.
Permission required: bets
Query Parameters:
-
status(string, optional):pending,won,lost, orpush -
limit(number, optional): Max results (default 100)
Response:
{
"status": "success",
"data": {
"bets": [ { "id": "uuid", "name": "Lakers ML", "status": "pending", ... } ],
"total": 42,
"limit": 100,
"offset": 0
}
}Get a single bet by UUID. Returns 404 if the bet does not belong to the API key owner.
Permission required: bets
Response:
{
"status": "success",
"data": {
"bet": {
"id": "uuid",
"name": "Lakers ML",
"betType": "single",
"stake": 50.00,
"odds": -150,
"status": "pending",
"legs": [ { ... } ]
}
}
}Search teams by name using case-insensitive partial match (ILIKE).
Query Parameters:
-
q(string, required): Search term — minimum 1 character
Response:
{
"status": "success",
"data": {
"teams": [
{ "id": "uuid", "name": "Los Angeles Lakers", "abbreviation": "LAL", "sport": "basketball_nba", "logoUrl": "https://..." }
],
"count": 1
}
}Health check endpoint.
Response:
{
"status": "healthy",
"timestamp": "2026-01-08T15:30:00Z",
"database": "connected",
"uptime": 3600
}React + TypeScript + Vite + Redux Toolkit + Tailwind CSS
src/
├── components/ # Reusable UI components
│ ├── bets/ # Bet-related components
│ ├── odds/ # Odds display components
│ ├── stats/ # Statistics components
│ └── common/ # Shared components
├── pages/ # Route pages
├── store/ # Redux store
├── services/ # API clients
├── hooks/ # Custom React hooks
├── types/ # TypeScript types
└── utils/ # Utility functions
import { configureStore } from '@reduxjs/toolkit';
import betSlipReducer from './betSlipSlice';
export const store = configureStore({
reducer: {
betSlip: betSlipReducer,
},
});
export type RootState = ReturnType<typeof store.getState>;
export type AppDispatch = typeof store.dispatch;State:
interface BetSlipState {
legs: BetLeg[];
betType: 'single' | 'parlay' | 'teaser';
stake: number;
teaserPoints: number;
}Actions:
-
addLeg(leg)- Add/update bet leg (auto-dedupes by gameId + selectionType) -
removeLeg(index)- Remove bet leg -
updateLeg(index, updates)- Update specific leg -
setBetType(type)- Set bet type -
setStake(amount)- Set stake amount -
setTeaserPoints(points)- Set teaser points (6, 6.5, 7) -
clearBetSlip()- Clear all legs -
updateOdds(gameId, odds)- Update odds for specific game
Auto-behaviors:
- Adding 2+ legs auto-switches to parlay
- Removing to 1 leg auto-switches to single
- Clearing legs resets stake to 0
import axios from 'axios';
const apiClient = axios.create({
baseURL: 'http://localhost:3001/api',
headers: { 'Content-Type': 'application/json' },
});
// Auto-adds Bearer token from localStorage
apiClient.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
});
// Auto-handles 401 Unauthorized
apiClient.interceptors.response.use(
(response) => response,
(error) => {
if (error.response?.status === 401) {
localStorage.removeItem('token');
// Redirect to login
}
return Promise.reject(error);
}
);Interactive bet slip widget.
Features:
- Add/remove legs dynamically
- Adjust stake with validation
- Calculate potential payout (parlay calculations)
- Teaser control for NFL/NBA spreads/totals
- Submit bet to API
Props: None (uses Redux state)
Display single bet with all details.
Props:
interface BetCardProps {
bet: Bet;
onSettle?: (betId: string, status: 'won' | 'lost' | 'push') => void;
onDelete?: (betId: string) => void;
onEdit?: (betId: string) => void;
}Display game with odds from multiple bookmakers.
Props:
interface GameCardProps {
game: Game;
onAddToBetSlip: (leg: BetLeg) => void;
showOdds?: boolean;
collapsed?: boolean;
}Features:
- Expandable odds table
- Click odds to add to bet slip
- Line movement indicators
- Team logos and colors
Grid of games with odds comparison.
Props:
interface OddsGridProps {
sport?: string;
date?: Date;
bookmakers?: string[];
}Features:
- Filter by sport, date
- Sort by commence time
- Toggle between moneyline/spread/total views
- Refresh odds button
Statistics and performance charts.
Features:
- Win rate charts (by sport, by bet type)
- P&L timeline graph
- ROI by bookmaker
- Bet distribution pie charts
- Filters: date range, sport, bet type
export const useBetSlip = () => {
const dispatch = useDispatch();
const betSlip = useSelector((state: RootState) => state.betSlip);
const addLeg = (leg: BetLeg) => dispatch(addLegAction(leg));
const removeLeg = (index: number) => dispatch(removeLegAction(index));
const calculatePayout = () => { /* Calculate based on betType */ };
return { betSlip, addLeg, removeLeg, calculatePayout };
};export const useOddsPolling = (gameId: string, interval: number = 30000) => {
const [odds, setOdds] = useState<Odds[]>([]);
useEffect(() => {
const fetchOdds = async () => {
const response = await apiClient.get(`/games/${gameId}`);
setOdds(response.data.data.currentOdds);
};
fetchOdds();
const timer = setInterval(fetchOdds, interval);
return () => clearInterval(timer);
}, [gameId, interval]);
return odds;
};List of all bets with filters and search.
Features:
- Tabs: Active, Won, Lost, All
- Search by name
- Filter by sport, date range
- Sort by date, stake, potential win
- Pagination
Performance analytics and insights.
Features:
- Overview cards (total bets, win rate, ROI, net profit)
- Charts: P&L over time, win rate by sport, bet distribution
- Best/worst performing sports/bet types
- Recent activity feed
// types/game.types.ts
export interface Game {
id: string;
externalId: string;
sportId: string;
awayTeamName: string;
homeTeamName: string;
commenceTime: Date;
status: 'scheduled' | 'in_progress' | 'completed';
completed: boolean;
sport: Sport;
currentOdds: Odds[];
}
export interface Sport {
id: string;
key: string;
name: string;
active: boolean;
}
export interface Odds {
id: string;
gameId: string;
bookmaker: string;
marketType: 'h2h' | 'spreads' | 'totals';
selection: 'home' | 'away' | 'over' | 'under';
price: number;
point?: number;
lastUpdated: Date;
}
// types/bet.types.ts
export interface Bet {
id: string;
name: string;
betType: 'single' | 'parlay' | 'teaser';
stake: number;
status: 'pending' | 'won' | 'lost' | 'push' | 'cancelled';
oddsAtPlacement: number;
potentialPayout: number;
actualPayout?: number;
teaserPoints?: number;
notes?: string;
createdAt: Date;
settledAt?: Date;
legs: BetLeg[];
}
export interface BetLeg {
id: string;
betId: string;
gameId: string;
selectionType: 'moneyline' | 'spread' | 'total';
selection: 'home' | 'away' | 'over' | 'under';
teamName?: string;
line?: number;
odds: number;
userAdjustedLine?: number;
userAdjustedOdds?: number;
status: 'pending' | 'won' | 'lost' | 'push' | 'cancelled';
result?: number;
game: Game;
}# .env
VITE_API_URL=http://localhost:3001/api
VITE_ENABLE_MOCK_API=false
VITE_ODDS_REFRESH_INTERVAL=30000cd mcp
python sports_mcp_server.pycd dashboard/backend
npm run dev # http://localhost:3001cd dashboard/frontend
npm run dev # http://localhost:5173# Backend
cd dashboard/backend
npm test
# MCP (when implemented)
cd mcp
pytest tests/ -v