{
  "openapi": "3.0.3",
  "info": {
    "title": "BallparkPal API",
    "version": "1.0.0",
    "description": "Versioned JSON API for BallparkPal."
  },
  "servers": [
    {
      "url": "/api",
      "description": "API root"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "ApiKeyQueryAuth": []
    }
  ],
  "paths": {
    "/v1/health": {
      "get": {
        "summary": "Health check",
        "security": [],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/markets": {
      "get": {
        "summary": "List active markets",
        "responses": {
          "200": {
            "description": "Market definitions",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MarketsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/teams": {
      "get": {
        "summary": "List teams",
        "responses": {
          "200": {
            "description": "Team list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/TeamsResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/players": {
      "get": {
        "summary": "List players",
        "parameters": [
          {
            "name": "teamId",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          },
          {
            "name": "q",
            "in": "query",
            "schema": {
              "type": "string",
              "maxLength": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Player list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayersResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/games": {
      "get": {
        "summary": "List games",
        "description": "Provide at least one of date (preferred) or gameId.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "gameId",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Game list",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GamesResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/games/{gameId}": {
      "get": {
        "summary": "Get game by gameId",
        "parameters": [
          {
            "name": "gameId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Single game",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/GameResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/projections": {
      "get": {
        "summary": "Get game projections",
        "description": "Projections for a single game. gameId is required; response data.items is the list of projection items.",
        "parameters": [
          {
            "name": "gameId",
            "in": "query",
            "required": true,
            "schema": {
              "type": "integer",
              "minimum": 1
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Projection items",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ProjectionsResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/parkfactors": {
      "get": {
        "summary": "Game-level park factors",
        "description": "Returns Ballpark Pal modeled park factors for every game on a given date. Percent values are integers (e.g. 18 = 18%). Amount values represent the estimated change in counting stats for the game.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Game date in YYYY-MM-DD format."
          }
        ],
        "responses": {
          "200": {
            "description": "Park factors for all games on the date",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkFactorsResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/v1/parkfactors/hitters": {
      "get": {
        "summary": "Hitter-level park factors",
        "description": "Returns individual hitter park factors at the game level. Provide at least one of date or gameId.",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "date"
            },
            "description": "Game date in YYYY-MM-DD format."
          },
          {
            "name": "gameId",
            "in": "query",
            "schema": {
              "type": "integer",
              "minimum": 1
            },
            "description": "Filter to a single game."
          }
        ],
        "responses": {
          "200": {
            "description": "Hitter park factors",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ParkFactorsHittersResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "500": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "responses": {
      "Error": {
        "description": "Error response",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorEnvelope"
            }
          }
        }
      }
    },
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "API key issued by BallparkPal."
      },
      "ApiKeyQueryAuth": {
        "type": "apiKey",
        "in": "query",
        "name": "apiKey",
        "description": "API key issued by BallparkPal. Header auth is preferred."
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "required": [
          "asOf",
          "requestId"
        ],
        "properties": {
          "asOf": {
            "type": "string",
            "format": "date-time"
          },
          "requestId": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "limit": {
            "type": "integer"
          },
          "offset": {
            "type": "integer"
          }
        },
        "additionalProperties": true
      },
      "Error": {
        "type": "object",
        "required": [
          "code",
          "message",
          "requestId"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          },
          "requestId": {
            "type": "string"
          }
        }
      },
      "ErrorEnvelope": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "$ref": "#/components/schemas/Error"
          }
        }
      },
      "Market": {
        "type": "object",
        "required": [
          "marketKey",
          "marketType",
          "subjectType",
          "displayName",
          "usesLine",
          "status"
        ],
        "properties": {
          "marketKey": {
            "type": "string"
          },
          "marketType": {
            "type": "string",
            "enum": [
              "game",
              "team",
              "batter",
              "pitcher"
            ]
          },
          "subjectType": {
            "type": "string",
            "enum": [
              "game",
              "team",
              "player"
            ]
          },
          "displayName": {
            "type": "string"
          },
          "usesLine": {
            "type": "boolean",
            "description": "False for moneyline-style markets, true for line-based markets."
          },
          "status": {
            "type": "string",
            "enum": [
              "active"
            ]
          }
        }
      },
      "Team": {
        "type": "object",
        "required": [
          "teamId",
          "abv",
          "city",
          "nickname"
        ],
        "properties": {
          "teamId": {
            "type": "integer"
          },
          "abv": {
            "type": "string"
          },
          "city": {
            "type": "string"
          },
          "nickname": {
            "type": "string"
          }
        }
      },
      "Player": {
        "type": "object",
        "required": [
          "playerId",
          "teamId",
          "firstName",
          "lastName",
          "fullName"
        ],
        "properties": {
          "playerId": {
            "type": "integer"
          },
          "teamId": {
            "type": "integer",
            "nullable": true
          },
          "firstName": {
            "type": "string"
          },
          "lastName": {
            "type": "string"
          },
          "fullName": {
            "type": "string"
          }
        }
      },
      "Game": {
        "type": "object",
        "required": [
          "gameId",
          "gameDate",
          "gameTime",
          "teamAwayId",
          "teamHomeId",
          "venueId"
        ],
        "properties": {
          "gameId": {
            "type": "integer"
          },
          "gameDate": {
            "type": "string",
            "format": "date",
            "nullable": true
          },
          "gameTime": {
            "type": "string"
          },
          "teamAwayId": {
            "type": "integer",
            "nullable": true
          },
          "teamHomeId": {
            "type": "integer",
            "nullable": true
          },
          "venueId": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "ProjectionSubject": {
        "type": "object",
        "required": [
          "type",
          "id"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "game",
              "team",
              "player"
            ]
          },
          "id": {
            "type": "integer"
          }
        }
      },
      "ProjectionItem": {
        "type": "object",
        "required": [
          "marketType",
          "marketKey",
          "displayName",
          "line",
          "side",
          "odds",
          "probability",
          "average",
          "subject"
        ],
        "properties": {
          "marketType": {
            "type": "string",
            "enum": [
              "game",
              "team",
              "batter",
              "pitcher"
            ]
          },
          "marketKey": {
            "type": "string"
          },
          "displayName": {
            "type": "string"
          },
          "line": {
            "type": "number"
          },
          "side": {
            "type": "string"
          },
          "odds": {
            "type": "integer"
          },
          "probability": {
            "type": "number"
          },
          "average": {
            "type": "number",
            "nullable": true,
            "description": "Simulated average projection for this stat. Null for probability-based markets (e.g. Moneyline, Pitcher Win) and markets without a projection mapping."
          },
          "subject": {
            "$ref": "#/components/schemas/ProjectionSubject"
          },
          "teamId": {
            "type": "integer",
            "nullable": true
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "data": {
            "type": "object",
            "required": [
              "status"
            ],
            "properties": {
              "status": {
                "type": "string",
                "enum": [
                  "ok"
                ]
              }
            }
          }
        }
      },
      "MarketsResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "data": {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Market"
                }
              }
            }
          }
        }
      },
      "TeamsResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "data": {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Team"
                }
              }
            }
          }
        }
      },
      "PlayersResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "data": {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Player"
                }
              }
            }
          }
        }
      },
      "GamesResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "data": {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/Game"
                }
              }
            }
          }
        }
      },
      "GameResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "data": {
            "$ref": "#/components/schemas/Game"
          }
        }
      },
      "ParkFactor": {
        "type": "object",
        "required": [
          "gameId",
          "gameTime",
          "teamAway",
          "teamHome",
          "runsPercent",
          "homeRunsPercent",
          "doublesTriplesPercent",
          "singlesPercent",
          "runsAmount",
          "homeRunsAmount",
          "doublesTriplesAmount",
          "singlesAmount"
        ],
        "properties": {
          "gameId": {
            "type": "integer",
            "description": "MLB game identifier (GamePk)."
          },
          "gameTime": {
            "type": "string",
            "description": "Scheduled game time."
          },
          "teamAway": {
            "type": "string",
            "description": "Away team abbreviation."
          },
          "teamHome": {
            "type": "string",
            "description": "Home team abbreviation."
          },
          "runsPercent": {
            "type": "integer",
            "description": "Modeled runs park factor as an integer percent (18 = 18%)."
          },
          "homeRunsPercent": {
            "type": "integer",
            "description": "Modeled home runs park factor as an integer percent."
          },
          "doublesTriplesPercent": {
            "type": "integer",
            "description": "Modeled doubles/triples park factor as an integer percent."
          },
          "singlesPercent": {
            "type": "integer",
            "description": "Modeled singles park factor as an integer percent."
          },
          "runsAmount": {
            "type": "number",
            "description": "Estimated change in runs for the game (decimal)."
          },
          "homeRunsAmount": {
            "type": "number",
            "description": "Estimated change in home runs for the game (decimal)."
          },
          "doublesTriplesAmount": {
            "type": "number",
            "description": "Estimated change in doubles/triples for the game (decimal)."
          },
          "singlesAmount": {
            "type": "number",
            "description": "Estimated change in singles for the game (decimal)."
          }
        }
      },
      "HitterParkFactor": {
        "type": "object",
        "required": [
          "gameId",
          "gameTime",
          "teamAway",
          "teamHome",
          "playerId",
          "playerName",
          "team",
          "homeRuns",
          "doublesTriples",
          "singles"
        ],
        "properties": {
          "gameId": {
            "type": "integer",
            "description": "MLB game identifier (GamePk)."
          },
          "gameTime": {
            "type": "string",
            "description": "Scheduled game time."
          },
          "teamAway": {
            "type": "string",
            "description": "Away team abbreviation."
          },
          "teamHome": {
            "type": "string",
            "description": "Home team abbreviation."
          },
          "playerId": {
            "type": "integer",
            "description": "MLB player identifier."
          },
          "playerName": {
            "type": "string",
            "description": "Player full name."
          },
          "team": {
            "type": "string",
            "description": "Player's team abbreviation."
          },
          "homeRuns": {
            "type": "number",
            "description": "Hitter-specific HR park factor multiplier."
          },
          "doublesTriples": {
            "type": "number",
            "description": "Hitter-specific 2B/3B park factor multiplier."
          },
          "singles": {
            "type": "number",
            "description": "Hitter-specific 1B park factor multiplier."
          }
        }
      },
      "ParkFactorsResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "data": {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ParkFactor"
                }
              }
            }
          }
        }
      },
      "ParkFactorsHittersResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "$ref": "#/components/schemas/Meta"
          },
          "data": {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/HitterParkFactor"
                }
              }
            }
          }
        }
      },
      "ProjectionsResponse": {
        "type": "object",
        "required": [
          "meta",
          "data"
        ],
        "properties": {
          "meta": {
            "type": "object",
            "properties": {
              "gameId": {
                "type": "integer"
              },
              "asOf": {
                "type": "string",
                "format": "date-time"
              }
            },
            "additionalProperties": true
          },
          "data": {
            "type": "object",
            "required": [
              "items"
            ],
            "properties": {
              "items": {
                "type": "array",
                "items": {
                  "$ref": "#/components/schemas/ProjectionItem"
                }
              }
            }
          }
        }
      }
    }
  }
}
