{
  "openapi": "3.1.0",
  "info": {
    "title": "GridIron Data API",
    "version": "1.0.0",
    "summary": "Fantasy football and NFL player-prop data: players, weekly, rest-of-season and per-stat projections, stats, snaps, deviations, matchups, news, schedule with betting odds, prop probabilities, defense vs position and injury changes.",
    "description": "REST API for fantasy football and NFL player-prop data. Send your key in the `x-api-key` header on every request (the health check needs none).\n\nPlans (each operation's `x-plan` is the lowest plan that can call it):\n\n| Plan | Price | Requests | Per minute |\n|---|---|---|---|\n| `free` | $0, no card | 50/month | 10 |\n| `basic` | $45/year or $5/month | 5,000/month | 30 |\n| `pro` | $29/month | 50,000/month | 100 |\n\nPro is the betting plan: `/props`, `/defense`, `odds` on `/schedule` games and `projected_stats` on `/projections/{week}` rows are Pro-only. Pro also includes injury change webhooks (`POST /webhooks` with an HTTPS URL; signed with HMAC-SHA256), documented at https://www.gridirondata.com/pages/docs.html#webhooks. Machine-readable plain-text reference for LLMs: https://www.gridirondata.com/llms-full.txt.\n\nA key below an endpoint's plan gets 403 with `reason` `basic_required` or `pro_required` and an `upgrade_url`. Limits answer 429 with `reason` `rate_limited`, `quota_exceeded` or `free_monthly_quota`.\n\nTeam codes: responses use `LAR`, `WAS` and `JAX`; `LA`, `WSH` and `JAC` are accepted as input. Calls without `season` use the current season (`GET /health` reports it).",
    "termsOfService": "https://www.gridirondata.com/pages/terms.html",
    "contact": {
      "name": "GridIron Data support",
      "email": "support@gridirondata.com",
      "url": "https://www.gridirondata.com/pages/docs.html"
    }
  },
  "externalDocs": {
    "description": "API documentation",
    "url": "https://www.gridirondata.com/pages/docs.html"
  },
  "servers": [
    {
      "url": "https://api.gridirondata.com/api/v1",
      "description": "Production"
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    }
  ],
  "tags": [
    {
      "name": "Players",
      "description": "Player profiles and lookup."
    },
    {
      "name": "Projections",
      "description": "Weekly projected points."
    },
    {
      "name": "Stats",
      "description": "Actuals, snap share, deviations and matchup history."
    },
    {
      "name": "News",
      "description": "Player and league-wide news."
    },
    {
      "name": "Injuries",
      "description": "Pro injury status changes feed."
    },
    {
      "name": "Schedule",
      "description": "NFL schedule, scores and bye weeks."
    },
    {
      "name": "Props",
      "description": "Pro betting tools: prop probabilities and defense vs position."
    },
    {
      "name": "Meta",
      "description": "Service status."
    }
  ],
  "paths": {
    "/health": {
      "get": {
        "operationId": "getHealth",
        "summary": "Health check",
        "description": "Liveness check. Reports the API version and the default season. Does not count against your quota.\n\n**Plan:** No API key needed.",
        "tags": [
          "Meta"
        ],
        "x-plan": "none",
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Health"
                },
                "example": {
                  "status": "healthy",
                  "version": "1.0.0",
                  "default_season": "2026"
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "security": []
      }
    },
    "/players": {
      "get": {
        "operationId": "listPlayers",
        "summary": "List players, or get one by exact name and position",
        "description": "Without `name`+`position`: a page of players (paginate with `cursor`/`next_cursor`). With both `name` and `position`: the single exact match (409 if ambiguous).\n\n**Plan:** Free plan and above. Free keys get profile fields only.\n\nWith `espn_id`, returns the one player with that ESPN athlete id.",
        "tags": [
          "Players"
        ],
        "x-plan": "free",
        "x-plan-note": "Free keys get profile fields only.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Exact player name. With `position`, returns that one player.",
            "schema": {
              "type": "string"
            },
            "example": "Josh Allen"
          },
          {
            "name": "position",
            "in": "query",
            "required": false,
            "description": "Filter by position (list), or the exact-match position.",
            "schema": {
              "$ref": "#/components/schemas/Position"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Exact-match form only: return just this season under `seasons`.",
            "schema": {
              "type": "string",
              "pattern": "^(19|20)\\d{2}$"
            },
            "example": "2026"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1-100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "espn_id",
            "in": "query",
            "required": false,
            "description": "Look a player up by ESPN athlete id (the numeric key sportsbook feeds use).",
            "schema": {
              "type": "integer"
            },
            "example": 3918298
          }
        ],
        "responses": {
          "200": {
            "description": "A page of players, or one player for the exact-match form.",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "#/components/schemas/PlayerList"
                    },
                    {
                      "$ref": "#/components/schemas/Player"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/players/search": {
      "get": {
        "operationId": "searchPlayers",
        "summary": "Search players",
        "description": "Case-insensitive substring search on name, with exact position/team filters. Defenses also match by team code, nickname or a DST suffix (`TB`, `Bucs`, `Tampa Bay DST`).\n\n**Plan:** Free plan and above. Free keys get profile fields only.",
        "tags": [
          "Players"
        ],
        "x-plan": "free",
        "x-plan-note": "Free keys get profile fields only.",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Partial or full name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/PositionFilterQuery"
          },
          {
            "$ref": "#/components/parameters/TeamFilterQuery"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum results, up to 100.",
            "schema": {
              "type": "integer",
              "maximum": 100,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerList"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/players/{player_id}": {
      "get": {
        "operationId": "getPlayer",
        "summary": "Get a player",
        "description": "The full player record: per-season stats, projections, snap counts, injury status and career matchup history.\n\n**Plan:** Free plan and above. Free keys get profile fields only.",
        "tags": [
          "Players"
        ],
        "x-plan": "free",
        "x-plan-note": "Free keys get profile fields only.",
        "parameters": [
          {
            "$ref": "#/components/parameters/PlayerIdPath"
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Return only this season under `seasons`.",
            "schema": {
              "type": "string",
              "pattern": "^(19|20)\\d{2}$"
            },
            "example": "2026"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Player"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/projections/ros": {
      "get": {
        "operationId": "getRestOfSeasonProjections",
        "summary": "Rest-of-season projections",
        "description": "Each player's projected fantasy points for the weeks left this season, skipping his bye and weeks already played. `method` says whether weekly projections or his per-game rate applied.\n\n**Plan:** Basic ($45/year or $5/month) and above; free keys get 403 `basic_required`.",
        "tags": [
          "Projections"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/PositionFilterQuery"
          },
          {
            "$ref": "#/components/parameters/TeamFilterQuery"
          },
          {
            "$ref": "#/components/parameters/PlayerIdQuery"
          },
          {
            "$ref": "#/components/parameters/ScoringQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RosProjections"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/projections/{week}": {
      "get": {
        "operationId": "getProjections",
        "summary": "Weekly projections",
        "description": "Projected points for every player for one week, highest first. Players who can't play project 0 with `available: false`; `projected_points_if_active` keeps the number.\n\n**Plan:** Free keys get the current week only; other weeks and seasons need Basic. Per-stat lines (`projected_stats`) need Pro.",
        "tags": [
          "Projections"
        ],
        "x-plan": "free",
        "x-plan-note": "Free keys get the current NFL week of the current season only; other weeks and seasons need Basic.",
        "parameters": [
          {
            "name": "week",
            "in": "path",
            "required": true,
            "description": "NFL week, 1-18.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 18
            }
          },
          {
            "$ref": "#/components/parameters/PositionFilterQuery"
          },
          {
            "$ref": "#/components/parameters/TeamFilterQuery"
          },
          {
            "$ref": "#/components/parameters/SeasonQuery"
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "`weekly` returns only real weekly projections (no prorated fallback).",
            "schema": {
              "type": "string",
              "enum": [
                "weekly"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/ScoringQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Projections"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/stats": {
      "get": {
        "operationId": "getStatsByName",
        "summary": "Player stats by name",
        "description": "Same as `GET /stats/{player_id}/{season}`, looking the player up by exact name and position.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Stats"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/NameQuery"
          },
          {
            "$ref": "#/components/parameters/PositionQuery"
          },
          {
            "$ref": "#/components/parameters/SeasonQuery"
          },
          {
            "$ref": "#/components/parameters/ScoringQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerStats"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/stats/{player_id}/{season}": {
      "get": {
        "operationId": "getStats",
        "summary": "Player stats for a season",
        "description": "Game-by-game actuals and season totals, with points resolved for the chosen scoring and snap share joined onto each week.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Stats"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/PlayerIdPath"
          },
          {
            "name": "season",
            "in": "path",
            "required": true,
            "description": "Season year.",
            "schema": {
              "type": "string",
              "pattern": "^(19|20)\\d{2}$"
            },
            "example": "2025"
          },
          {
            "$ref": "#/components/parameters/ScoringQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerStats"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/snaps": {
      "get": {
        "operationId": "getSnapsByName",
        "summary": "Snap share by name",
        "description": "Same as `GET /snaps/{player_id}`, looking the player up by exact name and position.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Stats"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/NameQuery"
          },
          {
            "$ref": "#/components/parameters/PositionQuery"
          },
          {
            "$ref": "#/components/parameters/SeasonQuery"
          },
          {
            "$ref": "#/components/parameters/TrendQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Snaps"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/snaps/{player_id}": {
      "get": {
        "operationId": "getSnaps",
        "summary": "Snap share",
        "description": "Per-week offensive snap share (0-100), the season average and a trailing-N-week trend.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Stats"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/PlayerIdPath"
          },
          {
            "$ref": "#/components/parameters/SeasonQuery"
          },
          {
            "$ref": "#/components/parameters/TrendQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Snaps"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/deviations": {
      "get": {
        "operationId": "getDeviationsByName",
        "summary": "Projection deviations by name",
        "description": "Same as `GET /deviations/{player_id}`, looking the player up by exact name and position.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Stats"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/NameQuery"
          },
          {
            "$ref": "#/components/parameters/PositionQuery"
          },
          {
            "$ref": "#/components/parameters/SeasonQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deviations"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/deviations/{player_id}": {
      "get": {
        "operationId": "getDeviations",
        "summary": "Projection deviations",
        "description": "Weekly actual-vs-projected points and the season average deviation.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Stats"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/PlayerIdPath"
          },
          {
            "$ref": "#/components/parameters/SeasonQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Deviations"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/matchups": {
      "get": {
        "operationId": "getMatchupByName",
        "summary": "Matchup history by name",
        "description": "Same as `GET /matchups/{player_id}/{opponent}`, looking the player up by exact name and position.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Stats"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/NameQuery"
          },
          {
            "$ref": "#/components/parameters/PositionQuery"
          },
          {
            "name": "opponent",
            "in": "query",
            "required": true,
            "description": "Opponent team code.",
            "schema": {
              "type": "string"
            },
            "example": "KC"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matchup"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/matchups/{player_id}/{opponent}": {
      "get": {
        "operationId": "getMatchup",
        "summary": "Matchup history",
        "description": "A player's career and per-season performance against one opponent.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Stats"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/PlayerIdPath"
          },
          {
            "name": "opponent",
            "in": "path",
            "required": true,
            "description": "Opponent team code; either spelling works for LA/LAR, WSH/WAS, JAC/JAX.",
            "schema": {
              "type": "string"
            },
            "example": "KC"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Matchup"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/news": {
      "get": {
        "operationId": "getNewsByName",
        "summary": "Player news by name",
        "description": "Same as `GET /news/{player_id}`, looking the player up by exact name and position.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "News"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/NameQuery"
          },
          {
            "$ref": "#/components/parameters/PositionQuery"
          },
          {
            "$ref": "#/components/parameters/NewsDaysQuery"
          },
          {
            "$ref": "#/components/parameters/NewsLimitQuery"
          },
          {
            "$ref": "#/components/parameters/NewsImpactQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerNews"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/news/latest": {
      "get": {
        "operationId": "getLatestNews",
        "summary": "League-wide news feed",
        "description": "Every tracked player's news, newest first. Paginate with `cursor`/`next_cursor`.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "News"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO-8601 or unix seconds. Default: now minus `days`. Clamped to 90 days.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Lookback when `since` is omitted, 1-90.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 90,
              "default": 7
            }
          },
          {
            "name": "impact",
            "in": "query",
            "required": false,
            "description": "Comma-separated impact tags: injury, transaction, role, general.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "position",
            "in": "query",
            "required": false,
            "description": "Comma-separated positions.",
            "schema": {
              "type": "string"
            }
          },
          {
            "$ref": "#/components/parameters/TeamFilterQuery"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Page size, 1-100.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 100,
              "default": 25
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "`next_cursor` from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/LatestNews"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/news/{player_id}": {
      "get": {
        "operationId": "getPlayerNews",
        "summary": "Player news",
        "description": "Recent impact-tagged headlines for one player, newest first. When there is none, `request_scrape` links to the scrape endpoint.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "News"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/PlayerIdPath"
          },
          {
            "$ref": "#/components/parameters/NewsDaysQuery"
          },
          {
            "$ref": "#/components/parameters/NewsLimitQuery"
          },
          {
            "$ref": "#/components/parameters/NewsImpactQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PlayerNews"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/news/{player_id}/request": {
      "post": {
        "operationId": "requestPlayerNewsScrape",
        "summary": "Request a news scrape",
        "description": "Queues an on-demand news scrape for one player. Poll `GET /news/{player_id}` afterwards.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "News"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/PlayerIdPath"
          }
        ],
        "responses": {
          "202": {
            "description": "Scrape queued.",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NewsScrapeAccepted"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          },
          "502": {
            "$ref": "#/components/responses/BadGateway"
          }
        }
      }
    },
    "/injuries/changes": {
      "get": {
        "operationId": "getInjuryChanges",
        "summary": "Injury status changes",
        "description": "Players whose injury status changed since `since`. Poll with `next_since` to get only new changes. A refresh run is never split across responses.\n\n**Plan:** Pro plan ($29/month) and above. Free and Basic keys get 403 `pro_required`.",
        "tags": [
          "Injuries"
        ],
        "x-plan": "pro",
        "parameters": [
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "ISO-8601 or unix seconds, exclusive. Default 24 hours ago; maximum lookback 14 days.",
            "schema": {
              "type": "string"
            },
            "example": "2026-09-27T16:00:00Z"
          },
          {
            "$ref": "#/components/parameters/PositionFilterQuery"
          },
          {
            "$ref": "#/components/parameters/TeamFilterQuery"
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Maximum changes, 1-1000.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 1000,
              "default": 500
            }
          },
          {
            "$ref": "#/components/parameters/SeasonQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/InjuryChanges"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/schedule": {
      "get": {
        "operationId": "getSchedule",
        "summary": "NFL schedule",
        "description": "Regular-season games with kickoff (UTC), status and final score. With `team`, also returns that team's `bye_week`.\n\n**Plan:** Basic ($45/year or $5/month) and above. Pro keys also get each game's `odds` (spread, over/under, moneylines, implied points) once posted.",
        "tags": [
          "Schedule"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/SeasonQuery"
          },
          {
            "$ref": "#/components/parameters/WeekFilterQuery"
          },
          {
            "$ref": "#/components/parameters/TeamFilterQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Schedule"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/byes": {
      "get": {
        "operationId": "getByes",
        "summary": "Bye weeks",
        "description": "Each team's bye week, and the teams on bye each week.\n\n**Plan:** Basic plan ($5/month) and above. A free key gets 403 `basic_required`.",
        "tags": [
          "Schedule"
        ],
        "x-plan": "basic",
        "parameters": [
          {
            "$ref": "#/components/parameters/SeasonQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Byes"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/props": {
      "get": {
        "operationId": "getPropProbability",
        "summary": "Player prop probability",
        "description": "P(over), P(under) and fair no-vig odds for a player stat line, e.g. Drake Maye over 245.5 passing yards, with the model's walk-forward backtest. Players on bye or unavailable get p_over 0. Model estimate, not betting advice.\n\n**Plan:** Pro ($29/month) and above.",
        "tags": [
          "Props"
        ],
        "x-plan": "pro",
        "parameters": [
          {
            "$ref": "#/components/parameters/PlayerIdQuery"
          },
          {
            "name": "espn_id",
            "in": "query",
            "required": false,
            "description": "ESPN athlete id, instead of player_id.",
            "schema": {
              "type": "integer"
            },
            "example": 4431452
          },
          {
            "$ref": "#/components/parameters/NameQuery"
          },
          {
            "$ref": "#/components/parameters/PositionQuery"
          },
          {
            "name": "stat",
            "in": "query",
            "required": true,
            "description": "The stat the line is on.",
            "schema": {
              "type": "string",
              "enum": [
                "pass_att",
                "pass_cmp",
                "pass_yds",
                "pass_td",
                "pass_int",
                "rush_att",
                "rush_yds",
                "rush_td",
                "rec",
                "rec_yds",
                "rec_td"
              ]
            },
            "example": "pass_yds"
          },
          {
            "name": "line",
            "in": "query",
            "required": true,
            "description": "The line, or up to 10 separated by commas.",
            "schema": {
              "type": "string"
            },
            "example": "245.5"
          },
          {
            "name": "week",
            "in": "query",
            "required": false,
            "description": "NFL week, 1-18. Default: the current week, or the next one if the player's game this week is already final.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 18
            }
          },
          {
            "$ref": "#/components/parameters/SeasonQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PropProbability"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "externalDocs": {
          "description": "How the props model works: distributions, fitting and backtest",
          "url": "https://www.gridirondata.com/pages/guide-price-player-props.html"
        }
      }
    },
    "/defense": {
      "get": {
        "operationId": "getDefenseVsPosition",
        "summary": "Defense vs position",
        "description": "What each defense allows per game to QBs, RBs, WRs and TEs: fantasy points and the stats behind them, from ESPN box scores. Rank 1 allows the most. Use `last` for trailing weeks.\n\n**Plan:** Pro ($29/month) and above; free and Basic keys get 403 `pro_required`.",
        "tags": [
          "Props"
        ],
        "x-plan": "pro",
        "parameters": [
          {
            "$ref": "#/components/parameters/SeasonQuery"
          },
          {
            "name": "position",
            "in": "query",
            "required": false,
            "description": "QB, RB, WR or TE. Default: all four.",
            "schema": {
              "type": "string",
              "enum": [
                "QB",
                "RB",
                "WR",
                "TE"
              ]
            }
          },
          {
            "name": "last",
            "in": "query",
            "required": false,
            "description": "Only the last N weeks with games.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 18
            }
          },
          {
            "$ref": "#/components/parameters/TeamFilterQuery"
          },
          {
            "$ref": "#/components/parameters/ScoringQuery"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/DefenseVsPosition"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Your API key from the dashboard. Get a free key at https://www.gridirondata.com/pages/signup.html"
      }
    },
    "parameters": {
      "PlayerIdPath": {
        "name": "player_id",
        "in": "path",
        "required": true,
        "description": "Player ID in the form `First Last#POSITION`, e.g. `Josh Allen#QB` (defenses: `Houston Texans#DST`). URL-encode the space (`%20`) and `#` (`%23`). Position case and a missing generational suffix are tolerated.",
        "schema": {
          "type": "string"
        },
        "example": "Josh Allen#QB"
      },
      "NameQuery": {
        "name": "name",
        "in": "query",
        "required": true,
        "description": "Exact player name (case-insensitive).",
        "schema": {
          "type": "string"
        },
        "example": "Josh Allen"
      },
      "PositionQuery": {
        "name": "position",
        "in": "query",
        "required": true,
        "description": "Player position.",
        "schema": {
          "$ref": "#/components/schemas/Position"
        }
      },
      "SeasonQuery": {
        "name": "season",
        "in": "query",
        "required": false,
        "description": "Season year. Defaults to the current season (see `GET /health` `default_season`).",
        "schema": {
          "type": "string",
          "pattern": "^(19|20)\\d{2}$"
        },
        "example": "2026"
      },
      "ScoringQuery": {
        "name": "scoring",
        "in": "query",
        "required": false,
        "description": "Scoring format: `ppr` (default), `half` or `standard`. Aliases `half_ppr`, `std`, `non_ppr` are accepted. The response echoes the applied value.",
        "schema": {
          "type": "string",
          "default": "ppr",
          "enum": [
            "ppr",
            "half",
            "standard",
            "half_ppr",
            "halfppr",
            "std",
            "non_ppr",
            "nonppr"
          ]
        }
      },
      "TrendQuery": {
        "name": "trend",
        "in": "query",
        "required": false,
        "description": "Weeks in the trailing snap-share average, 1-18.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 18,
          "default": 4
        }
      },
      "TeamFilterQuery": {
        "name": "team",
        "in": "query",
        "required": false,
        "description": "Team code, e.g. `BUF`. Either spelling works for LA/LAR, WSH/WAS and JAC/JAX.",
        "schema": {
          "type": "string"
        },
        "example": "BUF"
      },
      "PositionFilterQuery": {
        "name": "position",
        "in": "query",
        "required": false,
        "description": "Filter by position.",
        "schema": {
          "$ref": "#/components/schemas/Position"
        }
      },
      "NewsDaysQuery": {
        "name": "days",
        "in": "query",
        "required": false,
        "description": "Lookback window in days, 1-90. Default depends on `impact`: injury 10, general 14, role 30, transaction 45, none 14.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 90
        }
      },
      "NewsLimitQuery": {
        "name": "limit",
        "in": "query",
        "required": false,
        "description": "Maximum headlines, 1-50.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 50,
          "default": 10
        }
      },
      "NewsImpactQuery": {
        "name": "impact",
        "in": "query",
        "required": false,
        "description": "Only headlines with this impact tag.",
        "schema": {
          "$ref": "#/components/schemas/NewsImpact"
        }
      },
      "WeekFilterQuery": {
        "name": "week",
        "in": "query",
        "required": false,
        "description": "Only this week (1-18).",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 18
        }
      },
      "PlayerIdQuery": {
        "name": "player_id",
        "in": "query",
        "required": false,
        "description": "Only this player, e.g. `Josh Allen#QB`.",
        "schema": {
          "type": "string"
        }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Invalid parameter.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Invalid limit parameter"
            }
          }
        }
      },
      "NotFound": {
        "description": "Player, season or data not found.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Player not found: Josh Allen#QB"
            }
          }
        }
      },
      "Conflict": {
        "description": "More than one player matches name and position; use player_id.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Multiple players found with name Mike Williams and position WR. Please use player_id for exact match."
            }
          }
        }
      },
      "BadGateway": {
        "description": "The scrape could not be queued.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Could not queue the scrape request. Please try again later."
            }
          }
        }
      },
      "InternalError": {
        "description": "Unexpected server error.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Internal server error"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "Missing or invalid API key.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Unauthorized"
            }
          }
        }
      },
      "Forbidden": {
        "description": "The endpoint needs a higher plan (`basic_required`, `pro_required`), or the key's subscription is not active (`subscription_paused`, `subscription_canceled`, `payment_failed`, `subscription_inactive`).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Player stats is not included in the free plan. ...",
              "reason": "basic_required",
              "required_plan": "basic",
              "upgrade_url": "https://www.gridirondata.com/pages/pricing.html",
              "free_plan_includes": [
                "/players (profile, team, injury status)",
                "/projections/7 (season 2026)"
              ]
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "A rate limit or quota was hit. `reason` is `rate_limited` (back off for a minute), `quota_exceeded` or `free_monthly_quota` (monthly cap; `upgrade_url` links to pricing).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": "Rate limit exceeded: 30 requests per minute",
              "reason": "rate_limited",
              "upgrade_url": ""
            }
          }
        }
      }
    },
    "schemas": {
      "Position": {
        "type": "string",
        "enum": [
          "QB",
          "RB",
          "WR",
          "TE",
          "K",
          "DST"
        ]
      },
      "NewsImpact": {
        "type": "string",
        "enum": [
          "injury",
          "transaction",
          "role",
          "general"
        ]
      },
      "ScoringApplied": {
        "type": "string",
        "enum": [
          "ppr",
          "half",
          "standard"
        ]
      },
      "Error": {
        "type": "object",
        "description": "Error body. Plan, subscription and limit errors add a machine-readable `reason` and links.",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "string",
            "description": "Human-readable message."
          },
          "reason": {
            "type": "string",
            "description": "Machine-readable cause.",
            "examples": [
              "basic_required",
              "pro_required",
              "subscription_paused",
              "subscription_canceled",
              "payment_failed",
              "subscription_inactive",
              "rate_limited",
              "quota_exceeded",
              "free_monthly_quota"
            ]
          },
          "required_plan": {
            "type": "string",
            "enum": [
              "basic",
              "pro"
            ]
          },
          "upgrade_url": {
            "type": "string",
            "description": "Pricing page (empty for `rate_limited`)."
          },
          "free_plan_includes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "pro_includes": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "signup_url": {
            "type": "string"
          },
          "billing_url": {
            "type": "string"
          },
          "support_email": {
            "type": "string"
          },
          "pricing_url": {
            "type": "string"
          },
          "resume_url": {
            "type": "string"
          },
          "resume_steps": {
            "type": "array",
            "items": {
              "type": "string"
            }
          },
          "resumes_at": {
            "type": "string",
            "format": "date"
          },
          "dashboard_url": {
            "type": "string"
          }
        },
        "additionalProperties": true
      },
      "Health": {
        "type": "object",
        "required": [
          "status",
          "version",
          "default_season"
        ],
        "properties": {
          "status": {
            "type": "string",
            "const": "healthy"
          },
          "version": {
            "type": "string"
          },
          "default_season": {
            "type": "string",
            "description": "Season used when `season` is omitted."
          }
        }
      },
      "SeasonData": {
        "type": "object",
        "description": "One season of a player record. Fields present depend on the season and on what has been loaded.",
        "properties": {
          "team": {
            "type": "string"
          },
          "injury_status": {
            "type": "string"
          },
          "jersey_number": {
            "type": [
              "string",
              "number"
            ]
          },
          "weekly_stats": {
            "type": "object",
            "description": "Week number -> box-score row. Base `fantasy_points` is standard scoring, with `fantasy_points_ppr` / `fantasy_points_half` variants.",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "season_totals": {
            "type": "object",
            "description": "Season counting stats. Base `MISC_FPTS` (or `FPTS` for K/DST) is standard, with `_PPR` / `_HALF` variants.",
            "additionalProperties": true
          },
          "weekly_projections": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "season_projections": {
            "type": "object",
            "additionalProperties": true
          },
          "snap_counts": {
            "$ref": "#/components/schemas/SnapCounts"
          }
        },
        "additionalProperties": true
      },
      "SnapCounts": {
        "type": "object",
        "properties": {
          "weekly": {
            "type": "object",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": true
            }
          },
          "totals": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "MatchupBlock": {
        "type": "object",
        "properties": {
          "games_played": {
            "type": "number"
          },
          "games_with_projections": {
            "type": "number"
          },
          "total_actual": {
            "type": "number"
          },
          "total_projected": {
            "type": "number"
          },
          "avg_actual": {
            "type": "number"
          },
          "avg_projected": {
            "type": "number"
          },
          "avg_deviation": {
            "type": "number"
          },
          "avg_deviation_percentage": {
            "type": "number"
          }
        },
        "additionalProperties": true
      },
      "MatchupStats": {
        "allOf": [
          {
            "$ref": "#/components/schemas/MatchupBlock"
          },
          {
            "type": "object",
            "properties": {
              "by_season": {
                "type": "object",
                "additionalProperties": {
                  "$ref": "#/components/schemas/MatchupBlock"
                }
              },
              "last_game_season": {
                "type": "string"
              },
              "last_game_week": {
                "type": "number"
              }
            }
          }
        ]
      },
      "Player": {
        "type": "object",
        "description": "A player record. On the free plan only the profile fields, the current season's `team`/`injury_status`/`jersey_number` and a `free_plan` note are returned.",
        "required": [
          "player_id"
        ],
        "properties": {
          "player_id": {
            "type": "string",
            "examples": [
              "Josh Allen#QB"
            ]
          },
          "player_name": {
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          },
          "espn_player_id": {
            "type": [
              "string",
              "number"
            ]
          },
          "updated_at": {
            "type": "string"
          },
          "seasons": {
            "type": "object",
            "description": "Season year -> season data.",
            "additionalProperties": {
              "$ref": "#/components/schemas/SeasonData"
            }
          },
          "vs_opponent": {
            "type": "object",
            "description": "Team code -> career matchup history.",
            "additionalProperties": {
              "$ref": "#/components/schemas/MatchupStats"
            }
          },
          "free_plan": {
            "type": "string",
            "description": "Present only for free keys."
          }
        },
        "additionalProperties": true
      },
      "PlayerList": {
        "type": "object",
        "required": [
          "players",
          "count"
        ],
        "properties": {
          "players": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Player"
            }
          },
          "count": {
            "type": "integer"
          },
          "next_cursor": {
            "type": "string",
            "description": "Pass back as `cursor` for the next page. Absent on the last page."
          }
        }
      },
      "Projection": {
        "type": "object",
        "properties": {
          "player_id": {
            "type": "string"
          },
          "player_name": {
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          },
          "week": {
            "type": "string"
          },
          "projected_points": {
            "type": "number"
          },
          "projection_source": {
            "type": "string",
            "enum": [
              "weekly",
              "season_prorated",
              "bye",
              "unavailable"
            ]
          },
          "on_bye": {
            "type": "boolean"
          },
          "injury_status": {
            "$ref": "#/components/schemas/InjuryStatus"
          },
          "espn_player_id": {
            "type": [
              "integer",
              "null"
            ],
            "description": "ESPN athlete id, a stable numeric join key."
          },
          "injury_status_raw": {
            "type": [
              "string",
              "null"
            ],
            "description": "The status as stored by the source."
          },
          "available": {
            "type": [
              "boolean",
              "null"
            ],
            "description": "false when the player can't play this week (bye, OUT, IR, PUP, SUSPENDED, RESERVE or free agent). null for past weeks."
          },
          "unavailable_reason": {
            "type": "string",
            "enum": [
              "OUT",
              "IR",
              "PUP",
              "SUSPENDED",
              "RESERVE",
              "FREE_AGENT"
            ]
          },
          "projected_points_if_active": {
            "type": "number",
            "description": "For unavailable players: the projection they'd have if active."
          },
          "projected_stats": {
            "$ref": "#/components/schemas/StatLine",
            "description": "Pro plan and above."
          }
        }
      },
      "Projections": {
        "type": "object",
        "required": [
          "week",
          "season",
          "scoring",
          "projections",
          "count",
          "prorated_count"
        ],
        "properties": {
          "week": {
            "type": "string"
          },
          "season": {
            "type": "string"
          },
          "team": {
            "type": "string",
            "description": "Present when filtered by team."
          },
          "scoring": {
            "$ref": "#/components/schemas/ScoringApplied"
          },
          "projections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Projection"
            }
          },
          "count": {
            "type": "integer"
          },
          "prorated_count": {
            "type": "integer"
          },
          "unavailable_count": {
            "type": "integer"
          },
          "projected_stats": {
            "type": "string",
            "description": "Below Pro only: a note that per-stat projections need Pro, with the pricing link (the rows omit `projected_stats`)."
          }
        }
      },
      "PlayerStats": {
        "type": "object",
        "properties": {
          "player_id": {
            "type": "string"
          },
          "player_name": {
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          },
          "season": {
            "type": "string"
          },
          "scoring": {
            "$ref": "#/components/schemas/ScoringApplied"
          },
          "season_points": {
            "type": [
              "number",
              "null"
            ]
          },
          "weekly_stats": {
            "type": "object",
            "description": "Week number -> box-score row plus resolved `points`, `snap_pct` and `offense_snaps`.",
            "additionalProperties": {
              "type": "object",
              "additionalProperties": true,
              "properties": {
                "points": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "snap_pct": {
                  "type": [
                    "number",
                    "null"
                  ]
                },
                "offense_snaps": {
                  "type": [
                    "number",
                    "null"
                  ]
                }
              }
            }
          },
          "season_totals": {
            "type": "object",
            "additionalProperties": true
          },
          "snap_counts": {
            "$ref": "#/components/schemas/SnapCounts"
          },
          "injury_status": {
            "type": "string"
          }
        }
      },
      "SnapWeek": {
        "type": "object",
        "properties": {
          "week": {
            "type": "integer"
          },
          "offense_snaps": {
            "type": [
              "number",
              "null"
            ]
          },
          "snap_pct": {
            "type": [
              "number",
              "null"
            ]
          },
          "st_snaps": {
            "type": [
              "number",
              "null"
            ]
          },
          "st_pct": {
            "type": [
              "number",
              "null"
            ]
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          },
          "opponent": {
            "type": [
              "string",
              "null"
            ]
          },
          "defense_snaps": {
            "type": [
              "number",
              "null"
            ]
          },
          "defense_pct": {
            "type": [
              "number",
              "null"
            ]
          }
        }
      },
      "Snaps": {
        "type": "object",
        "properties": {
          "player_id": {
            "type": "string"
          },
          "player_name": {
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          },
          "season": {
            "type": "string"
          },
          "games": {
            "type": "integer"
          },
          "games_with_snaps": {
            "type": "integer"
          },
          "season_snap_pct_avg": {
            "type": [
              "number",
              "null"
            ]
          },
          "recent_snap_pct_avg": {
            "type": [
              "number",
              "null"
            ]
          },
          "trend_weeks": {
            "type": "integer"
          },
          "trend_delta": {
            "type": [
              "number",
              "null"
            ]
          },
          "totals": {
            "type": "object",
            "additionalProperties": true
          },
          "weekly_snaps": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/SnapWeek"
            }
          },
          "source": {
            "type": "string"
          }
        }
      },
      "Deviations": {
        "type": "object",
        "properties": {
          "player_id": {
            "type": "string"
          },
          "player_name": {
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "season": {
            "type": "string"
          },
          "avg_deviation": {
            "type": "number"
          },
          "weeks_analyzed": {
            "type": "integer"
          },
          "weekly_deviations": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "week": {
                  "type": "integer"
                },
                "actual_points": {
                  "type": "number"
                },
                "projected_points": {
                  "type": "number"
                },
                "deviation": {
                  "type": "number"
                },
                "deviation_percentage": {
                  "type": "number"
                },
                "opponent": {
                  "type": "string"
                }
              }
            }
          }
        }
      },
      "Matchup": {
        "type": "object",
        "properties": {
          "player_id": {
            "type": "string"
          },
          "player_name": {
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          },
          "opponent": {
            "type": "string"
          },
          "stats": {
            "$ref": "#/components/schemas/MatchupStats"
          }
        }
      },
      "Link": {
        "type": "object",
        "properties": {
          "method": {
            "type": "string"
          },
          "href": {
            "type": "string",
            "format": "uri"
          },
          "description": {
            "type": "string"
          }
        }
      },
      "NewsItem": {
        "type": "object",
        "properties": {
          "headline": {
            "type": "string"
          },
          "summary": {
            "type": "string"
          },
          "impact": {
            "$ref": "#/components/schemas/NewsImpact"
          },
          "source": {
            "type": "string"
          },
          "url": {
            "type": "string"
          },
          "published_at": {
            "type": "string"
          }
        }
      },
      "PlayerNews": {
        "type": "object",
        "required": [
          "player_id",
          "days",
          "count",
          "news"
        ],
        "properties": {
          "player_id": {
            "type": "string"
          },
          "days": {
            "type": "integer"
          },
          "count": {
            "type": "integer"
          },
          "news": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/NewsItem"
            }
          },
          "message": {
            "type": "string",
            "description": "Present when there is no news."
          },
          "request_scrape": {
            "allOf": [
              {
                "$ref": "#/components/schemas/Link"
              }
            ],
            "description": "Present when there is no news: POST here to queue a scrape."
          }
        }
      },
      "LatestNews": {
        "type": "object",
        "required": [
          "since",
          "until",
          "count",
          "filters",
          "news"
        ],
        "properties": {
          "since": {
            "type": "string"
          },
          "until": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "filters": {
            "type": "object",
            "properties": {
              "impact": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "position": {
                "type": "array",
                "items": {
                  "type": "string"
                }
              },
              "team": {
                "type": "string"
              }
            }
          },
          "news": {
            "type": "array",
            "items": {
              "allOf": [
                {
                  "$ref": "#/components/schemas/NewsItem"
                },
                {
                  "type": "object",
                  "properties": {
                    "player_id": {
                      "type": "string"
                    },
                    "player_name": {
                      "type": "string"
                    },
                    "position": {
                      "type": "string"
                    },
                    "team": {
                      "type": [
                        "string",
                        "null"
                      ]
                    }
                  }
                }
              ]
            }
          },
          "next_cursor": {
            "type": "string",
            "description": "Pass back as `cursor` for the next page."
          },
          "teaser": {
            "type": "boolean",
            "description": "Demo-key preview only."
          },
          "message": {
            "type": "string"
          },
          "upgrade_url": {
            "type": "string"
          }
        }
      },
      "NewsScrapeAccepted": {
        "type": "object",
        "properties": {
          "player_id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "const": "accepted"
          },
          "message": {
            "type": "string"
          },
          "poll": {
            "$ref": "#/components/schemas/Link"
          }
        }
      },
      "InjuryChanges": {
        "type": "object",
        "required": [
          "season",
          "since",
          "until",
          "count",
          "changes",
          "next_since",
          "truncated"
        ],
        "properties": {
          "season": {
            "type": "string"
          },
          "since": {
            "type": "string"
          },
          "until": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "changes": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "player_id": {
                  "type": "string"
                },
                "player_name": {
                  "type": "string"
                },
                "position": {
                  "type": "string"
                },
                "team": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "previous_status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "status": {
                  "type": [
                    "string",
                    "null"
                  ]
                },
                "detected_at": {
                  "type": "string"
                }
              }
            }
          },
          "next_since": {
            "type": "string",
            "description": "Pass back as `since` to poll for only new changes."
          },
          "last_checked_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "truncated": {
            "type": "boolean"
          },
          "note": {
            "type": "string",
            "description": "Present when `since` was clamped to the 14-day window."
          }
        }
      },
      "RosProjection": {
        "type": "object",
        "properties": {
          "player_id": {
            "type": "string"
          },
          "player_name": {
            "type": "string"
          },
          "position": {
            "type": "string"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          },
          "bye_week": {
            "type": [
              "integer",
              "null"
            ]
          },
          "games_remaining": {
            "type": "integer"
          },
          "remaining_weeks": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "ros_points": {
            "type": "number"
          },
          "points_per_game": {
            "type": "number"
          },
          "rate_source": {
            "type": "string",
            "description": "Where the per-game rate came from (a weekly projection, or season projection / 17)."
          },
          "rate_week": {
            "type": [
              "integer",
              "null"
            ]
          },
          "weekly_projection_weeks": {
            "type": "array",
            "items": {
              "type": "integer"
            },
            "description": "Remaining weeks that used a published weekly projection."
          },
          "method": {
            "type": "string",
            "enum": [
              "weekly_sum",
              "per_game_rate"
            ]
          },
          "injury_status": {
            "type": "string"
          },
          "projected_stats": {
            "type": "object",
            "additionalProperties": {
              "type": "number"
            },
            "description": "Present only when weekly projections carry stat fields."
          }
        }
      },
      "RosProjections": {
        "type": "object",
        "properties": {
          "season": {
            "type": "string"
          },
          "current_week": {
            "type": "integer"
          },
          "position": {
            "type": "string"
          },
          "team": {
            "type": "string"
          },
          "scoring": {
            "type": "string"
          },
          "method": {
            "type": "string",
            "enum": [
              "weekly_sum",
              "per_game_rate"
            ]
          },
          "method_note": {
            "type": "string"
          },
          "count": {
            "type": "integer"
          },
          "projections": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/RosProjection"
            }
          }
        }
      },
      "Game": {
        "type": "object",
        "properties": {
          "week": {
            "type": "integer"
          },
          "game_id": {
            "type": "string"
          },
          "kickoff_utc": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          },
          "home": {
            "type": "string"
          },
          "away": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "scheduled",
              "in_progress",
              "final"
            ]
          },
          "home_score": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Set once the game is final."
          },
          "away_score": {
            "type": [
              "integer",
              "null"
            ]
          },
          "odds": {
            "description": "Pro plan and above; omitted below Pro.",
            "$ref": "#/components/schemas/Odds"
          }
        }
      },
      "Schedule": {
        "type": "object",
        "properties": {
          "season": {
            "type": "string"
          },
          "week": {
            "type": "integer"
          },
          "team": {
            "type": "string"
          },
          "bye_week": {
            "type": [
              "integer",
              "null"
            ],
            "description": "Only when `team` is given."
          },
          "count": {
            "type": "integer"
          },
          "games": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Game"
            }
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          },
          "source": {
            "type": "string"
          },
          "odds": {
            "type": "string",
            "description": "Below Pro only: a note that betting lines need Pro, with the pricing link (the games omit `odds`)."
          }
        }
      },
      "Byes": {
        "type": "object",
        "properties": {
          "season": {
            "type": "string"
          },
          "byes": {
            "type": "object",
            "additionalProperties": {
              "type": "integer"
            },
            "description": "Team abbreviation -> bye week."
          },
          "by_week": {
            "type": "object",
            "additionalProperties": {
              "type": "array",
              "items": {
                "type": "string"
              }
            },
            "description": "Week -> teams on bye."
          },
          "source": {
            "type": "string"
          }
        }
      },
      "InjuryStatus": {
        "type": "string",
        "enum": [
          "ACTIVE",
          "QUESTIONABLE",
          "DOUBTFUL",
          "OUT",
          "IR",
          "PUP",
          "SUSPENDED",
          "RESERVE",
          "UNKNOWN"
        ],
        "description": "Standardized status. Every stored spelling maps to one value: Healthy/ACTIVE -> ACTIVE, IR/INJURY_RESERVE/IR-R -> IR, PUP-R -> PUP, RESERVE-DNR -> RESERVE. The stored string is in `injury_status_raw`."
      },
      "StatLine": {
        "type": "object",
        "description": "Per-game stat projection. Season projection / 17, scaled to the weekly points projection when one exists; all zero for bye or unavailable rows.",
        "properties": {
          "pass_att": {
            "type": "number"
          },
          "pass_cmp": {
            "type": "number"
          },
          "pass_yds": {
            "type": "number"
          },
          "pass_td": {
            "type": "number"
          },
          "pass_int": {
            "type": "number"
          },
          "rush_att": {
            "type": "number"
          },
          "rush_yds": {
            "type": "number"
          },
          "rush_td": {
            "type": "number"
          },
          "rec": {
            "type": "number"
          },
          "rec_yds": {
            "type": "number"
          },
          "rec_td": {
            "type": "number"
          }
        }
      },
      "Odds": {
        "type": [
          "object",
          "null"
        ],
        "description": "Pre-game betting line from the ESPN scoreboard (DraftKings), kept after kickoff as the closing line.",
        "properties": {
          "provider": {
            "type": [
              "string",
              "null"
            ]
          },
          "spread_home": {
            "type": [
              "number",
              "null"
            ],
            "description": "Negative when the home team is favored."
          },
          "over_under": {
            "type": [
              "number",
              "null"
            ]
          },
          "home_moneyline": {
            "type": [
              "integer",
              "null"
            ]
          },
          "away_moneyline": {
            "type": [
              "integer",
              "null"
            ]
          },
          "implied_home_points": {
            "type": [
              "number",
              "null"
            ],
            "description": "(over_under - spread_home) / 2"
          },
          "implied_away_points": {
            "type": [
              "number",
              "null"
            ],
            "description": "(over_under + spread_home) / 2"
          },
          "captured_at": {
            "type": [
              "string",
              "null"
            ],
            "format": "date-time"
          }
        }
      },
      "PropLine": {
        "type": "object",
        "properties": {
          "line": {
            "type": "number"
          },
          "p_over": {
            "type": "number"
          },
          "p_under": {
            "type": "number"
          },
          "p_push": {
            "type": "number",
            "description": "Non-zero only for whole-number lines."
          },
          "fair_odds_over": {
            "type": [
              "integer",
              "null"
            ],
            "description": "No-vig American odds."
          },
          "fair_odds_under": {
            "type": [
              "integer",
              "null"
            ]
          }
        }
      },
      "PropProbability": {
        "type": "object",
        "properties": {
          "player_id": {
            "type": "string"
          },
          "espn_player_id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "player_name": {
            "type": "string"
          },
          "position": {
            "$ref": "#/components/schemas/Position"
          },
          "team": {
            "type": [
              "string",
              "null"
            ]
          },
          "season": {
            "type": "string"
          },
          "week": {
            "type": "integer"
          },
          "stat": {
            "type": "string",
            "enum": [
              "pass_att",
              "pass_cmp",
              "pass_yds",
              "pass_td",
              "pass_int",
              "rush_att",
              "rush_yds",
              "rush_td",
              "rec",
              "rec_yds",
              "rec_td"
            ]
          },
          "injury_status": {
            "$ref": "#/components/schemas/InjuryStatus"
          },
          "injury_status_raw": {
            "type": [
              "string",
              "null"
            ]
          },
          "available": {
            "type": "boolean",
            "description": "false on a bye or when unavailable; every p_over is then 0."
          },
          "unavailable_reason": {
            "type": "string"
          },
          "game": {
            "type": [
              "object",
              "null"
            ],
            "properties": {
              "game_id": {
                "type": "string"
              },
              "kickoff_utc": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "home": {
                "type": "boolean"
              },
              "opponent": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "odds": {
                "$ref": "#/components/schemas/Odds"
              },
              "implied_team_points": {
                "type": [
                  "number",
                  "null"
                ]
              }
            }
          },
          "projection": {
            "type": "object",
            "properties": {
              "mean": {
                "type": "number"
              },
              "sd": {
                "type": "number"
              },
              "distribution": {
                "type": "string",
                "enum": [
                  "normal",
                  "gamma",
                  "poisson"
                ]
              },
              "season_projection_per_game": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "games_played": {
                "type": "integer"
              },
              "season_average": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "recent_games": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "week": {
                      "type": "integer"
                    },
                    "opponent": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "value": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "lines": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/PropLine"
            }
          },
          "model": {
            "type": "object",
            "description": "Method, fitted dispersion and the walk-forward backtest (Brier score and calibration) for this stat."
          },
          "disclaimer": {
            "type": "string"
          }
        }
      },
      "DefenseRow": {
        "type": "object",
        "properties": {
          "team": {
            "type": "string"
          },
          "rank": {
            "type": "integer",
            "description": "1 = allows the most fantasy points per game to the position."
          },
          "games": {
            "type": "integer"
          },
          "fantasy_points_allowed_per_game": {
            "type": "number"
          },
          "per_game": {
            "type": "object",
            "description": "Stats allowed per game: pass_yds, pass_td, rush_yds, rec, rec_yds, targets, ...",
            "additionalProperties": {
              "type": "number"
            }
          }
        }
      },
      "DefenseVsPosition": {
        "type": "object",
        "properties": {
          "season": {
            "type": "string"
          },
          "scoring": {
            "$ref": "#/components/schemas/ScoringApplied"
          },
          "weeks": {
            "type": "array",
            "items": {
              "type": "integer"
            }
          },
          "position": {
            "type": "string"
          },
          "defenses": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DefenseRow"
            }
          },
          "by_position": {
            "type": "object",
            "description": "Without `position`: QB, RB, WR and TE lists.",
            "additionalProperties": {
              "type": "array",
              "items": {
                "$ref": "#/components/schemas/DefenseRow"
              }
            }
          },
          "rank_meaning": {
            "type": "string"
          },
          "source": {
            "type": "string"
          },
          "updated_at": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      }
    }
  }
}
