openapi: 3.1.0
info:
  title: BALLDONTLIE Lab API
  version: "1.0.0"
  description: |
    Build, backtest, and deploy betting models programmatically. The Lab API gives you access to pre-built analytical factors, model creation, historical backtesting, and prediction generation across multiple sports.

    ## Quick Start

    1. **Create an account** at [lab-app.balldontlie.io](https://lab-app.balldontlie.io)
    2. **Upgrade to PRO or ALL-ACCESS** (free tiers cannot access the API)
    3. **Grab your API key** from your account settings

    ## Authentication

    All endpoints require authentication via API key in the `Authorization` header:

    ```bash
    curl "https://api.balldontlie.io/lab/v1/factors" \
      -H "Authorization: YOUR_API_KEY"
    ```

    ## Rate Limits

    | Limit | Value |
    |-------|-------|
    | Requests per minute | 100 |

    Rate limit headers are included in every response:

    | Header | Description |
    |--------|-------------|
    | `X-RateLimit-Limit` | Maximum requests per minute |
    | `X-RateLimit-Remaining` | Requests remaining in current window |
    | `X-RateLimit-Reset` | Unix timestamp when the limit resets |

    ## Supported Sports

    | Sport | Code | Status |
    |-------|------|--------|
    | NBA | `nba` | Available |
    | NFL | `nfl` | Available |
    | NHL | `nhl` | Available |
    | MLB | `mlb` | Available |

    ## Subscription Tiers

    | Tier | Backtest History | Models | API Access |
    |------|------------------|--------|------------|
    | ALL-ACCESS | 6 years | Unlimited | Full |
    | LAB PRO | 6 years | Unlimited | Full |

    Subscribe at [lab-app.balldontlie.io](https://lab-app.balldontlie.io) to get started.

  contact:
    name: BALLDONTLIE Support
    email: support@balldontlie.io
    url: https://discord.gg/cQJhfTPn8j
  termsOfService: https://www.balldontlie.io/terms

servers:
  - url: https://api.balldontlie.io
    description: Production server

components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: Authorization
      description: API key (can be prefixed with "Bearer " or sent raw)

  schemas:
    # Common Components
    Error:
      type: object
      properties:
        error:
          type: string
          description: Error message

    CursorPagination:
      type: object
      properties:
        next_cursor:
          type:
            - integer
            - "null"
          description: Cursor for the next page of results
        prev_cursor:
          type:
            - integer
            - "null"
          description: Cursor for the previous page of results
        per_page:
          type: integer
          description: Number of results per page

    OffsetPagination:
      type: object
      properties:
        total:
          type: integer
          description: Total number of results
        limit:
          type: integer
          description: Number of results per page
        offset:
          type: integer
          description: Number of results skipped

    # Lab Enums
    Sport:
      type: string
      enum: [nba, nhl, nfl, mlb]
      description: Supported sports

    BetType:
      type: string
      enum: [spread, moneyline, over_under, player_prop]
      description: Type of bet the model predicts

    ModelMode:
      type: string
      enum: [simple, weighted]
      description: |
        Model calculation mode:
        - simple: Assign importance (Low/Medium/High) to each factor
        - weighted: Assign precise weights that must sum to 100%

    Importance:
      type: string
      enum: [low, medium, high]
      description: Factor importance level (used in simple mode)

    PerformanceStatus:
      type: string
      enum: [pending, running, completed, failed]
      description: Status of a performance evaluation

    JobStatus:
      type: string
      enum: [pending, running, completed, failed]
      description: Status of a background job

    JobType:
      type: string
      enum: [preview, evaluate, generate_predictions]
      description: Type of background job

    PredictionResult:
      type:
        - string
        - "null"
      enum: [win, loss, push, null]
      description: Result of a prediction (null if pending)

    PredictedSide:
      type: string
      enum: [home, away, over, under]
      description: Side the model predicted

    GameResult:
      type: string
      enum: [win, loss, push, pending]
      description: Result of a game prediction

    # Lab Factor Schemas
    FactorConfigParamType:
      type: string
      enum: [integer, boolean, string]

    FactorConfigParam:
      type: object
      properties:
        type:
          $ref: "#/components/schemas/FactorConfigParamType"
        default:
          oneOf:
            - type: integer
            - type: boolean
            - type: string
          description: Default value for the parameter
        min:
          type: number
          description: Minimum value (for integer type)
        max:
          type: number
          description: Maximum value (for integer type)
        options:
          type: array
          items:
            type: string
          description: Available options (for string type)
        description:
          type: string
          description: Description of what the parameter controls

    Factor:
      type: object
      properties:
        id:
          type: integer
          description: Unique factor identifier
        slug:
          type: string
          description: URL-friendly factor identifier
        name:
          type: string
          description: Human-readable factor name
        description:
          type:
            - string
            - "null"
          description: Detailed description of the factor
        category:
          type: string
          description: Factor category (team_performance, matchup, situational, player, market)
        sport:
          type: string
          description: Sport this factor applies to
        output_type:
          type: string
          description: Type of output the factor produces
        raw_value_unit:
          type:
            - string
            - "null"
          description: Unit of measurement for raw values
        configurable_params:
          oneOf:
            - type: object
              additionalProperties:
                $ref: "#/components/schemas/FactorConfigParam"
            - type: "null"
          description: Configurable parameters for this factor

    # Lab Model Schemas
    ImportanceMultipliers:
      type: object
      description: Custom importance multipliers for simple mode
      properties:
        low:
          type: number
          description: Weight multiplier for low importance factors
        medium:
          type: number
          description: Weight multiplier for medium importance factors
        high:
          type: number
          description: Weight multiplier for high importance factors

    BettingThresholds:
      type: object
      description: Custom thresholds for determining when to place bets
      properties:
        spread_score_diff:
          type: number
          description: Score difference threshold for spread bets
        over_under_combined:
          type: number
          description: Combined score threshold for over/under bets
        moneyline_score_diff:
          type: number
          description: Score difference threshold for moneyline bets

    AdvancedConfig:
      oneOf:
        - type: object
          description: Optional advanced configuration for model calculations
          properties:
            importance_multipliers:
              $ref: "#/components/schemas/ImportanceMultipliers"
            betting_thresholds:
              $ref: "#/components/schemas/BettingThresholds"
        - type: "null"

    ModelFactorInput:
      type: object
      required:
        - factor_id
      properties:
        factor_id:
          type: integer
          description: ID of the factor to include in the model
        importance:
          $ref: "#/components/schemas/Importance"
        weight:
          type: number
          minimum: 0
          maximum: 100
          description: Weight of this factor (0-100, must sum to 100 in weighted mode)
        parameters:
          type: object
          additionalProperties: true
          description: Custom parameters for this factor
        conditions:
          type: object
          additionalProperties: true
          description: Conditions for when this factor applies

    ModelFactor:
      type: object
      properties:
        factor_id:
          type: integer
          description: ID of the factor
        factor_slug:
          type: string
          description: URL-friendly factor identifier
        factor_name:
          type: string
          description: Human-readable factor name
        importance:
          oneOf:
            - $ref: "#/components/schemas/Importance"
            - type: "null"
        weight:
          type:
            - number
            - "null"
          description: Weight of this factor (0-100)
        parameters:
          oneOf:
            - type: object
              additionalProperties: true
            - type: "null"
          description: Custom parameters for this factor
        conditions:
          oneOf:
            - type: object
              additionalProperties: true
            - type: "null"
          description: Conditions for when this factor applies

    ModelSlim:
      type: object
      properties:
        id:
          type: integer
          description: Unique model identifier
        name:
          type: string
          description: Model name
        description:
          type:
            - string
            - "null"
          description: Model description
        sport:
          type: string
          description: Sport this model predicts
        bet_type:
          $ref: "#/components/schemas/BetType"
        prop_type:
          type:
            - string
            - "null"
          description: Specific prop type (for player_prop bet_type)
        mode:
          $ref: "#/components/schemas/ModelMode"
        advanced_config:
          $ref: "#/components/schemas/AdvancedConfig"
        created_at:
          type: string
          format: date-time
          description: Timestamp when the model was created
        updated_at:
          type: string
          format: date-time
          description: Timestamp when the model was last updated

    Model:
      allOf:
        - $ref: "#/components/schemas/ModelSlim"
        - type: object
          properties:
            factors:
              type: array
              items:
                $ref: "#/components/schemas/ModelFactor"
              description: Factors included in this model

    CreateModelRequest:
      type: object
      required:
        - name
        - bet_type
        - mode
        - factors
      properties:
        name:
          type: string
          description: Model name
        description:
          type: string
          description: Model description
        sport:
          $ref: "#/components/schemas/Sport"
        bet_type:
          $ref: "#/components/schemas/BetType"
        prop_type:
          type: string
          description: Specific prop type (for player_prop bet_type)
        mode:
          $ref: "#/components/schemas/ModelMode"
        factors:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/ModelFactorInput"
          description: Factors to include in the model

    UpdateModelRequest:
      type: object
      properties:
        name:
          type: string
          description: Model name
        description:
          type: string
          description: Model description
        bet_type:
          $ref: "#/components/schemas/BetType"
        prop_type:
          type: string
          description: Specific prop type (for player_prop bet_type)
        mode:
          $ref: "#/components/schemas/ModelMode"
        factors:
          type: array
          items:
            $ref: "#/components/schemas/ModelFactorInput"
          description: Factors to include in the model

    # Lab Performance Result Schemas
    ResultsByConfidence:
      type: object
      properties:
        bucket:
          type: string
          description: Confidence bucket range (e.g., "0.5-0.6")
        games:
          type: integer
          description: Number of games in this bucket
        wins:
          type: integer
          description: Number of wins
        losses:
          type: integer
          description: Number of losses
        pushes:
          type: integer
          description: Number of pushes
        win_rate:
          type: number
          description: Win rate as decimal
        roi:
          type: number
          description: Return on investment as decimal

    ResultsOverTime:
      type: object
      properties:
        date:
          type: string
          description: Date or period
        cumulative_wins:
          type: integer
          description: Cumulative wins up to this date
        cumulative_losses:
          type: integer
          description: Cumulative losses up to this date
        cumulative_roi:
          type: number
          description: Cumulative ROI up to this date

    # Lab Prediction Schemas
    TeamInfo:
      type: object
      properties:
        id:
          type: integer
          description: Team ID
        name:
          type: string
          description: Team name
        abbreviation:
          type: string
          description: Team abbreviation

    GameInfo:
      type: object
      properties:
        id:
          type: integer
          description: Game ID
        date:
          type: string
          format: date
          description: Game date (YYYY-MM-DD)
        home_team:
          $ref: "#/components/schemas/TeamInfo"
        visitor_team:
          $ref: "#/components/schemas/TeamInfo"
        home_team_score:
          type:
            - integer
            - "null"
          description: Home team final score (null if game not completed)
        visitor_team_score:
          type:
            - integer
            - "null"
          description: Visitor team final score (null if game not completed)
        status:
          type:
            - string
            - "null"
          description: Game status (e.g., "Final", "Scheduled")

    Prediction:
      type: object
      properties:
        id:
          type: integer
          description: Unique prediction identifier
        model_id:
          type: integer
          description: ID of the model that made this prediction
        game_id:
          type: integer
          description: ID of the game being predicted
        predicted_value:
          type:
            - number
            - "null"
          description: Predicted value
        confidence:
          type:
            - number
            - "null"
          description: Confidence level (0-1)
        market_value:
          type:
            - number
            - "null"
          description: Market line value at prediction time
        edge:
          type:
            - number
            - "null"
          description: Edge over market
        actual_value:
          type:
            - number
            - "null"
          description: Actual result value
        result:
          $ref: "#/components/schemas/PredictionResult"
        created_at:
          type: string
          format: date-time
          description: Timestamp when prediction was created
        resolved_at:
          type:
            - string
            - "null"
          format: date-time
          description: Timestamp when prediction was resolved

    PredictionWithGame:
      allOf:
        - $ref: "#/components/schemas/Prediction"
        - type: object
          properties:
            game:
              $ref: "#/components/schemas/GameInfo"

    PredictionStats:
      type: object
      properties:
        total:
          type: integer
          description: Total predictions
        wins:
          type: integer
          description: Number of wins
        losses:
          type: integer
          description: Number of losses
        pushes:
          type: integer
          description: Number of pushes
        win_rate:
          type: number
          description: Win rate as percentage

    # Lab Performance Schemas
    PerformanceTeamInfo:
      type: object
      properties:
        id:
          type: integer
          description: Team ID
        abbreviation:
          type: string
          description: Team abbreviation
        name:
          type: string
          description: Team name

    FactorValue:
      type: object
      properties:
        factor_slug:
          type: string
          description: Factor identifier
        raw_value:
          oneOf:
            - type: number
            - type: boolean
            - type: string
          description: Raw factor value
        score:
          type: number
          description: Normalized score (0-100)
        parameters:
          type: object
          additionalProperties: true
          description: Parameters used for calculation

    PerformanceGameResult:
      type: object
      properties:
        game_id:
          type: integer
          description: Game ID
        date:
          type: string
          format: date
          description: Game date (YYYY-MM-DD)
        home_team:
          $ref: "#/components/schemas/PerformanceTeamInfo"
        away_team:
          $ref: "#/components/schemas/PerformanceTeamInfo"
        home_score:
          type:
            - integer
            - "null"
          description: Home team score
        away_score:
          type:
            - integer
            - "null"
          description: Away team score
        market_value:
          type: number
          description: Market line value
        home_model_score:
          type: number
          description: Model's score for home team
        away_model_score:
          type: number
          description: Model's score for away team
        predicted_side:
          $ref: "#/components/schemas/PredictedSide"
        confidence:
          type: number
          description: Confidence level
        result:
          $ref: "#/components/schemas/GameResult"
        is_future:
          type: boolean
          description: Whether this is a future game (prediction only)
        factor_details:
          type: object
          properties:
            home:
              type: array
              items:
                $ref: "#/components/schemas/FactorValue"
            away:
              type: array
              items:
                $ref: "#/components/schemas/FactorValue"
          description: Detailed factor values for each team

    ModelPerformance:
      type: object
      properties:
        model_id:
          type: integer
          description: ID of the model
        status:
          $ref: "#/components/schemas/PerformanceStatus"
        error_message:
          type:
            - string
            - "null"
          description: Error message if status is failed
        evaluated_at:
          type:
            - string
            - "null"
          format: date-time
          description: Timestamp when evaluation completed
        games_evaluated:
          type:
            - integer
            - "null"
          description: Number of games evaluated
        date_range_start:
          type:
            - string
            - "null"
          format: date
          description: Start of evaluation date range (YYYY-MM-DD)
        date_range_end:
          type:
            - string
            - "null"
          format: date
          description: End of evaluation date range (YYYY-MM-DD)
        total_bets:
          type:
            - integer
            - "null"
          description: Total number of bets
        wins:
          type:
            - integer
            - "null"
          description: Number of wins
        losses:
          type:
            - integer
            - "null"
          description: Number of losses
        pushes:
          type:
            - integer
            - "null"
          description: Number of pushes
        win_rate:
          type:
            - number
            - "null"
          description: Win rate as decimal
        roi:
          type:
            - number
            - "null"
          description: Return on investment as decimal
        avg_confidence:
          type:
            - number
            - "null"
          description: Average confidence
        results_by_confidence:
          oneOf:
            - type: array
              items:
                $ref: "#/components/schemas/ResultsByConfidence"
            - type: "null"
          description: Results by confidence bucket
        results_over_time:
          oneOf:
            - type: array
              items:
                $ref: "#/components/schemas/ResultsOverTime"
            - type: "null"
          description: Results over time

    EvaluatePerformanceRequest:
      type: object
      properties:
        start_season:
          type: string
          pattern: "^\\d{4}-\\d{2}$"
          description: Start season (format "YYYY-YY", e.g., "2024-25"). Allowed range 2020-21 to 2025-26.
        end_season:
          type: string
          pattern: "^\\d{4}-\\d{2}$"
          description: End season (format "YYYY-YY", e.g., "2024-25"). Allowed range 2020-21 to 2025-26.

    PreviewFactorInput:
      type: object
      required:
        - factor_id
      properties:
        factor_id:
          type: integer
          description: ID of the factor
        importance:
          $ref: "#/components/schemas/Importance"
        weight:
          type: number
          minimum: 0
          maximum: 100
          description: Weight (0-100)
        parameters:
          type: object
          additionalProperties: true
          description: Factor parameters

    PreviewRequest:
      type: object
      required:
        - bet_type
        - mode
        - factors
      properties:
        bet_type:
          $ref: "#/components/schemas/BetType"
        mode:
          $ref: "#/components/schemas/ModelMode"
        sport:
          $ref: "#/components/schemas/Sport"
        advanced_config:
          $ref: "#/components/schemas/AdvancedConfig"
        factors:
          type: array
          minItems: 1
          items:
            $ref: "#/components/schemas/PreviewFactorInput"
          description: Factors for the preview
        start_season:
          type: string
          pattern: "^\\d{4}-\\d{2}$"
          description: Start season (format "YYYY-YY", e.g., "2023-24")
        end_season:
          type: string
          pattern: "^\\d{4}-\\d{2}$"
          description: End season (format "YYYY-YY", e.g., "2024-25")

    PreviewPrediction:
      type: object
      properties:
        game_id:
          type: integer
          description: Game ID
        date:
          type: string
          format: date
          description: Game date (YYYY-MM-DD)
        home_team:
          $ref: "#/components/schemas/PerformanceTeamInfo"
        away_team:
          $ref: "#/components/schemas/PerformanceTeamInfo"
        market_value:
          type: number
          description: Market line value
        predicted_side:
          $ref: "#/components/schemas/PredictedSide"
        confidence:
          type: number
          description: Confidence level
        home_model_score:
          type: number
          description: Model's score for home team
        away_model_score:
          type: number
          description: Model's score for away team

    PreviewOutput:
      type: object
      properties:
        historical:
          type: object
          properties:
            total_games:
              type: integer
              description: Total games evaluated
            wins:
              type: integer
              description: Number of wins
            losses:
              type: integer
              description: Number of losses
            pushes:
              type: integer
              description: Number of pushes
            win_rate:
              type: number
              description: Win rate as decimal
            roi:
              type: number
              description: ROI as decimal
            avg_confidence:
              type: number
              description: Average confidence
            results_by_confidence:
              type: array
              items:
                $ref: "#/components/schemas/ResultsByConfidence"
        predictions:
          type: array
          items:
            $ref: "#/components/schemas/PreviewPrediction"
          description: Predictions for upcoming games

    # Lab Job Schemas
    Job:
      type: object
      properties:
        id:
          type: string
          format: uuid
          description: Unique job identifier (UUID)
        model_id:
          type:
            - integer
            - "null"
          description: ID of the associated model (null for preview jobs)
        job_type:
          $ref: "#/components/schemas/JobType"
        status:
          $ref: "#/components/schemas/JobStatus"
        output:
          description: Job output (structure depends on job_type)
          oneOf:
            - $ref: "#/components/schemas/PreviewOutput"
            - type: object
            - type: "null"
        error_message:
          type:
            - string
            - "null"
          description: Error message if status is failed
        created_at:
          type: string
          format: date-time
          description: Timestamp when job was created
        started_at:
          type:
            - string
            - "null"
          format: date-time
          description: Timestamp when job started processing
        completed_at:
          type:
            - string
            - "null"
          format: date-time
          description: Timestamp when job completed

  examples:
    # ============================================
    # Factor Examples
    # ============================================
    FactorTeamPerformance:
      summary: Team Performance Factor
      description: A factor that tracks team scoring average
      value:
        id: 1
        slug: team_last_n_pts_avg
        name: Team Last N Games Scoring Average
        description: Average points scored by team in their last N games. Higher scoring indicates stronger offense.
        category: team_performance
        sport: nba
        output_type: points
        raw_value_unit: points per game
        configurable_params:
          n_games:
            type: integer
            default: 10
            min: 3
            max: 30
            description: Number of recent games to average
          home_only:
            type: boolean
            default: false
            description: Only include home games
          away_only:
            type: boolean
            default: false
            description: Only include away games

    FactorMatchup:
      summary: Matchup Factor
      description: A factor that evaluates head-to-head history
      value:
        id: 6
        slug: head_to_head_record
        name: Head-to-Head Record
        description: Win percentage in recent matchups between the two teams. Historical dominance can persist.
        category: matchup
        sport: nba
        output_type: percentage
        raw_value_unit: win percentage
        configurable_params:
          n_games:
            type: integer
            default: 10
            min: 3
            max: 20
            description: Number of recent matchups to consider
          include_playoffs:
            type: boolean
            default: true
            description: Include playoff games in history

    FactorSituational:
      summary: Situational Factor
      description: A boolean factor for back-to-back games
      value:
        id: 11
        slug: back_to_back
        name: Back-to-Back Game
        description: Whether this is the second game of a back-to-back. B2B teams typically underperform.
        category: situational
        sport: nba
        output_type: boolean
        raw_value_unit: boolean
        configurable_params: {}

    FactorListResponse:
      summary: List of factors
      description: Response containing multiple factors filtered by category
      value:
        data:
          - id: 1
            slug: team_last_n_pts_avg
            name: Team Last N Games Scoring Average
            description: Average points scored by team in their last N games.
            category: team_performance
            sport: nba
            output_type: points
            raw_value_unit: points per game
            configurable_params:
              n_games:
                type: integer
                default: 10
                min: 3
                max: 30
                description: Number of recent games to average
          - id: 2
            slug: team_last_n_pts_allowed_avg
            name: Team Last N Games Points Allowed Average
            description: Average points allowed by team in their last N games.
            category: team_performance
            sport: nba
            output_type: points
            raw_value_unit: points per game
            configurable_params:
              n_games:
                type: integer
                default: 10
                min: 3
                max: 30
                description: Number of recent games to average

    # ============================================
    # Model Examples
    # ============================================
    ModelSimpleMode:
      summary: Simple Mode Model
      description: A model using simple importance-based factor weighting
      value:
        id: 1
        name: Momentum Tracker
        description: Tracks team momentum using recent scoring and ATS performance
        sport: nba
        bet_type: spread
        prop_type: null
        mode: simple
        advanced_config: null
        created_at: "2026-01-31T22:08:30.516Z"
        updated_at: "2026-01-31T22:08:30.516Z"
        factors:
          - factor_id: 1
            factor_slug: team_last_n_pts_avg
            factor_name: Team Last N Games Scoring Average
            importance: high
            weight: null
            parameters: null
            conditions: null
          - factor_id: 4
            factor_slug: team_ats_record
            factor_name: Team Against The Spread Record
            importance: high
            weight: null
            parameters: null
            conditions: null
          - factor_id: 10
            factor_slug: rest_days
            factor_name: Rest Days Advantage
            importance: medium
            weight: null
            parameters: null
            conditions: null

    ModelWeightedMode:
      summary: Weighted Mode Model
      description: A model using precise weight-based factor weighting
      value:
        id: 2
        name: Weighted Totals Model
        description: null
        sport: nba
        bet_type: over_under
        prop_type: null
        mode: weighted
        advanced_config: null
        created_at: "2026-01-31T22:08:37.754Z"
        updated_at: "2026-01-31T22:08:37.754Z"
        factors:
          - factor_id: 1
            factor_slug: team_last_n_pts_avg
            factor_name: Team Last N Games Scoring Average
            importance: null
            weight: 40
            parameters: null
            conditions: null
          - factor_id: 2
            factor_slug: team_last_n_pts_allowed_avg
            factor_name: Team Last N Games Points Allowed Average
            importance: null
            weight: 35
            parameters: null
            conditions: null
          - factor_id: 5
            factor_slug: team_over_under_trend
            factor_name: Team Over/Under Trend
            importance: null
            weight: 25
            parameters: null
            conditions: null

    CreateModelSimpleRequest:
      summary: Create Simple Mode Model
      description: Request to create a model with simple importance-based weighting
      value:
        name: Momentum Tracker
        description: Tracks team momentum using recent scoring and ATS performance
        bet_type: spread
        mode: simple
        factors:
          - factor_id: 1
            importance: high
          - factor_id: 4
            importance: high
          - factor_id: 10
            importance: medium

    CreateModelWeightedRequest:
      summary: Create Weighted Mode Model
      description: Request to create a model with precise weight-based weighting (must sum to 100)
      value:
        name: Weighted Totals Model
        bet_type: over_under
        mode: weighted
        factors:
          - factor_id: 1
            weight: 40
          - factor_id: 2
            weight: 35
          - factor_id: 5
            weight: 25

    ModelListResponse:
      summary: List of user's models
      description: Response containing user's models with pagination
      value:
        data:
          - id: 1
            name: Momentum Tracker
            description: Tracks team momentum
            sport: nba
            bet_type: spread
            prop_type: null
            mode: simple
            advanced_config: null
            created_at: "2026-01-31T22:08:30.516Z"
            updated_at: "2026-01-31T22:08:30.516Z"
            factors:
              - factor_id: 1
                factor_slug: team_last_n_pts_avg
                factor_name: Team Last N Games Scoring Average
                importance: high
                weight: null
                parameters: null
                conditions: null
        meta:
          per_page: 25

    WeightValidationError:
      summary: Weight validation error
      description: Error when factor weights don't sum to 100 in weighted mode
      value:
        error: Factor weights must sum to 100 for weighted mode

    # ============================================
    # Prediction Examples
    # ============================================
    PredictionListEmpty:
      summary: Empty predictions list
      description: Response when no predictions exist for a model
      value:
        data: []
        meta:
          per_page: 25

    PredictionStats:
      summary: Prediction statistics
      description: Aggregate statistics for a model's predictions
      value:
        data:
          total: 0
          wins: 0
          losses: 0
          pushes: 0
          win_rate: 0

    # ============================================
    # Job Examples
    # ============================================
    JobPending:
      summary: Pending Job
      description: A job that is waiting to be processed
      value:
        id: "81cea460-5e30-4f15-9e0d-9493541a3c7d"
        model_id: 1
        job_type: generate_predictions
        status: pending
        output: null
        error_message: null
        created_at: "2026-01-31T22:14:50.285Z"
        started_at: null
        completed_at: null

    JobRunning:
      summary: Running Job
      description: A job that is currently being processed
      value:
        id: "81cea460-5e30-4f15-9e0d-9493541a3c7d"
        model_id: 1
        job_type: generate_predictions
        status: running
        output: null
        error_message: null
        created_at: "2026-01-31T22:14:50.285Z"
        started_at: "2026-01-31T22:14:51.212Z"
        completed_at: null

    JobFailed:
      summary: Failed Job
      description: A job that failed to complete
      value:
        id: "81cea460-5e30-4f15-9e0d-9493541a3c7d"
        model_id: 1
        job_type: generate_predictions
        status: failed
        output: null
        error_message: Job timed out after 10 minutes
        created_at: "2026-01-31T22:14:50.285Z"
        started_at: "2026-01-31T22:14:51.212Z"
        completed_at: "2026-01-31T22:24:57.305Z"

    JobCancelled:
      summary: Cancelled Job
      description: A job that was cancelled by the user
      value:
        id: "81cea460-5e30-4f15-9e0d-9493541a3c7d"
        model_id: 1
        job_type: evaluate
        status: failed
        output: null
        error_message: Cancelled by user
        created_at: "2026-01-31T22:14:50.285Z"
        started_at: null
        completed_at: "2026-01-31T22:15:02.123Z"

    PreviewJobCreated:
      summary: Preview Job Created
      description: A preview job that was just created
      value:
        id: "616aacdb-1501-47d9-b09c-ac3164f6885b"
        model_id: null
        job_type: preview
        status: pending
        output: null
        error_message: null
        created_at: "2026-01-31T22:15:59.724Z"
        started_at: null
        completed_at: null

    CreatePreviewRequest:
      summary: Create Preview Request
      description: Request to preview model performance without saving
      value:
        bet_type: spread
        mode: simple
        factors:
          - factor_id: 1
            importance: high
          - factor_id: 2
            importance: medium

    JobNotFoundError:
      summary: Job not found
      description: Error when job ID doesn't exist
      value:
        error: Job not found

    JobInvalidUUIDError:
      summary: Invalid UUID error
      description: Error when job ID is not a valid UUID
      value:
        errors:
          - param: id
            error: id must be a valid UUID

    # ============================================
    # Performance Examples
    # ============================================
    PerformanceNotEvaluated:
      summary: Performance Not Evaluated
      description: Response when model has not been evaluated yet
      value:
        data: null

    PerformanceRunning:
      summary: Performance Running
      description: Response when performance evaluation is in progress
      value:
        data:
          model_id: 1
          status: running
          error_message: null
          evaluated_at: null
          games_evaluated: null
          date_range_start: null
          date_range_end: null
          total_bets: null
          wins: null
          losses: null
          pushes: null
          win_rate: null
          roi: null
          avg_confidence: null
          results_by_confidence: null
          results_over_time: null

tags:
  - name: Factors
    description: Pre-built factors for building prediction models
  - name: Models
    description: User-created betting prediction models
  - name: Predictions
    description: Model predictions for games
  - name: Performance
    description: Model performance evaluation
  - name: Jobs
    description: Background job status

paths:
  # ============================================
  # Factor Endpoints
  # ============================================
  /lab/v1/factors:
    get:
      operationId: listFactors
      summary: List all factors
      description: |
        Returns all available factors for building prediction models.
        Factors are pre-built analytical components that can be combined to create models.
      tags:
        - Factors
      parameters:
        - name: category
          in: query
          schema:
            type: string
          description: Filter by factor category (team_performance, matchup, situational, player, market)
        - name: sport
          in: query
          schema:
            $ref: "#/components/schemas/Sport"
          description: Filter by sport (default nba)
      responses:
        "200":
          description: List of factors
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Factor"
              examples:
                factorList:
                  $ref: "#/components/examples/FactorListResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /lab/v1/factors/{id}:
    get:
      operationId: getFactor
      summary: Get a factor
      description: Returns details of a specific factor.
      tags:
        - Factors
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Factor ID
      responses:
        "200":
          description: Factor details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Factor"
              examples:
                teamPerformanceFactor:
                  $ref: "#/components/examples/FactorTeamPerformance"
                matchupFactor:
                  $ref: "#/components/examples/FactorMatchup"
                situationalFactor:
                  $ref: "#/components/examples/FactorSituational"
        "400":
          description: Invalid factor ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Factor not found

  # ============================================
  # Model Endpoints
  # ============================================
  /lab/v1/models:
    get:
      operationId: listModels
      summary: List user's models
      description: Returns all models owned by the authenticated user.
      tags:
        - Models
      parameters:
        - name: sport
          in: query
          schema:
            type: string
          description: Filter by sport
        - name: bet_type
          in: query
          schema:
            type: string
          description: Filter by bet type
        - name: per_page
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Number of results per page
        - name: cursor
          in: query
          schema:
            type: integer
          description: Cursor for pagination
      responses:
        "200":
          description: List of models
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Model"
                  meta:
                    $ref: "#/components/schemas/CursorPagination"
              examples:
                modelList:
                  $ref: "#/components/examples/ModelListResponse"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    post:
      operationId: createModel
      summary: Create a model
      description: |
        Creates a new prediction model.

        For weighted mode, factor weights must sum to 100.
      tags:
        - Models
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateModelRequest"
            examples:
              simpleMode:
                $ref: "#/components/examples/CreateModelSimpleRequest"
              weightedMode:
                $ref: "#/components/examples/CreateModelWeightedRequest"
      responses:
        "201":
          description: Model created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Model"
              examples:
                simpleModel:
                  $ref: "#/components/examples/ModelSimpleMode"
                weightedModel:
                  $ref: "#/components/examples/ModelWeightedMode"
        "400":
          description: Invalid request body or weights don't sum to 100
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                weightValidation:
                  $ref: "#/components/examples/WeightValidationError"

  /lab/v1/models/{id}:
    get:
      operationId: getModel
      summary: Get a model
      description: Returns details of a specific model owned by the user.
      tags:
        - Models
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
      responses:
        "200":
          description: Model details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Model"
        "400":
          description: Invalid model ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found

    put:
      operationId: updateModel
      summary: Update a model
      description: |
        Updates an existing model.

        For weighted mode, factor weights must sum to 100.

        **Note:** Editing factors, mode, bet_type, or advanced_config clears any existing performance data.
      tags:
        - Models
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateModelRequest"
      responses:
        "200":
          description: Model updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Model"
        "400":
          description: Invalid request body or weights don't sum to 100
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found

    delete:
      operationId: deleteModel
      summary: Delete a model
      description: Deletes a model and all associated data (predictions, performance, jobs).
      tags:
        - Models
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
      responses:
        "204":
          description: Model deleted
        "400":
          description: Invalid model ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found

  # ============================================
  # Prediction Endpoints
  # ============================================
  /lab/v1/predictions:
    get:
      operationId: listPredictions
      summary: List predictions for a model
      description: Returns predictions for a specific model with game details.
      tags:
        - Predictions
      parameters:
        - name: model_id
          in: query
          required: true
          schema:
            type: integer
          description: Model ID (required)
        - name: result
          in: query
          schema:
            type: string
          description: Filter by result (win, loss, push)
        - name: start_date
          in: query
          schema:
            type: string
            format: date
          description: Filter by start date (ISO8601)
        - name: end_date
          in: query
          schema:
            type: string
            format: date
          description: Filter by end date (ISO8601)
        - name: per_page
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 25
          description: Number of results per page
        - name: cursor
          in: query
          schema:
            type: integer
          description: Cursor for pagination
      responses:
        "200":
          description: List of predictions with game details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PredictionWithGame"
                  meta:
                    $ref: "#/components/schemas/CursorPagination"
              examples:
                empty:
                  $ref: "#/components/examples/PredictionListEmpty"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /lab/v1/predictions/stats:
    get:
      operationId: getPredictionStats
      summary: Get prediction statistics
      description: Returns aggregate statistics for a model's predictions.
      tags:
        - Predictions
      parameters:
        - name: model_id
          in: query
          required: true
          schema:
            type: integer
          description: Model ID (required)
      responses:
        "200":
          description: Prediction statistics
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/PredictionStats"
              examples:
                stats:
                  $ref: "#/components/examples/PredictionStats"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /lab/v1/predictions/{id}:
    get:
      operationId: getPrediction
      summary: Get a prediction
      description: Returns details of a specific prediction.
      tags:
        - Predictions
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Prediction ID
      responses:
        "200":
          description: Prediction details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Prediction"
        "400":
          description: Invalid prediction ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Prediction not found

  /lab/v1/models/{id}/predictions/generate:
    post:
      operationId: generatePredictions
      summary: Generate predictions
      description: |
        Creates a background job to generate predictions for upcoming games.
        Poll the job endpoint to get results.

        **Note:** Only one prediction generation job can run per model at a time. If a pending
        or running job already exists, a 409 Conflict is returned.
      tags:
        - Predictions
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
      responses:
        "201":
          description: Job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Job"
        "400":
          description: Invalid model ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Prediction generation already running
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                alreadyRunning:
                  summary: Active job exists
                  value:
                    error: Predictions are already being generated
                    active_job_id: "81cea460-5e30-4f15-9e0d-9493541a3c7d"

  # ============================================
  # Performance Endpoints
  # ============================================
  /lab/v1/performance/preview:
    post:
      operationId: createPreview
      summary: Create preview job
      description: |
        Creates a background job to preview model performance without saving.
        Returns historical performance and predictions for upcoming games.
        Poll the job endpoint to get results.
      tags:
        - Performance
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/PreviewRequest"
            examples:
              preview:
                $ref: "#/components/examples/CreatePreviewRequest"
      responses:
        "201":
          description: Preview job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Job"
              examples:
                previewJob:
                  $ref: "#/components/examples/PreviewJobCreated"
        "400":
          description: Invalid request body
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /lab/v1/models/{id}/performance:
    get:
      operationId: getModelPerformance
      summary: Get model performance
      description: |
        Returns performance evaluation results for a model.
        Returns null if evaluation has not been run yet.
      tags:
        - Performance
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
      responses:
        "200":
          description: Performance results (null if not evaluated)
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    oneOf:
                      - $ref: "#/components/schemas/ModelPerformance"
                      - type: "null"
              examples:
                notEvaluated:
                  $ref: "#/components/examples/PerformanceNotEvaluated"
                running:
                  $ref: "#/components/examples/PerformanceRunning"
        "400":
          description: Invalid model ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

    post:
      operationId: evaluatePerformance
      summary: Trigger performance evaluation
      description: |
        Creates a background job to run performance evaluation for a model against historical data.
        Returns immediately with a job object - poll the job endpoint to get results.
        Clears any existing performance data before running.

        **Season Format:** "YYYY-YY" (e.g., "2024-25")
        **Allowed Seasons:** 2020-21 through 2025-26

        **Note:** Only one evaluation job can run per model at a time. If a pending or running
        evaluation job already exists, a 409 Conflict is returned.
      tags:
        - Performance
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
      requestBody:
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/EvaluatePerformanceRequest"
      responses:
        "201":
          description: Evaluation job created
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Job"
              examples:
                evaluateJob:
                  summary: Evaluate job created
                  value:
                    data:
                      id: "81cea460-5e30-4f15-9e0d-9493541a3c7d"
                      model_id: 1
                      job_type: evaluate
                      status: pending
                      output: null
                      error_message: null
                      created_at: "2026-01-31T22:14:50.285Z"
                      started_at: null
                      completed_at: null
        "400":
          description: Invalid request (season format, etc.)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "409":
          description: Evaluation already running
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                alreadyRunning:
                  summary: Active job exists
                  value:
                    error: A backtest is already running
                    active_job_id: "81cea460-5e30-4f15-9e0d-9493541a3c7d"

    delete:
      operationId: clearPerformance
      summary: Clear performance data
      description: Clears all performance evaluation data for a model.
      tags:
        - Performance
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
      responses:
        "204":
          description: Performance data cleared
        "400":
          description: Invalid model ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /lab/v1/models/{id}/performance/games:
    get:
      operationId: getPerformanceGames
      summary: Get per-game performance details
      description: Returns paginated per-game breakdown of performance evaluation.
      tags:
        - Performance
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
        - name: limit
          in: query
          schema:
            type: integer
            default: 25
          description: Number of results per page
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
          description: Number of results to skip
        - name: result
          in: query
          schema:
            type: string
            enum: [win, loss, push, pending]
          description: Filter by result
      responses:
        "200":
          description: Per-game performance details
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/PerformanceGameResult"
                  meta:
                    $ref: "#/components/schemas/OffsetPagination"
        "400":
          description: Invalid parameters
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ============================================
  # Job Endpoints
  # ============================================
  /lab/v1/jobs/{id}:
    get:
      operationId: getJob
      summary: Get job status
      description: |
        Returns the status and output of a background job.
        Poll this endpoint until status is "completed" or "failed".
      tags:
        - Jobs
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Job ID (UUID)
      responses:
        "200":
          description: Job status and output
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Job"
              examples:
                pending:
                  $ref: "#/components/examples/JobPending"
                running:
                  $ref: "#/components/examples/JobRunning"
                failed:
                  $ref: "#/components/examples/JobFailed"
        "400":
          description: Invalid job ID (must be valid UUID)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                invalidUUID:
                  $ref: "#/components/examples/JobInvalidUUIDError"
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                notFound:
                  $ref: "#/components/examples/JobNotFoundError"

    delete:
      operationId: cancelJob
      summary: Cancel a pending job
      description: |
        Cancels a pending job. Only jobs with status "pending" can be cancelled.
        Running jobs cannot be cancelled as they are already being processed.
        Cancelled jobs have their status set to "failed" with error message "Cancelled by user".
      tags:
        - Jobs
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
          description: Job ID (UUID)
      responses:
        "200":
          description: Job cancelled successfully
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    $ref: "#/components/schemas/Job"
              examples:
                cancelled:
                  $ref: "#/components/examples/JobCancelled"
        "400":
          description: Job cannot be cancelled (not pending status)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                notPending:
                  summary: Cannot cancel non-pending job
                  value:
                    error: Cannot cancel job with status "running". Only pending jobs can be cancelled.
        "404":
          description: Job not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  /lab/v1/models/{id}/jobs/active:
    get:
      operationId: getActiveJobsForModel
      summary: Get active jobs for a model
      description: |
        Returns all pending and running jobs for a specific model.
        Used to determine if a model has jobs in progress that would prevent new jobs from being created.
      tags:
        - Jobs
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          description: Model ID
      responses:
        "200":
          description: List of active jobs
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items:
                      $ref: "#/components/schemas/Job"
              examples:
                activeJobs:
                  summary: Active jobs for model
                  value:
                    data:
                      - id: "81cea460-5e30-4f15-9e0d-9493541a3c7d"
                        model_id: 1
                        job_type: evaluate
                        status: running
                        output: null
                        error_message: null
                        created_at: "2026-01-31T22:14:50.285Z"
                        started_at: "2026-01-31T22:14:51.212Z"
                        completed_at: null
                noActiveJobs:
                  summary: No active jobs
                  value:
                    data: []
        "400":
          description: Invalid model ID
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
        "404":
          description: Model not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
