{
  "openapi": "3.1.0",
  "info": {
    "title": "Krok Odds API",
    "version": "1.0.0",
    "summary": "Australian sports odds, racing, and betting-opportunity data API.",
    "description": "Live prices from 140+ Australian bookmakers across 181 sport and league keys, plus a full Australian racing stack (thoroughbred, harness, greyhound), built-in arbitrage/+EV/middles/low-holds scanners, player props, AI tips, closing-line archives and historical data back to January 2024.\n\nAll endpoints return a consistent `{success, data, meta}` JSON envelope. Authenticate with an API key via the `X-API-Key` header (or `?api_key=`/`?apikey=` query param). `/status` is the only endpoint that does not require a key.\n\nFree tier: 50 credits/month, 5 req/min, 100-row cap per request. API tier (A$49/mo): 50,000 credits/month included, 500 req/min, up to 500-5000 rows per request depending on endpoint, plus optional pay-as-you-go overage beyond the included 50,000 credits.",
    "contact": {
      "name": "Krok Odds",
      "url": "https://krokodds.com.au/api-access",
      "email": "support@krokodds.com.au"
    },
    "termsOfService": "https://krokodds.com.au/terms",
    "license": {
      "name": "Proprietary \u2014 commercial API, see Terms of Service",
      "url": "https://krokodds.com.au/terms"
    }
  },
  "servers": [
    {
      "url": "https://krokodds.com.au/api/v1",
      "description": "Production"
    }
  ],
  "tags": [
    {
      "name": "Gameday",
      "description": "Per-event head-to-head odds, alt lines, best prices, player props, live state and AI summaries."
    },
    {
      "name": "Opportunities",
      "description": "Built-in scanners: arbitrage, positive EV, middles, low-holds, player props and SGM picks."
    },
    {
      "name": "Racing",
      "description": "Australian thoroughbred, harness and greyhound racing \u2014 meetings, form, ratings, movers, arbs, tote and results."
    },
    {
      "name": "Odds Feed",
      "description": "Raw scraped per-bookmaker odds and racing prices across 140+ AU bookmakers."
    },
    {
      "name": "Sports",
      "description": "Sport reference lists, team form, standings, injuries and weather impact."
    },
    {
      "name": "Tips & Predictions",
      "description": "AI-generated tips, confidence scores and accuracy tracking."
    },
    {
      "name": "Tennis",
      "description": "ATP/WTA rankings, fixtures, match results, Elo and surface stats."
    },
    {
      "name": "Historical",
      "description": "Deep historical archives \u2014 player/team game logs, shot charts, AFL/NRL results. Paid tier only."
    },
    {
      "name": "Reference",
      "description": "Static lookup data \u2014 players, venues, headshots, team logos, metadata."
    },
    {
      "name": "Prediction Markets",
      "description": "Polymarket / Kalshi sentiment and universal prediction-market feeds."
    },
    {
      "name": "Other Sports",
      "description": "Cricket, cycling, esports, F1, golf, MMA, boxing, NFL and soccer coverage."
    },
    {
      "name": "Infrastructure",
      "description": "Account, billing, bulk export, streaming, webhooks and API health."
    },
    {
      "name": "Additional Data",
      "description": "Odds history, steam moves, player prop stats, injuries, weather and other supporting feeds."
    }
  ],
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "ApiKeyQuery": []
    },
    {
      "ApiKeyQueryAlt": []
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Your Krok Odds API key. Get one free at https://krokodds.com.au/api-access"
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "api_key",
        "description": "API key as a query parameter (alternative to the header)."
      },
      "ApiKeyQueryAlt": {
        "type": "apiKey",
        "in": "query",
        "name": "apikey",
        "description": "API key as a query parameter, no underscore (alternative form)."
      },
      "SessionAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "__session",
        "description": "Firebase session cookie used by the krokodds.com.au dashboard. Not usable by third-party API integrations \u2014 dashboard-only."
      }
    },
    "schemas": {
      "SuccessEnvelope": {
        "type": "object",
        "additionalProperties": true,
        "properties": {
          "success": {
            "type": "boolean",
            "const": true
          },
          "data": {
            "description": "Array (or, for a few endpoints, single object) of result rows."
          },
          "meta": {
            "type": "object",
            "properties": {
              "count": {
                "type": "integer"
              },
              "tier": {
                "type": "string",
                "enum": [
                  "free",
                  "api"
                ]
              },
              "timestamp": {
                "type": "string",
                "format": "date-time"
              },
              "rate_limit": {
                "type": "object",
                "properties": {
                  "limit": {
                    "type": "integer"
                  },
                  "remaining": {
                    "type": "integer"
                  },
                  "reset": {
                    "type": "string",
                    "description": "ISO date-time, or empty string if unset"
                  }
                }
              }
            },
            "additionalProperties": true
          }
        },
        "required": [
          "success",
          "data"
        ]
      },
      "ErrorEnvelope": {
        "type": "object",
        "properties": {
          "success": {
            "type": "boolean",
            "const": false
          },
          "error": {
            "type": "string"
          },
          "message": {
            "type": "string"
          }
        },
        "required": [
          "success",
          "error"
        ]
      }
    }
  },
  "paths": {
    "/gameday/alt-lines": {
      "get": {
        "tags": [
          "Gameday"
        ],
        "summary": "Alternate-line (non-standard spread/total) odds for events, best price per line plus the full raw line list.",
        "description": "Alternate-line (non-standard spread/total) odds for events, best price per line plus the full raw line list.\n\n**Note:** No creditCost concept in this route \u2014 auth is monthly-usage based (meta.rate_limit), not per-request credits. event_id does a direct Supabase doc lookup instead of a query. No results returns data: [] (HTTP 200), not 404. Response cached via unstable_cache 300s (free) / 60s (api). CORS open (Access-Control-Allow-Origin: *).",
        "operationId": "get_gameday_alt_lines",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (also accepts `sport`), e.g. `basketball_nba`. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Return alt-lines for a single event only (direct doc lookup).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Filter lines/best-by-line to a single market key, e.g. `spreads`. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max events returned. Clamped to 50 (free) / 2000 (api).",
            "schema": {
              "type": "number",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "event_id": "basketball_nba_20260903_lal_bos",
                      "sport_key": "basketball_nba",
                      "sport_title": "NBA",
                      "home_team": "Boston Celtics",
                      "away_team": "Los Angeles Lakers",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "best_by_line": [
                        {
                          "key": "spreads|-4.5",
                          "market_key": "spreads",
                          "selection": "Boston Celtics -4.5",
                          "point": -4.5,
                          "bookmaker": "sportsbet",
                          "odds": 1.9,
                          "bet_link": "https://www.sportsbet.com.au/..."
                        }
                      ],
                      "all_lines": [
                        {
                          "market_key": "spreads",
                          "selection": "Boston Celtics -4.5",
                          "point": -4.5,
                          "bookmaker": "ladbrokes",
                          "odds": 1.87,
                          "bet_link": "https://www.ladbrokes.com.au/..."
                        }
                      ],
                      "line_count": 12,
                      "updated_at": "2026-09-03T10:05:00Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": "basketball_nba",
                    "event_id": null,
                    "market": null,
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/gameday/best-prices": {
      "get": {
        "tags": [
          "Gameday"
        ],
        "summary": "Best available price per market/selection per event, aggregated across bookmakers.",
        "description": "Best available price per market/selection per event, aggregated across bookmakers.\n\n**Note:** Cache 120s (free) / 30s (api) \u2014 tighter than most gameday routes since best prices move fastest. `bookmaker_count` reflects how many books were quoting that exact price, not total books on the market.",
        "operationId": "get_gameday_best_prices",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (also accepts `sport`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Filter to a single event via query (not a direct doc get, unlike alt-lines).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Filter the prices array to a single market. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max events returned. Clamped to 50 (free) / 2000 (api).",
            "schema": {
              "type": "number",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "basketball_nba_20260903_lal_bos",
                      "event_id": "basketball_nba_20260903_lal_bos",
                      "sport_key": "basketball_nba",
                      "home_team": "Boston Celtics",
                      "away_team": "Los Angeles Lakers",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "prices": [
                        {
                          "market": "h2h",
                          "selection": "Boston Celtics",
                          "point": null,
                          "price": 1.65,
                          "bookmaker": "sportsbet",
                          "bookmaker_title": "Sportsbet",
                          "bookmaker_count": 9,
                          "bet_link": "https://www.sportsbet.com.au/..."
                        }
                      ],
                      "updated_at": "2026-09-03T10:04:30Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "markets_filter": null,
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/gameday/event/{id}": {
      "get": {
        "tags": [
          "Gameday"
        ],
        "summary": "Bundled single-event fetch \u2014 pulls summary, h2h, best_prices, alt_lines, odds_history, live and props in one call, selec",
        "description": "Bundled single-event fetch \u2014 pulls summary, h2h, best_prices, alt_lines, odds_history, live and props in one call, selectable via `parts`.\n\n**Note:** 400 error if sport_key is missing while parts includes h2h or live. Each requested part is read via Promise.allSettled, so a single collection failure returns null for that part rather than failing the whole request. `parts` output shapes vary per sub-collection (not normalized to a common schema) \u2014 summary/h2h/best_prices/alt_lines/odds_history/live are single objects (or null), props is an array.",
        "operationId": "get_gameday_event_id",
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "description": "Gameday event id (path segment), e.g. `basketball_nba_20260903_lal_bos`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Required only if `parts` includes h2h or live (they key off `${sportKey}_${eventId}`); also accepts `sport`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "parts",
            "in": "query",
            "required": false,
            "description": "Comma-separated subset of parts to fetch; invalid values are silently dropped.",
            "schema": {
              "type": "string",
              "default": "all of: summary,h2h,best_prices,alt_lines,props,odds_history,live"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "event_id": "basketball_nba_20260903_lal_bos",
                    "sport_key": "basketball_nba",
                    "summary": {
                      "headline": "Celtics look to cover at home against shorthanded Lakers",
                      "summary": "Boston is 8-2 ATS in its last 10 home games...",
                      "key_storylines": [
                        "Lakers missing starting PG",
                        "Celtics on a 5-game win streak"
                      ],
                      "generated_at": "2026-09-03T08:00:00Z"
                    },
                    "h2h": {
                      "summary": {
                        "total_meetings": 12,
                        "home_wins": 7,
                        "away_wins": 5
                      },
                      "last_meetings": [
                        {
                          "date": "2026-02-14",
                          "home_team": "Boston Celtics",
                          "away_team": "Los Angeles Lakers",
                          "home_score": 118,
                          "away_score": 109
                        }
                      ]
                    },
                    "best_prices": {
                      "id": "basketball_nba_20260903_lal_bos",
                      "prices": []
                    },
                    "alt_lines": {
                      "bestByLine": {},
                      "allLines": []
                    },
                    "odds_history": null,
                    "live": null,
                    "props": [
                      {
                        "id": "prop123",
                        "playerName": "Jayson Tatum",
                        "marketKey": "player_points",
                        "line": 27.5,
                        "odds": 1.9,
                        "bookmaker": "sportsbet",
                        "bet_link": "https://..."
                      }
                    ]
                  },
                  "meta": {
                    "tier": "free",
                    "parts": [
                      "summary",
                      "h2h",
                      "best_prices",
                      "alt_lines",
                      "props",
                      "odds_history",
                      "live"
                    ],
                    "event_id": "basketball_nba_20260903_lal_bos",
                    "sport_key": "basketball_nba",
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/gameday/h2h": {
      "get": {
        "tags": [
          "Gameday"
        ],
        "summary": "Head-to-head historical matchup summaries and recent meeting results between the two teams in an event.",
        "description": "Head-to-head historical matchup summaries and recent meeting results between the two teams in an event.\n\n**Note:** `team` filter is applied client-side after fetching up to limit*2 rows from Supabase, then sliced to `limit` \u2014 so a narrow team filter combined with a high limit can under-return relative to what actually exists. Draws is always 0 for sports without draws.",
        "operationId": "get_gameday_h2h",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (also accepts `sport`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Filter to a single event.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Substring match (case-insensitive) against either home_team or away_team.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned. Clamped to 50 (free) / 2000 (api).",
            "schema": {
              "type": "number",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "basketball_nba_20260903_lal_bos",
                      "event_id": "basketball_nba_20260903_lal_bos",
                      "sport_key": "basketball_nba",
                      "sport_title": "NBA",
                      "home_team": "Boston Celtics",
                      "away_team": "Los Angeles Lakers",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "summary": {
                        "total_meetings": 12,
                        "home_wins": 7,
                        "away_wins": 5,
                        "draws": 0,
                        "home_win_pct": 58.3,
                        "away_win_pct": 41.7
                      },
                      "last_meetings": [
                        {
                          "date": "2026-02-14",
                          "home_team": "Boston Celtics",
                          "away_team": "Los Angeles Lakers",
                          "home_score": 118,
                          "away_score": 109,
                          "venue": "TD Garden"
                        }
                      ],
                      "updated_at": "2026-09-03T09:00:00Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/gameday/live": {
      "get": {
        "tags": [
          "Gameday"
        ],
        "summary": "Live/in-progress and recently-finished game states \u2014 scores, status (pre/live/final), completion flag.",
        "description": "Live/in-progress and recently-finished game states \u2014 scores, status (pre/live/final), completion flag.\n\n**Note:** Shortest cache window of the gameday group: 60s free / 15s api, reflecting the need for near-real-time scores. Ordered commence_time descending (most recently started first) unlike other gameday routes which order ascending.",
        "operationId": "get_gameday_live",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (also accepts `sport`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Filter to a single event.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter to a specific game status; other values are ignored (no filter applied).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned. Clamped to 50 (free) / 2000 (api).",
            "schema": {
              "type": "number",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "event_id": "basketball_nba_20260903_lal_bos",
                      "sport_key": "basketball_nba",
                      "home_team": "Boston Celtics",
                      "away_team": "Los Angeles Lakers",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "home_score": 54,
                      "away_score": 49,
                      "completed": false,
                      "status": "live",
                      "last_update": "2026-09-03T23:45:12Z",
                      "updated_at": "2026-09-03T23:45:12Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": "basketball_nba",
                    "event_id": null,
                    "status": "live",
                    "timestamp": "2026-09-03T23:45:20Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/gameday/props": {
      "get": {
        "tags": [
          "Gameday"
        ],
        "summary": "Player prop best-odds, aggregated across bookmakers and grouped by event; underlying storage is one row per (bookmaker, ",
        "description": "Player prop best-odds, aggregated across bookmakers and grouped by event; underlying storage is one row per (bookmaker, market, outcome) quote.\n\n**Note:** `limit` counts events, but rows are per-prop-quote, so the route over-fetches up to limit*40 (max 2000) prop rows to try to cover ~limit events after grouping \u2014 high-volume slates can still under-return events. Each prop is a best-odds aggregate across books (bet_link points to the best-odds book only, other books in book_count have no exposed URL). Cache floor enforced at >=300s regardless of declared tier value (see CLAUDE.md Firestore-read-cost rule).",
        "operationId": "get_gameday_props",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (also accepts `sport`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Filter to a single event.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Filter to a single prop market key, e.g. `player_points`. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player_slug",
            "in": "query",
            "required": false,
            "description": "Filter to a single player by slugified name (lowercase, hyphenated).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max EVENTS returned (not prop rows). Clamped to 50 (free) / 2000 (api).",
            "schema": {
              "type": "number",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "event_id": "basketball_nba_20260903_lal_bos",
                      "sport_key": "basketball_nba",
                      "sport_title": "NBA",
                      "home_team": "Boston Celtics",
                      "away_team": "Los Angeles Lakers",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "props": [
                        {
                          "market_key": "player_points",
                          "player_name": "Jayson Tatum",
                          "player_slug": "jayson-tatum",
                          "line": 27.5,
                          "side": "over",
                          "best_odds": 1.95,
                          "best_bookmaker": "sportsbet",
                          "book_count": 6,
                          "bet_link": "https://www.sportsbet.com.au/..."
                        }
                      ],
                      "prop_count": 1,
                      "updated_at": "2026-09-03T10:00:00Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": "basketball_nba",
                    "event_id": null,
                    "market": null,
                    "player_slug": null,
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/gameday/summaries": {
      "get": {
        "tags": [
          "Gameday"
        ],
        "summary": "AI-generated pre-game summaries/headlines/storylines and best-market snapshot per event.",
        "description": "AI-generated pre-game summaries/headlines/storylines and best-market snapshot per event.\n\n**Note:** Longest cache window of the gameday group: 600s free / 120s api (summaries change slowly, tied to the CLAUDE.md Firestore-read-cost 600s-window rule for gameday-data.ts). event_id does a direct doc get; otherwise a sport-filtered query ordered by commence_time ascending.",
        "operationId": "get_gameday_summaries",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (also accepts `sport`).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Direct doc lookup for a single event's summary.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned. Clamped to 50 (free) / 2000 (api).",
            "schema": {
              "type": "number",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "event_id": "basketball_nba_20260903_lal_bos",
                      "sport_key": "basketball_nba",
                      "sport_title": "NBA",
                      "home_team": "Boston Celtics",
                      "away_team": "Los Angeles Lakers",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "summary": "Boston enters as 4.5-point favorites off a five-game win streak...",
                      "headline": "Celtics eye sixth straight win as Lakers battle injuries",
                      "key_storylines": [
                        "Lakers PG questionable",
                        "Celtics 8-2 ATS at home"
                      ],
                      "best_h2h": {
                        "selection": "Boston Celtics",
                        "odds": 1.65,
                        "bookmaker": "sportsbet"
                      },
                      "best_spread": {
                        "selection": "Boston Celtics -4.5",
                        "odds": 1.9,
                        "bookmaker": "ladbrokes"
                      },
                      "best_total": {
                        "selection": "Over 224.5",
                        "odds": 1.87,
                        "bookmaker": "sportsbet"
                      },
                      "generated_at": "2026-09-03T08:00:00Z",
                      "updated_at": "2026-09-03T08:00:00Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": "basketball_nba",
                    "event_id": null,
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/opportunities": {
      "get": {
        "tags": [
          "Opportunities"
        ],
        "summary": "Umbrella/legacy endpoint that dispatches to the same underlying data as the dedicated opportunity-type routes, selected ",
        "description": "Umbrella/legacy endpoint that dispatches to the same underlying data as the dedicated opportunity-type routes, selected via `type`.\n\n**Note:** Legacy/umbrella wrapper around `fetchOpportunitiesData` (src/lib/v1-opportunities-data.ts) \u2014 response shape per row depends on `type` and is not normalized to one schema across types. Response includes a static `endpoints` hint block pointing to unrelated racing endpoints (`/v1/racing/arbs`, `/v1/racing/movers`), which do not correspond to any of the 14 files audited here \u2014 likely stale/aspirational documentation baked into the payload. Rate-limit headers use `limit` (the row cap) rather than the usual free/api tier request cap seen on other routes.",
        "operationId": "get_opportunities",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Which opportunity category to fetch; `positive-ev` is aliased internally to `snipes`. Determines the feature-gate applied (snipes\u2192positive_ev, middles\u2192middles, racing\u2192racing, playerprops\u2192player_props; `all` has no per-type gate).",
            "schema": {
              "type": "string",
              "default": "all"
            }
          },
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport name; uppercased before matching.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minvalue",
            "in": "query",
            "required": false,
            "description": "Minimum opportunity value/edge threshold (also accepts `min_value`).",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "min_value",
            "in": "query",
            "required": false,
            "description": "Alias for minvalue.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned. Clamped to 100 (free) / 500 (api).",
            "schema": {
              "type": "number",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "snipe_abc123",
                      "sport": "NBA",
                      "sport_key": "basketball_nba",
                      "event": "Boston Celtics vs Los Angeles Lakers",
                      "market": "h2h",
                      "selection1": "Boston Celtics",
                      "bookmaker1": "sportsbet",
                      "odds1": 2.1,
                      "value": 6.4,
                      "commence_time": "2026-09-03T23:10:00Z"
                    }
                  ],
                  "meta": {
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "timestamp": "2026-09-03T10:06:00Z"
                  },
                  "endpoints": [
                    {
                      "path": "/v1/racing/arbs",
                      "description": "Australian racing arbitrage opportunities",
                      "parameters": [
                        "venue",
                        "race_type (T/H/G)",
                        "minedge",
                        "limit"
                      ]
                    },
                    {
                      "path": "/v1/racing/movers",
                      "description": "Racing steamers and drifters (significant market movers)",
                      "parameters": [
                        "venue",
                        "race_type (T/H/G)",
                        "movement_type (steamer|drifter)",
                        "min_movement",
                        "limit"
                      ]
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/opportunities/arbitrage": {
      "get": {
        "tags": [
          "Opportunities"
        ],
        "summary": "Cross-bookmaker arbitrage (surebet) opportunities \u2014 two legs that lock in guaranteed profit regardless of outcome.",
        "description": "Cross-bookmaker arbitrage (surebet) opportunities \u2014 two legs that lock in guaranteed profit regardless of outcome.\n\n**Note:** Supports keyset pagination via cursor/next_cursor and field projection via `fields`. Drops arbs for events that started >12h ago and rows whose freshness stamp is >5min old or with insane odds (isPriceFresh/hasSaneOdds). Betfair Exchange legs are stripped for hobby-equivalent (free) tier via filterExchangeForHobby. `status` (live/upcoming) is derived from commence_time, not stored. Cache 300s both tiers.",
        "operationId": "get_opportunities_arbitrage",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport (also accepts sport_key as fallback); uppercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minvalue",
            "in": "query",
            "required": false,
            "description": "Minimum arb value/edge % (also accepts min_value).",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "min_value",
            "in": "query",
            "required": false,
            "description": "Alias for minvalue.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Substring match against either leg's bookmaker (case-insensitive).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows per page. Clamped to 100 (free) / 10000 (api).",
            "schema": {
              "type": "number",
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque keyset-pagination cursor from a prior response's meta.next_cursor.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Field projection \u2014 restrict each row to only the named fields.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "arb_9f2c1a",
                      "event": "Boston Celtics vs Los Angeles Lakers",
                      "home_team": "Boston Celtics",
                      "away_team": "Los Angeles Lakers",
                      "sport": "NBA",
                      "sport_key": "basketball_nba",
                      "market": "h2h",
                      "selection1": "Boston Celtics",
                      "selection2": "Los Angeles Lakers",
                      "bookmaker1": "sportsbet",
                      "bookmaker2": "ladbrokes",
                      "odds1": 2.05,
                      "odds2": 2.15,
                      "bet_link1": "https://www.sportsbet.com.au/...",
                      "bet_link2": "https://www.ladbrokes.com.au/...",
                      "line": null,
                      "value": 2.35,
                      "instructions": "Stake $511.20 on Boston Celtics @ sportsbet, $488.80 on Los Angeles Lakers @ ladbrokes",
                      "tool_type": "surebet",
                      "status": "upcoming",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "updated_at": "2026-09-03T09:55:00Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "next_cursor": "eyJ2IjoyLjM1LCJpZCI6ImFyYl85ZjJjMWEifQ==",
                    "filters": {
                      "sport": null,
                      "sport_key": null,
                      "bookmaker": null,
                      "min_value": null
                    },
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/opportunities/low-holds": {
      "get": {
        "tags": [
          "Opportunities"
        ],
        "summary": "Two-way markets where the bookmaker overround (vig) across two books is unusually low, near break-even for bettors.",
        "description": "Two-way markets where the bookmaker overround (vig) across two books is unusually low, near break-even for bettors.\n\n**Note:** Fastest-refreshing opportunities route: cache 60s (free) / 15s (api) vs 300s for arbitrage/middles/positive-ev. Sorted ascending by hold percentage (lowest hold first). Same staleness (12h commence cutoff) and price-freshness (5min) filtering, plus filterExchangeForHobby, as arbitrage. Keyset pagination + field projection supported.",
        "operationId": "get_opportunities_low_holds",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport (also accepts sport_key fallback); uppercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minvalue",
            "in": "query",
            "required": false,
            "description": "Minimum value threshold (also accepts min_value); checks value/holdPct alias chain.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "min_value",
            "in": "query",
            "required": false,
            "description": "Alias for minvalue.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "max_hold",
            "in": "query",
            "required": false,
            "description": "Upper bound on hold percentage (holdPct or value).",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Substring match against either leg's bookmaker.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows per page. Clamped to 100 (free) / 10000 (api).",
            "schema": {
              "type": "number",
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque keyset-pagination cursor.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Field projection list.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "lowhold_7ab391",
                      "sport": "AFL",
                      "sport_key": "aussierules_afl",
                      "event": "Collingwood vs Essendon",
                      "home_team": "Collingwood",
                      "away_team": "Essendon",
                      "market": "h2h",
                      "commence_time": "2026-09-03T08:40:00Z",
                      "bookmaker1": "betright",
                      "selection1": "Collingwood",
                      "odds1": 1.87,
                      "bookmaker2": "tab",
                      "selection2": "Essendon",
                      "odds2": 2.05,
                      "bet_link1": "https://www.betright.com.au/...",
                      "bet_link2": "https://www.tab.com.au/...",
                      "line": null,
                      "value": 1.8,
                      "hold_pct": 1.8,
                      "profit_percent": 1.8,
                      "instructions": "Combined hold across both books is 1.8% \u2014 near break-even.",
                      "tool_type": "low_hold",
                      "status": "upcoming",
                      "updated_at": "2026-09-03T09:50:00Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "next_cursor": null,
                    "filters": {
                      "sport": "AFL",
                      "sport_key": null,
                      "bookmaker": null,
                      "min_value": null,
                      "max_hold": null
                    },
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/opportunities/middles": {
      "get": {
        "tags": [
          "Opportunities"
        ],
        "summary": "Middle opportunities \u2014 two different lines/points across bookmakers where both bets can win if the result lands between ",
        "description": "Middle opportunities \u2014 two different lines/points across bookmakers where both bets can win if the result lands between them.\n\n**Note:** Only middles route without a `filters` block in meta (present on arbitrage/low-holds/positive-ev but not middles). Same 12h staleness + 5min freshness filtering as arbitrage. No filterExchangeForHobby call here (unlike arbitrage/low-holds), so Betfair Exchange legs are NOT stripped for free tier on this route. Cache 300s both tiers.",
        "operationId": "get_opportunities_middles",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport (also accepts sport_key fallback); uppercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minvalue",
            "in": "query",
            "required": false,
            "description": "Minimum middle value (also accepts min_value).",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "min_value",
            "in": "query",
            "required": false,
            "description": "Alias for minvalue.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows per page. Clamped to 100 (free) / 10000 (api).",
            "schema": {
              "type": "number",
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque keyset-pagination cursor.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Field projection list.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "middle_44d1c9",
                      "event": "Sydney Swans vs Geelong Cats",
                      "home_team": "Sydney Swans",
                      "away_team": "Geelong Cats",
                      "sport": "AFL",
                      "sport_key": "aussierules_afl",
                      "market": "spreads",
                      "selection1": "Sydney Swans -6.5",
                      "selection2": "Geelong Cats +9.5",
                      "bookmaker1": "sportsbet",
                      "bookmaker2": "pointsbet",
                      "odds1": 1.91,
                      "odds2": 1.95,
                      "bet_link1": "https://www.sportsbet.com.au/...",
                      "bet_link2": "https://www.pointsbet.com.au/...",
                      "value": 4.2,
                      "line1": -6.5,
                      "line2": 9.5,
                      "middle_window": 3,
                      "worst_case_loss": -12.5,
                      "best_case_profit": 87.5,
                      "instructions": "Bet Sydney -6.5 @ sportsbet and Geelong +9.5 @ pointsbet.",
                      "tool_type": "middle",
                      "status": "upcoming",
                      "commence_time": "2026-09-03T08:40:00Z",
                      "updated_at": "2026-09-03T09:52:00Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "next_cursor": null,
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/opportunities/player-props": {
      "get": {
        "tags": [
          "Opportunities"
        ],
        "summary": "Individual player-prop bets with a positive expected-value edge, optionally enriched with historical hit-rate stats.",
        "description": "Individual player-prop bets with a positive expected-value edge, optionally enriched with historical hit-rate stats.\n\n**Note:** `historical_stats` is only populated when include_stats=true AND tier !== 'free' \u2014 free-tier requests get `historical_stats: undefined` (omitted key) even if include_stats=true. Underlying query scans a 12h commence_time window (max 5000 rows) then sorts by ev_percentage in memory rather than at the DB level, because there's no index for a global ev_percentage sort (avoids Postgres statement timeout). No cursor/fields projection support (unlike arbitrage/low-holds/middles/positive-ev). Filters out rows where player_name looks like a market/selection label (regex-based artifact cleanup). Cache 60s free / 15s api.",
        "operationId": "get_opportunities_player_props",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport (also accepts sport_key fallback); lowercased (unlike other opportunities routes which uppercase).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Exact match against market_key, e.g. `player_points`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player",
            "in": "query",
            "required": false,
            "description": "Substring match against player_name (case-insensitive).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Substring match against bookmaker.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_ev",
            "in": "query",
            "required": false,
            "description": "Minimum ev_percentage threshold.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "include_stats",
            "in": "query",
            "required": false,
            "description": "When `true` (and tier is not free), joins in historical_stats (hit rates, streaks) per prop from player_props_stats.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned. Clamped to 100 (free) / 10000 (api).",
            "schema": {
              "type": "number",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "prop_e8a112",
                      "event_id": "basketball_nba_20260903_lal_bos",
                      "event": "Boston Celtics vs Los Angeles Lakers",
                      "sport": "basketball_nba",
                      "sport_key": "basketball_nba",
                      "market_key": "player_points",
                      "player_name": "Jayson Tatum",
                      "line": 27.5,
                      "side": "over",
                      "odds": 1.95,
                      "bookmaker": "sportsbet",
                      "bet_link": "https://www.sportsbet.com.au/...",
                      "ev_percentage": 7.8,
                      "commence_time": "2026-09-03T23:10:00Z",
                      "historical_stats": {
                        "sample_size": 22,
                        "hit_rate_over": 0.68,
                        "hit_rate_under": 0.32,
                        "last_5_results": [
                          true,
                          true,
                          false,
                          true,
                          true
                        ],
                        "last_5_hit_rate": 0.8,
                        "streak": "over_2",
                        "line_bucket": "25-30",
                        "updated_at": "2026-09-02T22:00:00Z"
                      }
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 100,
                    "requested_limit": 100,
                    "filters": {
                      "sport": null,
                      "sport_key": "basketball_nba",
                      "player": null,
                      "bookmaker": null,
                      "market": null,
                      "min_ev": null
                    },
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 500,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/opportunities/positive-ev": {
      "get": {
        "tags": [
          "Opportunities"
        ],
        "summary": "Single-leg bets priced favorably against a sharp reference price (e.g. Betfair Exchange fair odds) \u2014 positive expected v",
        "description": "Single-leg bets priced favorably against a sharp reference price (e.g. Betfair Exchange fair odds) \u2014 positive expected value snipes.\n\n**Note:** `bookmaker2` falls back to the synthetic string 'Betfair Fair' (a reference price, not a bettable book) when no second real book is quoted; bet_link2 is only populated when a genuine second bookmaker exists. `value_indicator` (overpriced/underpriced/fair) is computed by comparing odds1 to sharpPrice. Same 12h staleness + 5min freshness filtering as arbitrage/middles. No filterExchangeForHobby applied. Cache 300s both tiers.",
        "operationId": "get_opportunities_positive_ev",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport (also accepts sport_key fallback); uppercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minvalue",
            "in": "query",
            "required": false,
            "description": "Minimum EV value (also accepts min_value).",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "min_value",
            "in": "query",
            "required": false,
            "description": "Alias for minvalue.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Substring match against either leg's bookmaker.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_odds",
            "in": "query",
            "required": false,
            "description": "Minimum odds1 threshold (also accepts minOdds).",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "minOdds",
            "in": "query",
            "required": false,
            "description": "Alias for min_odds.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows per page. Clamped to 100 (free) / 10000 (api).",
            "schema": {
              "type": "number",
              "default": 100
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque keyset-pagination cursor.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Field projection list.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "snipe_5c02af",
                      "event": "Boston Celtics vs Los Angeles Lakers",
                      "home_team": "Boston Celtics",
                      "away_team": "Los Angeles Lakers",
                      "sport": "NBA",
                      "sport_key": "basketball_nba",
                      "market": "h2h",
                      "selection1": "Boston Celtics",
                      "bookmaker1": "sportsbet",
                      "bookmaker2": "Betfair Fair",
                      "odds1": 1.95,
                      "odds2": null,
                      "bet_link1": "https://www.sportsbet.com.au/...",
                      "bet_link2": null,
                      "line": null,
                      "value": 5.6,
                      "value_indicator": "underpriced",
                      "sharp_price": 1.85,
                      "confidence": "high",
                      "instructions": "Back Boston Celtics @ sportsbet 1.95, sharp fair price implies 1.85.",
                      "tool_type": "positive_ev",
                      "status": "upcoming",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "updated_at": "2026-09-03T09:58:00Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "next_cursor": null,
                    "filters": {
                      "sport": null,
                      "sport_key": "basketball_nba",
                      "bookmaker": null,
                      "min_value": null,
                      "min_odds": null
                    },
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/opportunities/sgm-picks": {
      "get": {
        "tags": [
          "Opportunities"
        ],
        "summary": "AI-generated Same-Game-Multi (SGM) leg combinations with confidence tier, fair/minimum odds, and combined win probabilit",
        "description": "AI-generated Same-Game-Multi (SGM) leg combinations with confidence tier, fair/minimum odds, and combined win probability.\n\n**Note:** Only opportunities route that reads via getAdminDb() (Firestore) as its primary path, with an isSupabase('sgm_picks') feature-flagged Supabase attempt first that falls back to Firestore on failure \u2014 returns 503 'Database unavailable' if Firestore admin isn't initialized, even if Supabase would have worked (503 check happens before the Supabase attempt). Only future events are returned (commenceTime >= now); no staleness/freshness filtering like the other opportunities routes since these are pre-game AI picks, not live odds edges. Cache 60s free / 15s api. No meta.limit/requested_limit/next_cursor fields in the response (unlike arbitrage/middles/etc).",
        "operationId": "get_opportunities_sgm_picks",
        "parameters": [
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (also accepts sport_key). Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Alias for sport.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "tier",
            "in": "query",
            "required": false,
            "description": "Filter to a risk tier; any other value is ignored (no filter).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_confidence",
            "in": "query",
            "required": false,
            "description": "Minimum confidence score, clamped to 1-5.",
            "schema": {
              "type": "number",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned. Clamped to 50 (free) / 1000 (api).",
            "schema": {
              "type": "number",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "sgm_pick_a91cd0",
                      "event_id": "basketball_nba_20260903_lal_bos",
                      "event": "Boston Celtics vs Los Angeles Lakers",
                      "sport": "NBA",
                      "sport_key": "basketball_nba",
                      "commence_time": "2026-09-03T23:10:00Z",
                      "tier": "value",
                      "confidence": 4,
                      "confidence_label": "High",
                      "legs": [
                        {
                          "market": "player_points",
                          "selection": "Jayson Tatum Over 27.5",
                          "odds": 1.9
                        },
                        {
                          "market": "h2h",
                          "selection": "Boston Celtics",
                          "odds": 1.65
                        }
                      ],
                      "fair_odds_conservative": 2.9,
                      "minimum_acceptable_odds": 2.6,
                      "combined_probability": 0.365,
                      "suggested_bookmaker": "sportsbet",
                      "bet_link": "https://www.sportsbet.com.au/...",
                      "resolution": "pending"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "timestamp": "2026-09-03T10:06:00Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/arbs": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Live cross-bookmaker arbitrage opportunities on Australian racing (win markets), read from the racing_opportunities/arbs",
        "description": "Live cross-bookmaker arbitrage opportunities on Australian racing (win markets), read from the racing_opportunities/arbs doc (Supabase-first, Firestore fallback).\n\n**Note:** Reads a single pre-aggregated doc (racing_opportunities/arbs), not a live scan; filters applied in JS after fetch. Cache 60s free / 15s api. Legacy inline auth boilerplate (not using shared authorizeRacing helper).",
        "operationId": "get_racing_arbs",
        "parameters": [
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on meeting venue name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "Exact match on race type code (e.g. T/H/G) as stored on the arb record.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "minedge",
            "in": "query",
            "required": false,
            "description": "Minimum arb edge (as a fraction, e.g. 0.03 = 3%) to include.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned; capped at 100 (free) / 500 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "arb_20260903_randwick_r5_furioustempo",
                      "venue": "Randwick",
                      "race_number": 5,
                      "race_name": "Group 3 Show County Quality",
                      "race_type": "T",
                      "jump_time": "2026-09-03T04:35:00.000Z",
                      "runner": "Furious Tempo",
                      "arb_type": "win",
                      "leg1_bookmaker": "Sportsbet",
                      "leg1_odds": 4.8,
                      "leg1_bet_link": "https://www.sportsbet.com.au/search?q=Furious%20Tempo",
                      "leg2_bookmaker": "Betfair",
                      "leg2_odds": 5.4,
                      "leg2_bet_link": "https://www.betfair.com.au/exchange/plus/search?q=Furious%20Tempo",
                      "edge": 0.0231,
                      "stake1_pct": 52.9,
                      "stake2_pct": 47.1,
                      "all_legs": null,
                      "detected_at": "2026-09-03T03:58:12.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/connections": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Jockey/trainer profile lookup by name \u2014 Betfair form win-strike-rate, FormFav per-track stats, and Racing.com career/cur",
        "description": "Jockey/trainer profile lookup by name \u2014 Betfair form win-strike-rate, FormFav per-track stats, and Racing.com career/current-season profile, merged.\n\n**Note:** Names always echo back as rows with null strike rates when unmatched \u2014 `meta.matched` (not `count`) is the true 'has data' signal. Slug resolution tries multiple candidate spellings (connectionSlugCandidates); dedupes by resolved slug.",
        "operationId": "get_racing_connections",
        "parameters": [
          {
            "name": "names",
            "in": "query",
            "required": true,
            "description": "One or more jockey/trainer names to look up (max 50).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "One of jockey | trainer | both.",
            "schema": {
              "type": "string",
              "default": "both"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "jockeys": [
                      {
                        "name": "James McDonald",
                        "slug": "james-mcdonald",
                        "matched_slug": "james-mcdonald",
                        "win_strike_rate": 24.6,
                        "runs": 512,
                        "wins": 126,
                        "track_stats": [
                          {
                            "venue": "Randwick",
                            "total_starts": 88,
                            "wins": 24,
                            "places": 41,
                            "win_rate": 27.3,
                            "place_rate": 46.6
                          }
                        ],
                        "racing_com": {
                          "code": "j4821",
                          "career_wins": 1834,
                          "career_starts": 8422,
                          "win_percent": 21.8,
                          "place_percent": 47.2,
                          "recent_win_percent": 26.1,
                          "current_wins": 92,
                          "current_starts": 344,
                          "current_seconds": 58,
                          "current_thirds": 44,
                          "current_vic_metro_starts": 12,
                          "current_vic_metro_wins": 3,
                          "current_vic_country_starts": 0,
                          "current_vic_country_wins": 0,
                          "current_sa_metro_starts": 0,
                          "current_sa_metro_wins": 0,
                          "current_sa_country_starts": 0,
                          "current_sa_country_wins": 0,
                          "victorian_ranking": null,
                          "location": "Sydney, NSW",
                          "apprentice": false,
                          "weight_average": 57.2
                        }
                      }
                    ],
                    "trainers": []
                  },
                  "meta": {
                    "count": 1,
                    "matched": 1,
                    "empty": false,
                    "tier": "free",
                    "type": "jockey",
                    "names": [
                      "James McDonald"
                    ],
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/connections/combinations": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Win-strike-rate stats for jockey/trainer/horse combinations, computed from the last 90 days of racing_historical_results",
        "description": "Win-strike-rate stats for jockey/trainer/horse combinations, computed from the last 90 days of racing_historical_results.\n\n**Note:** Computed in-process over up to 5000 racing_historical_results rows from the last 90 days (Supabase racing_historical_results, Firestore fallback) \u2014 not a precomputed table. Sorted by wins desc then runs desc.",
        "operationId": "get_racing_connections_combinations",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "One of jockey_trainer | jockey_horse | trainer_horse \u2014 which combo pair to aggregate.",
            "schema": {
              "type": "string",
              "default": "jockey_trainer"
            }
          },
          {
            "name": "names",
            "in": "query",
            "required": false,
            "description": "Filter combos where either side's name contains one of these (case-insensitive); max 50 entries.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned; capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "primary": "j mcdonald",
                      "secondary": "c waller",
                      "runs": 41,
                      "wins": 11,
                      "win_strike_rate": 26.83
                    },
                    {
                      "primary": "j mcdonald",
                      "secondary": "j cummings",
                      "runs": 22,
                      "wins": 5,
                      "win_strike_rate": 22.73
                    }
                  ],
                  "meta": {
                    "count": 2,
                    "empty": false,
                    "tier": "free",
                    "type": "jockey_trainer",
                    "names": null,
                    "window": "90 days",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/connections/season-stats": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Per-season (Aug\u2013Jul AU racing season) jockey/trainer stats broken down by venue and race type, from racing_results_flat.",
        "description": "Per-season (Aug\u2013Jul AU racing season) jockey/trainer stats broken down by venue and race type, from racing_results_flat.\n\n**Note:** 503 'Season stats require Supabase backend' if DATA_BACKEND isn't Supabase for 'racing' \u2014 this endpoint has NO Firestore fallback. Queries racing_results_flat with .ilike on jockey/trainer column.",
        "operationId": "get_racing_connections_season_stats",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": true,
            "description": "Jockey or trainer name (case-insensitive).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "One of jockey | trainer.",
            "schema": {
              "type": "string",
              "default": "jockey"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Season label e.g. '2025/26', or a single year (interpreted as Aug prevYear\u2013Jul year).",
            "schema": {
              "type": "string",
              "default": "current season"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "name": "James McDonald",
                    "type": "jockey",
                    "season": "2025/26",
                    "overall": {
                      "starts": 344,
                      "wins": 92,
                      "places": 194,
                      "win_rate": 26.74,
                      "place_rate": 56.4
                    },
                    "by_venue": [
                      {
                        "venue": "randwick",
                        "starts": 88,
                        "wins": 24,
                        "places": 48,
                        "win_rate": 27.27,
                        "place_rate": 54.55
                      },
                      {
                        "venue": "rosehill-gardens",
                        "starts": 61,
                        "wins": 15,
                        "places": 33,
                        "win_rate": 24.59,
                        "place_rate": 54.1
                      }
                    ],
                    "by_type": [
                      {
                        "race_type": "T",
                        "starts": 344,
                        "wins": 92,
                        "places": 194,
                        "win_rate": 26.74,
                        "place_rate": 56.4
                      }
                    ]
                  },
                  "meta": {
                    "tier": "free",
                    "name": "James McDonald",
                    "type": "jockey",
                    "season": "2025/26",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/futures": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Upcoming major-race futures (Melbourne Cup, Cox Plate, Golden Rose etc.) merged from PointsBet futures markets and Amuse",
        "description": "Upcoming major-race futures (Melbourne Cup, Cox Plate, Golden Rose etc.) merged from PointsBet futures markets and Amused/BlackStream racing feeds.\n\n**Note:** Venue is GUESSED from race/competition name text via a hardcoded RACE_VENUE map \u2014 not authoritative. Only future dates (startTime >= today) are queried. Uses shared authorizeRacing() helper (../_shared.ts).",
        "operationId": "get_racing_futures",
        "parameters": [
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Substring match on inferred venue (mapped from race/competition name via a well-known-race lookup table).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "One of T | H | G.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Exact-match filter on the entry's derived date.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned; capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "date": "2026-11-03",
                      "venue": "Flemington",
                      "race_name": "Melbourne Cup",
                      "race_type": "T",
                      "runners": [
                        {
                          "name": "Vauban",
                          "odds": 8.5,
                          "bookmaker": "PointsBet"
                        },
                        {
                          "name": "Absurde",
                          "odds": 12.0,
                          "bookmaker": "PointsBet"
                        }
                      ],
                      "prize_money": null,
                      "distance": null,
                      "conditions": null,
                      "bookmakers": [
                        "PointsBet",
                        "Amused"
                      ]
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "sources": [
                      "PointsBet",
                      "Amused"
                    ],
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "timestamp": "2026-09-03T04:00:00.000Z"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/international": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "UK/IE and other international racecards and results, from racing_intl_racecards / racing_intl_results (The Racing API so",
        "description": "UK/IE and other international racecards and results, from racing_intl_racecards / racing_intl_results (The Racing API sourced).\n\n**Note:** Built on the shared makeV1Route() factory (v1-collection-route.ts) with feature: 'historical' \u2014 this is the ONLY racing endpoint that is NOT free-tier (historical/bulk_export are the only two paid-only features). 204 status + credit refund on empty result set.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_racing_international",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "One of racecards | results \u2014 which collection to query.",
            "schema": {
              "type": "string",
              "default": "racecards"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Exact-match filter on race date.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned; capped at 50 (free) / 200 (api). Note: gated to api-tier anyway via the 'historical' feature.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "date": "2026-09-03",
                      "venue": "Ascot",
                      "country": "GB",
                      "race_name": "3:35 Ascot - Handicap",
                      "race_class": "Class 4",
                      "distance_f": 8,
                      "runners": [
                        {
                          "name": "Northern Lad",
                          "jockey": "R Moore",
                          "trainer": "C Appleby",
                          "draw": 3,
                          "or_rating": 78
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 100,
                    "requested_limit": 100,
                    "type": "racecards",
                    "date": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 200,
                      "remaining": 1998,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/meetings": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "The full racing board \u2014 today's (or filtered) AU/NZ/UK-IE race meetings with races and runners, heavily enriched (TAB ca",
        "description": "The full racing board \u2014 today's (or filtered) AU/NZ/UK-IE race meetings with races and runners, heavily enriched (TAB cards, Racing.com sectionals/videos, FormFav form/stats, results, Betfair Exchange snapshots, PuntersEdge movers).\n\n**Note:** By far the heaviest endpoint \u2014 up to 6 sequential/parallel enrichment joins per meeting (TAB, intl racecards, Racing.com sectionals/videos, form, stats, results/TABNZ/Betfair/PuntersEdge). Cache 120s free / 30s api (both below the 300s CLAUDE.md floor per v1Revalidate() clamp logic). Full runner object has ~80 optional keys \u2014 stripped of nulls by default; use ?full=1 to restore. UK/IE meetings synthesized as type 'R' but counted as 'T' unless H/G explicitly requested.\n\n**Performance:** The default response includes heavy enrichments (TAB, form, sectionals, stats) and takes ~30s for 50 meetings. Use `?lite=1` for faster responses (~3-5s) \u2014 returns the same data structure with meetings, races, and runners with odds, just skips the enrichment pipeline.",
        "operationId": "get_racing_meetings",
        "parameters": [
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "One of T | H | G. Alias: `type`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Alias for race_type.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on venue name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "jurisdiction",
            "in": "query",
            "required": false,
            "description": "State/tote-jurisdiction code (e.g. NSW, VIC). Alias: `state`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Alias for jurisdiction.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max meetings returned; capped at 50 (free) / 1000 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "full",
            "in": "query",
            "required": false,
            "description": "When '1', returns the explicit-null shape (all ~80 runner keys present even if null); default strips null/empty-array fields to shrink payload.",
            "schema": {
              "type": "boolean",
              "default": "0"
            }
          },
          {
            "name": "lite",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "0",
                "1",
                "true",
                "false"
              ]
            },
            "description": "Set to 1 or true for faster response (~3-5s). Returns meetings, races, and runners with odds \u2014 skips heavy TAB/form/sectionals enrichments. Recommended for polling. Default: 0 (full enrichment, ~30s+)."
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "tab_1234567",
                      "meeting_key": "2026-09-03_flemington",
                      "venue": "Flemington",
                      "state": "VIC",
                      "country": null,
                      "tote_jurisdiction": "VIC",
                      "type": "T",
                      "date": "2026-09-03",
                      "race_count": 8,
                      "next_jump": "2026-09-03T04:35:00.000Z",
                      "weather": {
                        "summary": "Partly cloudy",
                        "temp_c": 16,
                        "rain_24h_mm": 0,
                        "wind_kmh": 14,
                        "wind_dir": "SW"
                      },
                      "track_hint": "Good 4",
                      "prize_money": 180000,
                      "races": [
                        {
                          "id": "1234567_5",
                          "race_key": "2026-09-03_flemington_5",
                          "number": 5,
                          "jump_time": "2026-09-03T04:35:00.000Z",
                          "name": "Turnbull Stakes",
                          "distance": 2000,
                          "race_class": "Group 1",
                          "status": "open",
                          "track_condition": "Good 4",
                          "runner_count": 10,
                          "runners": [
                            {
                              "id": "r1",
                              "number": 3,
                              "name": "Zaaki",
                              "barrier": 6,
                              "jockey": "J Bowman",
                              "trainer": "A Freedman",
                              "weight": 58.5,
                              "is_scratched": false,
                              "best_win": 3.2,
                              "best_win_bookmaker": "Sportsbet",
                              "odds": [
                                {
                                  "bookmaker": "Sportsbet",
                                  "win": 3.2,
                                  "place": 1.5,
                                  "bet_link": "https://www.sportsbet.com.au/search?q=Zaaki"
                                },
                                {
                                  "bookmaker": "Betfair",
                                  "win": 3.4,
                                  "matched": 42000,
                                  "bet_link": "https://www.betfair.com.au/exchange/plus/search?q=Zaaki"
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/movers": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Live odds steamers/drifters (significant price movements) across AU racing markets, read from racing_opportunities/mover",
        "description": "Live odds steamers/drifters (significant price movements) across AU racing markets, read from racing_opportunities/movers.\n\n**Note:** Reads a single doc combining `movers` + `drifters` arrays. Falls back Firestore\u2192Supabase if the Supabase doc is missing or both arrays are empty. Legacy inline auth (not shared _shared.ts helper).",
        "operationId": "get_racing_movers",
        "parameters": [
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on meeting venue.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "Exact match on race type code.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "movement_type",
            "in": "query",
            "required": false,
            "description": "One of steamer | drifter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_movement",
            "in": "query",
            "required": false,
            "description": "Minimum absolute movement percentage to include.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned; capped at 100 (free) / 500 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "race_id": "1234567_5",
                      "venue": "Caulfield",
                      "race_number": 5,
                      "race_name": "Memsie Stakes",
                      "race_type": "T",
                      "jump_time": "2026-09-03T05:10:00.000Z",
                      "runner": "Alligator Blood",
                      "runner_number": 4,
                      "bookmaker": "Ladbrokes",
                      "bet_link": "https://www.ladbrokes.com.au/search?q=Alligator%20Blood",
                      "opening_odds": 6.5,
                      "current_odds": 4.2,
                      "movement_pct": -35.4,
                      "movement_type": "steamer"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/odds-history": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Per-runner price-movement curves (open \u2192 every fluctuation \u2192 close/SP/BSP) per bookmaker for a meeting/race/runner, buil",
        "description": "Per-runner price-movement curves (open \u2192 every fluctuation \u2192 close/SP/BSP) per bookmaker for a meeting/race/runner, built from TAB cards, historical results and racing_odds_snapshots.\n\n**Note:** Free tier is hard-clamped to the last 14 days regardless of the account's `historicalDays` window (extra check beyond the standard historical-clamp gate) \u2014 returns 402 with X-Krok-Feature: historical. Tote/SP book keys (tote, vrc, racenet, etc.) are excluded from bookFlucs. Falls back card\u2192historical-results\u2192live-snapshot-only in that order per meeting. Uses shared _shared.ts auth (feature racing_premium, cost 5).",
        "operationId": "get_racing_odds_history",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Meeting date to fetch.",
            "schema": {
              "type": "string",
              "default": "today (Sydney meeting date)"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on venue.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "raceNumber",
            "in": "query",
            "required": false,
            "description": "Filter to a single race number. Alias: `race`.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "race",
            "in": "query",
            "required": false,
            "description": "Alias for raceNumber.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "runner_slug",
            "in": "query",
            "required": false,
            "description": "Filter to a single runner by slugified name.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "tab_1234567",
                      "venue": "Flemington",
                      "date": "2026-09-03",
                      "races": [
                        {
                          "number": 5,
                          "name": "Turnbull Stakes",
                          "status": "closed",
                          "start_time": "2026-09-03T04:35:00.000Z",
                          "runners": [
                            {
                              "number": 3,
                              "name": "Zaaki",
                              "is_scratched": false,
                              "open": 3.6,
                              "sp": 3.2,
                              "bsp": 3.15,
                              "flucs": [
                                {
                                  "timestamp": "2026-09-03T02:00:00.000Z",
                                  "odds": 3.6
                                },
                                {
                                  "timestamp": "2026-09-03T04:30:00.000Z",
                                  "odds": 3.2
                                }
                              ],
                              "bookFlucs": {
                                "sportsbet": [
                                  {
                                    "timestamp": "2026-09-03T02:00:00.000Z",
                                    "odds": 3.7
                                  },
                                  {
                                    "timestamp": "2026-09-03T04:30:00.000Z",
                                    "odds": 3.3
                                  }
                                ]
                              }
                            }
                          ]
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "date": "2026-09-03",
                    "venue": null,
                    "race_number": 5,
                    "runner_slug": null,
                    "tier": "free",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    },
                    "source": "card"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/predictions": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Betfair-derived win/place probability predictions for AU/NZ races (branded 'Betfair Predictions'), from external_betfair",
        "description": "Betfair-derived win/place probability predictions for AU/NZ races (branded 'Betfair Predictions'), from external_betfair_predictions.\n\n**Note:** Built on makeV1Route() factory. Sorted by model_rank asc then win_probability desc. Fetches limit*4 rows when venue/code/horse filters are set (JS post-filter), capped at the requested limit afterward.",
        "operationId": "get_racing_predictions",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Race date to filter.",
            "schema": {
              "type": "string",
              "default": "today"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on venue.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "code",
            "in": "query",
            "required": false,
            "description": "Racing code: thoroughbred | harness | greyhound.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "horse",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on horse name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned; capped at 200 (free) / 1000 (api).",
            "schema": {
              "type": "integer",
              "default": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "bf_pred_20260903_flemington_5_3",
                      "date": "2026-09-03",
                      "venue": "Flemington",
                      "race_no": 5,
                      "horse_name": "Zaaki",
                      "win_probability": 0.31,
                      "place_probability": 0.58,
                      "model_rank": 1,
                      "code": "thoroughbred"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 200,
                    "requested_limit": 200,
                    "date": "2026-09-03",
                    "venue": null,
                    "code": null,
                    "horse": null,
                    "source": "Betfair Predictions",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 200,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/ratings": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "KrokBot AI pick detail for a single race \u2014 per-runner win probability/rating, field analysis verdicts, and Betfair money",
        "description": "KrokBot AI pick detail for a single race \u2014 per-runner win probability/rating, field analysis verdicts, and Betfair money-flow tip signals, from racing_ai_picks.\n\n**Note:** 404 with meta.status='not_yet_populated' if no pick exists for the race yet. `model` field is deliberately null (internal, not exposed via public API). Betfair money-flow tip join is best-effort (venue+raceNumber+sport, disambiguated by jumpTime proximity) \u2014 failure degrades gracefully, doesn't fail the request.",
        "operationId": "get_racing_ratings",
        "parameters": [
          {
            "name": "race_id",
            "in": "query",
            "required": true,
            "description": "Direct racing_ai_picks doc id lookup.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "meeting_id",
            "in": "query",
            "required": true,
            "description": "Meeting id, used with race_number as a fallback query.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "race_number",
            "in": "query",
            "required": true,
            "description": "Race number within the meeting, used with meeting_id.",
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "race_id": "1234567_5",
                    "meeting_id": "1234567",
                    "race_name": "Turnbull Stakes",
                    "race_number": 5,
                    "venue": "Flemington",
                    "state": "VIC",
                    "race_type": "T",
                    "jump_time": "2026-09-03T04:35:00.000Z",
                    "date": "2026-09-03",
                    "tier": "feature",
                    "score": 82.4,
                    "confidence": "high",
                    "top_pick": {
                      "slug": "zaaki",
                      "name": "Zaaki",
                      "bet_link": "https://www.sportsbet.com.au/search?q=Zaaki"
                    },
                    "runners": [
                      {
                        "slug": "zaaki",
                        "name": "Zaaki",
                        "win_prob": 0.31,
                        "fair_odds": 3.23,
                        "confidence": "high",
                        "rank": 1,
                        "edge_pct": 4.2,
                        "value": true,
                        "money_share": 0.28,
                        "money_delta": 0.03,
                        "rating": 88.5
                      }
                    ],
                    "risk": "low",
                    "pick_rationale": "Strong recent form at set weights over 2000m.",
                    "track_condition": "Good 4",
                    "bestWin": 3.2,
                    "bestWinBookmaker": "Sportsbet",
                    "value_picks": [],
                    "market_mover": null
                  },
                  "meta": {
                    "count": 8,
                    "empty": false,
                    "tier": "free",
                    "race_id": "1234567_5",
                    "meeting_id": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/results": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Archive query across settled race results (Betfair BSP-based, lighter fields than the date-specific endpoint) with filte",
        "description": "Archive query across settled race results (Betfair BSP-based, lighter fields than the date-specific endpoint) with filters by track, race type, runner and date range.\n\n**Note:** Explicit CREDIT_COST=5 archive endpoint; refunds the full credit cost when the result set is empty. Setting `since`/`until` on a free-tier key returns a 402 tier-gate error for the 'historical' feature BEFORE the per-key clamp runs. `track`/`runner_slug`/`race_name` filters are applied client-side after an over-fetch (up to 2000 rows).",
        "operationId": "get_racing_results",
        "parameters": [
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "One of T | H | G.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "track_slug",
            "in": "query",
            "required": false,
            "description": "Exact match on venue slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "track",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on track name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "race_name",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on race name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "runner_slug",
            "in": "query",
            "required": false,
            "description": "Exact match on a runner's slug within the race.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Results on/after this date. Requires 'historical' feature (api tier); also clamped per-key by verification.historicalDays.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Results on/before this date. Requires 'historical' feature (api tier).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned; capped at 50 (free) / 2000 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "2026-09-03_flemington_5",
                      "date": "2026-09-03",
                      "track": "Flemington",
                      "track_slug": "flemington",
                      "state": "VIC",
                      "race_no": 5,
                      "race_type": "T",
                      "distance_m": 2000,
                      "race_name": "Turnbull Stakes",
                      "winning_time": 121.4,
                      "mile_rate": null,
                      "winner": {
                        "tab_number": 3,
                        "name": "Zaaki",
                        "slug": "zaaki",
                        "jockey": "J Bowman",
                        "trainer": "A Freedman",
                        "win_bsp": 3.15
                      },
                      "runners": [
                        {
                          "tab_number": 3,
                          "name": "Zaaki",
                          "slug": "zaaki",
                          "finish_position": 1,
                          "win_result": 1,
                          "win_bsp": 3.15,
                          "jockey": "J Bowman",
                          "trainer": "A Freedman"
                        }
                      ],
                      "going": "Good 4",
                      "race_class": "Group 1",
                      "ingested_at": "2026-09-03T05:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "race_type": null,
                    "track_slug": null,
                    "race_name": null,
                    "runner_slug": null,
                    "since": null,
                    "until": null,
                    "data_note": "This endpoint returns Betfair BSP-based results (limited fields). For richer results including margins, times and full connections, use the date-specific endpoint /api/v1/racing/results/{date}.",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 937,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/results/{date}": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Full settled race results for every meeting on a single date \u2014 finishing order, margins, times, BSP, jockey/trainer \u2014 me",
        "description": "Full settled race results for every meeting on a single date \u2014 finishing order, margins, times, BSP, jockey/trainer \u2014 merged with TAB tote pools and dividends.\n\n**Note:** Same `{date}_{venueSlug}_{raceNo}` race_key/meeting_key join keys as /racing/meetings. Sorts results by raceNo in JS (NOT in SQL) to avoid a full seq-scan on the unindexed data->>raceNo JSONB path \u2014 deliberate perf workaround. Cache 300s (respects the CLAUDE.md floor).",
        "operationId": "get_racing_results_date",
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "description": "Meeting date to fetch results for; 400 if not a valid YYYY-MM-DD string.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on track name/slug. Alias: `track`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "track",
            "in": "query",
            "required": false,
            "description": "Alias for venue.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "2026-09-03_flemington_5",
                      "race_key": "2026-09-03_flemington_5",
                      "meeting_key": "2026-09-03_flemington",
                      "date": "2026-09-03",
                      "track": "Flemington",
                      "track_slug": "flemington",
                      "state": "VIC",
                      "race_no": 5,
                      "race_type": "T",
                      "distance_m": 2000,
                      "race_name": "Turnbull Stakes",
                      "going": "Good 4",
                      "race_class": "Group 1",
                      "status": "settled",
                      "winner": {
                        "tab_number": 3,
                        "name": "Zaaki",
                        "slug": "zaaki",
                        "jockey": "J Bowman",
                        "trainer": "A Freedman",
                        "win_bsp": 3.15
                      },
                      "pools": [
                        {
                          "pool_type": "WIN",
                          "total": 184200
                        }
                      ],
                      "dividends": [
                        {
                          "pool_type": "WIN",
                          "tab_number": 3,
                          "dividend": 3.4
                        }
                      ],
                      "runners": [
                        {
                          "tab_number": 3,
                          "name": "Zaaki",
                          "finish_position": 1,
                          "margin": 0,
                          "win_result": 1,
                          "win_bsp": 3.15,
                          "jockey": "J Bowman",
                          "trainer": "A Freedman",
                          "barrier": 6,
                          "weight": "58.5kg"
                        },
                        {
                          "tab_number": 7,
                          "name": "Buffalo River",
                          "finish_position": 2,
                          "margin": 0.8,
                          "win_result": 0,
                          "win_bsp": 6.4,
                          "jockey": "M Zahra",
                          "trainer": "C Maher",
                          "barrier": 2,
                          "weight": "56kg"
                        }
                      ],
                      "exotic_pools": [
                        {
                          "type": "QUINELLA",
                          "total": 42100
                        }
                      ],
                      "ingested_at": "2026-09-03T05:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "date": "2026-09-03",
                    "venue": null,
                    "tier": "free",
                    "timestamp": "2026-09-03T05:30:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/runner-form": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "A single runner's recent race history (form) across thoroughbred/harness/greyhound codes, with joined Racing.com section",
        "description": "A single runner's recent race history (form) across thoroughbred/harness/greyhound codes, with joined Racing.com sectionals and a Brightcove replay-video fallback resolver.\n\n**Note:** Per memory (runner-form Supabase path): the Supabase branch (`runner_recent_races` table) is gated behind isSupabase('runner-form') and was reported BLOCKED/undeployed as of the last audit \u2014 may silently fall back to the Firestore `runner_historical_stats/{sport}__{slug}/recent_races` subcollection path. Sectionals join is a single best-effort query per request (ilike on horseName).",
        "operationId": "get_racing_runner_form",
        "parameters": [
          {
            "name": "runner_slug",
            "in": "query",
            "required": true,
            "description": "Slugified runner name to look up.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "One of racing_T | racing_H | racing_G. Alias: `sport`.",
            "schema": {
              "type": "string",
              "default": "all three codes"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only races on/after this date; clamped by the key's historicalDays window.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Only races on/before this date.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned; capped at 50 (free) / 2000 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "race_id": "2026-08-20_randwick_4",
                      "sport_key": "racing_T",
                      "runner_slug": "zaaki",
                      "date": "2026-08-20",
                      "track": "Randwick",
                      "race_no": 4,
                      "race_name": "Warwick Stakes",
                      "distance": 1400,
                      "win_result": 1,
                      "place_result": 1,
                      "win_bsp": 2.8,
                      "tab_number": 5,
                      "jockey": "J Bowman",
                      "trainer": "A Freedman",
                      "barrier": 3,
                      "finish_position": 1,
                      "margin": 1.2,
                      "replay_video": "https://videos.krokodds.com.au/replay/2026-08-20_randwick_4.m3u8",
                      "sectionals": {
                        "l600": 34.1,
                        "l400": 22.4,
                        "l200": 11.3,
                        "speed": {
                          "early": 58.2,
                          "mid": 59.8,
                          "late": 61.4,
                          "overall": 59.9,
                          "peak": 62.1
                        },
                        "closing_ratio": 1.04,
                        "runs_sampled": 8
                      }
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "empty": false,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "runner_slug": "zaaki",
                    "sport_key": null,
                    "since": null,
                    "until": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/runner-stats": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Aggregated career/split stats for a single runner \u2014 win/place rates by track, distance, going, track+distance combo, and",
        "description": "Aggregated career/split stats for a single runner \u2014 win/place rates by track, distance, going, track+distance combo, and prep-stage (first-up/second-up/in-prep), plus sectionals and career prize money.\n\n**Note:** 404 with meta.status='not_yet_populated' if no historical record AND no sectionals row exist. When sport_key is omitted, probes racing_T \u2192 racing_H \u2192 racing_G in order and returns the first with a career record (falls further back to racing_com_sectionals-only if no fetchRunnerStats hit).",
        "operationId": "get_racing_runner_stats",
        "parameters": [
          {
            "name": "runner_slug",
            "in": "query",
            "required": true,
            "description": "Slugified runner name to look up.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "One of racing_T | racing_H | racing_G. Alias: `sport`.",
            "schema": {
              "type": "string",
              "default": "probes all three in order"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Alias for sport_key.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "runner_slug": "zaaki",
                    "sport_key": "racing_T",
                    "historical": {
                      "name": "Zaaki",
                      "starts": 34,
                      "wins": 11,
                      "places": 20,
                      "win_rate": 32.35,
                      "place_rate": 58.82,
                      "avg_win_bsp": 4.1,
                      "last_track": "Randwick",
                      "last_date": "2026-08-20"
                    },
                    "career_prize_money": 8420000,
                    "sectionals": {
                      "avg_l600": 34.5,
                      "avg_l400": 22.8,
                      "avg_l200": 11.5,
                      "closing_ratio": 1.03,
                      "avg_speed_early": 57.9,
                      "avg_speed_mid": 59.4,
                      "avg_speed_late": 61.0,
                      "avg_overall_speed": 59.5,
                      "avg_peak_speed": 61.8
                    },
                    "recent_races": [
                      {
                        "date": "2026-08-20",
                        "venue": "Randwick",
                        "finish": 1,
                        "position": 1,
                        "distance": 1400,
                        "speed": 61.2
                      }
                    ],
                    "splits": {
                      "by_track": [
                        {
                          "bucket": "Randwick",
                          "starts": 9,
                          "wins": 4,
                          "places": 6,
                          "win_rate": 44.4,
                          "place_rate": 66.7,
                          "avg_bsp": 3.6
                        }
                      ],
                      "by_distance": [
                        {
                          "bucket": "1400m",
                          "starts": 12,
                          "wins": 5,
                          "places": 8,
                          "win_rate": 41.7,
                          "place_rate": 66.7,
                          "avg_bsp": 3.9
                        }
                      ],
                      "by_going": [
                        {
                          "bucket": "Good",
                          "starts": 20,
                          "wins": 8,
                          "places": 13,
                          "win_rate": 40,
                          "place_rate": 65,
                          "avg_bsp": 3.8
                        }
                      ],
                      "by_track_distance": [],
                      "by_prep_stage": {
                        "first_up": {
                          "bucket": "first_up",
                          "starts": 6,
                          "wins": 2,
                          "places": 4,
                          "win_rate": 33.3,
                          "place_rate": 66.7,
                          "avg_bsp": 4.5
                        },
                        "second_up": {
                          "bucket": "second_up",
                          "starts": 6,
                          "wins": 3,
                          "places": 4,
                          "win_rate": 50,
                          "place_rate": 66.7,
                          "avg_bsp": 3.2
                        },
                        "in_prep": {
                          "bucket": "in_prep",
                          "starts": 22,
                          "wins": 6,
                          "places": 12,
                          "win_rate": 27.3,
                          "place_rate": 54.5,
                          "avg_bsp": 4.4
                        }
                      },
                      "class_change": null,
                      "avg_days_between_starts": 28,
                      "spell_length": 84,
                      "form_string": "1-2x14",
                      "best_bsp": 2.1,
                      "best_bsp_label": "2026-06-14 Rosehill",
                      "total_analysed": 34
                    }
                  },
                  "meta": {
                    "tier": "free",
                    "runner_slug": "zaaki",
                    "sport_key": "racing_T",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/sectionals/{horseCode}": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Racing.com sectional times (L600/L400/L200, speed map) and profile (condition splits, first/second/third-up stats, sire ",
        "description": "Racing.com sectional times (L600/L400/L200, speed map) and profile (condition splits, first/second/third-up stats, sire progeny) for a horse, keyed by Racing.com's own horse code.\n\n**Note:** 404 if BOTH racing_com_sectionals and racing_com_profiles docs are missing for that code. Cache 600s \u2014 sectionals are backfilled post-race and slow-moving. Uses the shared _shared.ts authorizeRacing() with default feature 'racing'.",
        "operationId": "get_racing_sectionals_horseCode",
        "parameters": [
          {
            "name": "horseCode",
            "in": "path",
            "required": true,
            "description": "Racing.com horse code (alphanumeric/underscore/hyphen, 1-64 chars); 400 if it fails that pattern.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "horse_code": "h284719",
                    "name": "Zaaki",
                    "sectionals": {
                      "run_count": 8,
                      "l600": 34.1,
                      "l400": 22.4,
                      "l200": 11.3,
                      "speed": {
                        "early": 58.2,
                        "mid": 59.8,
                        "late": 61.4,
                        "overall": 59.9,
                        "peak": 62.1
                      },
                      "closing_ratio": 1.04
                    },
                    "profile": {
                      "condition_splits": {
                        "firm": "2:1-0-1",
                        "good": "24:9-5-3",
                        "soft": "6:1-2-1",
                        "heavy": "2:0-0-1",
                        "wet": "8:1-2-2"
                      },
                      "first_up": "6:2-0-1",
                      "second_up": "6:3-1-0",
                      "third_up": "5:1-1-1",
                      "winning_range": "1400m-2000m",
                      "days_since_last_win": 96,
                      "career_stats": "34:11-9-6",
                      "sire_progeny_dry": "412:78-65-59",
                      "sire_progeny_wet": "88:14-11-9"
                    }
                  },
                  "meta": {
                    "tier": "free",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/racing/tote-pools": {
      "get": {
        "tags": [
          "Racing"
        ],
        "summary": "Parimutuel (tote) pool totals, dividends and multi-leg exotic pools per race for a meeting day, from TAB card data (exte",
        "description": "Parimutuel (tote) pool totals, dividends and multi-leg exotic pools per race for a meeting day, from TAB card data (external_tab_racing).\n\n**Note:** Cache is 60s (below the 300s CLAUDE.md floor, but v1Revalidate() enforces the floor at the framework level regardless of the declared constant). Dividends array is typically empty pre-race and only populates once TAB settles the pool.",
        "operationId": "get_racing_tote_pools",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Meeting date to fetch tote pool data for.",
            "schema": {
              "type": "string",
              "default": "today (Sydney meeting date)"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on venue.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "tab_1234567",
                      "venue": "Flemington",
                      "state": "VIC",
                      "type": "T",
                      "date": "2026-09-03",
                      "tote_jurisdiction": "VIC",
                      "exotic_pools": [
                        {
                          "type": "QUADDIE",
                          "legs": [
                            4,
                            5,
                            6,
                            7
                          ],
                          "total": 612400,
                          "jackpot": false
                        }
                      ],
                      "races": [
                        {
                          "number": 5,
                          "name": "Turnbull Stakes",
                          "distance": 2000,
                          "status": "open",
                          "start_time": "2026-09-03T04:35:00.000Z",
                          "pools": [
                            {
                              "pool_type": "WIN",
                              "total": 184200
                            },
                            {
                              "pool_type": "PLACE",
                              "total": 96700
                            }
                          ],
                          "dividends": []
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "date": "2026-09-03",
                    "venue": null,
                    "tier": "free",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/bookmakers": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Catalogue of bookmakers KrokOdds scrapes directly (pure registry read, no DB hit).",
        "description": "Catalogue of bookmakers KrokOdds scrapes directly (pure registry read, no DB hit).\n\n**Note:** Zero-cost registry read \u2014 no database hit. Cache 300s. `feeds[].status` is 'pending' when the sync is written/merged but the Cloud Function is not yet deployed \u2014 surfaced rather than hidden. `aliases` = white-label brands accepted by `?bookmaker=` on other endpoints, served from the parent book's pricing backend. meta.redistributable is @deprecated in favour of meta.direct_scrape (removal 2027-02-01).",
        "operationId": "get_odds_feed_bookmakers",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "key": "sportsbet",
                      "name": "Sportsbet",
                      "type": "corporate",
                      "feeds": [
                        {
                          "kind": "sports",
                          "status": "live"
                        },
                        {
                          "kind": "racing",
                          "status": "live"
                        }
                      ],
                      "racing": true,
                      "sports": true,
                      "aliases": []
                    },
                    {
                      "key": "betmakers",
                      "name": "BetMakers Nimbus",
                      "type": "platform",
                      "feeds": [
                        {
                          "kind": "sports",
                          "status": "live"
                        },
                        {
                          "kind": "racing",
                          "status": "pending"
                        }
                      ],
                      "racing": false,
                      "sports": true,
                      "aliases": [
                        {
                          "key": "unibet",
                          "name": "Unibet"
                        },
                        {
                          "key": "betright",
                          "name": "BetRight"
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape",
                    "note": "All feeds listed here are scraped directly by KrokOdds. Display-permitted in your own product with attribution; not licensed for resale as a standalone feed \u2014 see Terms 6.1. Aggregator-licensed odds are never served from /v1/odds-feed."
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/clv": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Historical closing-line odds (last snapshot per bookmaker before commence_time) from the CLV archive.",
        "description": "Historical closing-line odds (last snapshot per bookmaker before commence_time) from the CLV archive.\n\n**Note:** Backed by `clv_archive` table; Supabase-first with a Firestore fallback on query failure. `bookmaker` filter is applied in-memory AFTER the cache read so all sport/date combos share one cache entry. Cache 300s. `from` is clamped via clampHistoricalFrom to the caller's plan window. 400 on malformed from/to (must be YYYY-MM-DD).",
        "operationId": "get_odds_feed_clv",
        "parameters": [
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport key, e.g. 'basketball_nba', 'aussierules_afl'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Filter by bookmaker key, e.g. 'sportsbet', 'tab'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start of commence_time date range. Clamped to the caller's historicalDays window.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End of commence_time date range.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results; capped per tier.",
            "schema": {
              "type": "integer",
              "default": "50 (free) / 500 (api)"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "event_id": "nrl-panthers-storm-20260903",
                      "sport": "rugbyleague_nrl",
                      "sport_title": "NRL",
                      "home_team": "Penrith Panthers",
                      "away_team": "Melbourne Storm",
                      "commence_time": "2026-09-03T09:30:00Z",
                      "archived_at": "2026-09-03T09:29:10Z",
                      "bookmakers": [
                        {
                          "key": "sportsbet",
                          "markets": [
                            {
                              "key": "h2h",
                              "outcomes": [
                                {
                                  "name": "Penrith Panthers",
                                  "bet_link": "https://www.sportsbet.com.au/..."
                                },
                                {
                                  "name": "Melbourne Storm",
                                  "bet_link": "https://www.sportsbet.com.au/..."
                                }
                              ]
                            }
                          ]
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "sport": "rugbyleague_nrl",
                    "bookmaker": null,
                    "date_from": "2026-08-27",
                    "date_to": null,
                    "limit": 50,
                    "requested_limit": 50,
                    "total_matched": 1,
                    "truncated": false,
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/prediction-markets": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Prediction-market odds from Polymarket and Kalshi with de-vigged probabilities and CLOB order-book enrichment.",
        "description": "Prediction-market odds from Polymarket and Kalshi with de-vigged probabilities and CLOB order-book enrichment.\n\n**Note:** Reads `external_prediction_markets` (Supabase), synced every 30 min by `predictionMarketSync` Cloud Function. Cache 600s \u2014 matches the sync interval, fresher would be wasted reads. 400 on invalid source/type enum values.",
        "operationId": "get_odds_feed_prediction_markets",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "'polymarket' or 'kalshi'.",
            "schema": {
              "type": "string",
              "default": "both"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "KrokOdds sport key, e.g. 'basketball_nba'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "'game' or 'futures'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max 1000.",
            "schema": {
              "type": "integer",
              "default": 200
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "pm_0x8f21a",
                      "source": "polymarket",
                      "type": "game",
                      "marketTitle": "Will the Sydney Kings win vs Melbourne United?",
                      "sportKey": "basketball_nbl",
                      "eventKey": "nbl-kings-united-20260903",
                      "matchConfidence": 0.94,
                      "impliedProbs": {
                        "yes": 0.57,
                        "no": 0.43
                      },
                      "rawPrices": {
                        "yes": 0.57,
                        "no": 0.43
                      },
                      "volume": 18450.25,
                      "liquidity": 6200.5,
                      "bestBid": 0.56,
                      "bestAsk": 0.58,
                      "spread": 0.02,
                      "lastTradePrice": 0.57,
                      "oneDayPriceChange": 0.03,
                      "updatedAt": "2026-09-03T08:15:00Z"
                    }
                  ],
                  "meta": {
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape",
                    "sources": [
                      "polymarket"
                    ],
                    "filter": {
                      "source": null,
                      "sport": "basketball_nbl",
                      "type": null
                    },
                    "note": "Prediction-market contract prices de-vigged and enriched with CLOB order-book data. Synced every 30 minutes."
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/racing": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Racing meetings -> races -> runners -> per-book fixed/tote odds, normalised across all direct-scrape racing feeds.",
        "description": "Racing meetings -> races -> runners -> per-book fixed/tote odds, normalised across all direct-scrape racing feeds.\n\n**Note:** Gated on 'racing' not 'scraped_odds' by design \u2014 both are free today; kept separate so racing can move to paid independently. Per-collection scan cap is 600 (TAB alone runs ~420 meetings on a busy day). TAB stores thoroughbred as 'R' internally; API normalises to 'T' in both query and output. date before the caller's historicalDays window returns 403. Cache 300s.",
        "operationId": "get_odds_feed_racing",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Meeting date, AU-local.",
            "schema": {
              "type": "string",
              "default": "today (Australia/Melbourne)"
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Canonical key or white-label alias (e.g. 'swiftbet' resolves to bluebet).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Substring match on venue slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "T/R (thoroughbred), H (harness), G (greyhound).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped per tier.",
            "schema": {
              "type": "integer",
              "default": "10 (free) / 500 (api)"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "bookmaker_key": "tab",
                      "venue_slug": "flemington",
                      "race_type": "T",
                      "races": [
                        {
                          "race_number": 4,
                          "runners": [
                            {
                              "number": 7,
                              "name": "Golden Streak",
                              "win": 4.2,
                              "place": 1.65,
                              "bet_link": "https://tab.com.au/..."
                            }
                          ]
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "date": "2026-09-03",
                    "bookmaker": "tab",
                    "brand": null,
                    "limit": 10,
                    "requested_limit": 10,
                    "total_matched": 1,
                    "truncated": false,
                    "scan_capped_bookmakers": [],
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/racing/history": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Historical per-book, per-runner racing odds snapshots (~5-min granularity) from the BigQuery cold archive \u2014 the backtest",
        "description": "Historical per-book, per-runner racing odds snapshots (~5-min granularity) from the BigQuery cold archive \u2014 the backtest feed.\n\n**Note:** PAID-ONLY endpoint: gated on `bulk_export`, which is NOT in FREE_TIER_FEATURES \u2014 requires the api (paid) plan. `from` is REQUIRED (400 if missing/malformed); unbounded scans are rejected. Max 31-day window (400 if exceeded). Cursor-based pagination \u2014 supports `?fields=` projection via parseFields/projectRows. 503 (not 500) if the BQ table doesn't exist yet (mirror/flatten not live). TAB's 'R' race_type is normalised to 'T' in output; both 'T' and 'R' accepted as query aliases.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_odds_feed_racing_history",
        "parameters": [
          {
            "name": "from",
            "in": "query",
            "required": true,
            "description": "Required. Start of date window; clamped to the caller's historicalDays.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End of date window. Must be >= from. Window capped at 31 days total.",
            "schema": {
              "type": "string",
              "default": "same as from"
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "One of tab, betfair, sportsbet, ladbrokes, neds, pointsbet, bluebet, palmerbet.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Substring match on venue_slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "T/R (thoroughbred), H (harness), G (greyhound). T and R both map to stored 'T'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 5000 for both free and api internal tiers.",
            "schema": {
              "type": "integer",
              "default": 5000
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque pagination cursor returned as meta.next_cursor from the previous page.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "date": "2026-09-03",
                      "snapshot_ts": "2026-09-03T05:05:00.000Z",
                      "snapshot_ms": 1772773500000,
                      "venue_slug": "flemington",
                      "race_no": 4,
                      "race_id": "flemington-20260903-r4",
                      "meeting_id": "flemington-20260903",
                      "race_type": "T",
                      "runner_no": 7,
                      "runner_name": "Golden Streak",
                      "scratched": false,
                      "book_key": "sportsbet",
                      "bet_link": "https://www.sportsbet.com.au/...",
                      "win": 4.2,
                      "place": 1.65,
                      "tote_win": 4.4,
                      "tote_place": 1.7,
                      "lay": 4.4
                    }
                  ],
                  "meta": {
                    "from": "2026-08-20",
                    "to": "2026-09-03",
                    "partition_days": 15,
                    "bookmaker": "sportsbet",
                    "venue": null,
                    "race_type": "T",
                    "limit": 5000,
                    "requested_limit": 5000,
                    "next_cursor": "eyJtcyI6MTc3Mjc3MzUwMDAwMCwicmFjZSI6ImZsZW1pbmd0b24tMjAyNjA5MDMtcjQiLCJydW5uZXIiOjcsImJvb2siOiJzcG9ydHNiZXQifQ",
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/racing/movements": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Cross-bookmaker price movements (opening vs current) for today's/tomorrow's races, per runner.",
        "description": "Cross-bookmaker price movements (opening vs current) for today's/tomorrow's races, per runner.\n\n**Note:** Reads a single precomputed doc `racing_opportunities/racing_book_movements` (Supabase-first, Firestore fallback). Cache 60s. Returns empty array (not error) when the doc has no movements yet. Same tier gate as /api/v1/odds-feed/racing ('racing'), independent of 'scraped_odds'.",
        "operationId": "get_odds_feed_racing_movements",
        "parameters": [
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Substring match on venue name (case-insensitive).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "T/R (thoroughbred), H (harness), G (greyhound).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Filter each runner's bookMovements down to one book.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "raceId": "flemington-20260903-r4",
                      "raceName": "Race 4",
                      "venue": "Flemington",
                      "raceNumber": 4,
                      "raceType": "T",
                      "jumpTime": "2026-09-03T05:10:00Z",
                      "runners": [
                        {
                          "number": 7,
                          "name": "Golden Streak",
                          "bestMovementPct": -12.5,
                          "bookMovements": [
                            {
                              "book": "sportsbet",
                              "opening": 4.8,
                              "current": 4.2,
                              "pct": -12.5,
                              "bet_link": "https://www.sportsbet.com.au/..."
                            },
                            {
                              "book": "tab",
                              "opening": 5.0,
                              "current": 4.4,
                              "pct": -12.0,
                              "bet_link": "https://tab.com.au/..."
                            }
                          ]
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "updatedAt": "2026-09-03T05:00:12Z"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/results": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Settled race results (winner + full finishing order) from TAB/OddsPro result feeds.",
        "description": "Settled race results (winner + full finishing order) from TAB/OddsPro result feeds.\n\n**Note:** Backed by `race_results` collection (Supabase, single jsonb-path equality filter on date + limit). date before the caller's historicalDays window returns 403. Cache 300s, scan cap 1000 rows/day.",
        "operationId": "get_odds_feed_results",
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "AU-local race date.",
            "schema": {
              "type": "string",
              "default": "today (Australia/Melbourne)"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "R (thoroughbred), H (harness), G (greyhound); T accepted as alias for R.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "e.g. 'tab', 'oddspro'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Substring match on venue slug.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped per tier.",
            "schema": {
              "type": "integer",
              "default": "20 (free) / 800 (api)"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "date": "2026-09-03",
                      "venue": "Flemington",
                      "venueSlug": "flemington",
                      "raceNumber": 4,
                      "raceType": "R",
                      "source": "tab",
                      "status": "settled",
                      "winner": {
                        "number": 7,
                        "name": "Golden Streak"
                      },
                      "placings": [
                        {
                          "position": 1,
                          "number": 7,
                          "name": "Golden Streak"
                        },
                        {
                          "position": 2,
                          "number": 3,
                          "name": "Silver Line"
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "date": "2026-09-03",
                    "sport": null,
                    "source": null,
                    "limit": 20,
                    "requested_limit": 20,
                    "total_matched": 1,
                    "truncated": false,
                    "scan_capped": false,
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/results/{date}": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "All settled race results for a specific AU-local date (path-param variant of /odds-feed/results).",
        "description": "All settled race results for a specific AU-local date (path-param variant of /odds-feed/results).\n\n**Note:** Same source, tier gate and shape as /odds-feed/results (query-param version); date comes from the path instead of ?date=. 400 on malformed date; shares the `shared.ts` loader (`race_results` collection, cache 300s, scan cap 1000).",
        "operationId": "get_odds_feed_results_date",
        "parameters": [
          {
            "name": "date",
            "in": "path",
            "required": true,
            "description": "AU-local race date.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "R (thoroughbred), H (harness), G (greyhound). T accepted as alias for R.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "e.g. 'tab', 'oddspro'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Substring match on venue slug.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "date": "2026-09-03",
                      "venue": "Flemington",
                      "venueSlug": "flemington",
                      "raceNumber": 4,
                      "raceType": "R",
                      "source": "tab",
                      "status": "settled",
                      "winner": {
                        "number": 7,
                        "name": "Golden Streak"
                      },
                      "placings": [
                        {
                          "position": 1,
                          "number": 7,
                          "name": "Golden Streak"
                        },
                        {
                          "position": 2,
                          "number": 3,
                          "name": "Silver Line"
                        }
                      ],
                      "scratchings": [
                        5
                      ],
                      "resultAt": "2026-09-03T05:14:22Z"
                    }
                  ],
                  "meta": {
                    "date": "2026-09-03",
                    "sport": "R",
                    "source": null,
                    "limit": 20,
                    "total_matched": 1,
                    "truncated": false,
                    "scan_capped": false,
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/sports": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Sports with live directly-scraped event coverage, with per-sport live event counts and supplying bookmakers.",
        "description": "Sports with live directly-scraped event coverage, with per-sport live event counts and supplying bookmakers.\n\n**Note:** Counts come from PostgREST head-only COUNT queries, never a full document scan (deliberate cost guard, see June 2026 cost incident). Cache 1800s \u2014 sport list moves slowly. If ALL count queries fail, the route throws (500) rather than caching an empty list for 1800s. Racing-only books (BlueBet, Palmerbet) return empty sports list \u2014 that's real coverage, not an error; surfaced via meta.bookmaker_sports_feed = 'pending'.",
        "operationId": "get_odds_feed_sports",
        "parameters": [
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Canonical key or white-label alias; filters the (sport, book) counts in-memory over one cache entry.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "sport": "aussierules_afl",
                      "event_count": 9,
                      "bookmakers": [
                        "betr",
                        "ladbrokes",
                        "pointsbet",
                        "sportsbet",
                        "tab"
                      ],
                      "href": "/api/v1/odds-feed/sports/aussierules_afl"
                    },
                    {
                      "sport": "rugbyleague_nrl",
                      "event_count": 8,
                      "bookmakers": [
                        "ladbrokes",
                        "sportsbet",
                        "tab"
                      ],
                      "href": "/api/v1/odds-feed/sports/rugbyleague_nrl"
                    }
                  ],
                  "meta": {
                    "bookmaker": null,
                    "brand": null,
                    "bookmaker_sports_feed": null,
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-feed/sports/{sport}": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Events + full market/selection odds for one sport, fanned out across every book that scrapes it.",
        "description": "Events + full market/selection odds for one sport, fanned out across every book that scrapes it.\n\n**Note:** Per-collection scan cap 100, independent of the caller's `limit`, so an in-memory bookmaker filter still has a full candidate pool. 60s in-memory per-instance cache layered under the 300s unstable_cache to dedupe burst traffic. bet_link attached AFTER slicing to `limit`, outside the cache, so only served selections pay the link-build cost.",
        "operationId": "get_odds_feed_sports_sport",
        "parameters": [
          {
            "name": "sport",
            "in": "path",
            "required": true,
            "description": "Sport slug, e.g. 'basketball_nba', 'soccer_epl'. Resolved via resolveSportSlug; 400 if unresolvable.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Canonical key or white-label alias.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "league",
            "in": "query",
            "required": false,
            "description": "League-specific slug, e.g. 'soccer_epl'; takes precedence over `competition`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "competition",
            "in": "query",
            "required": false,
            "description": "Substring match on competition name (used only if `league` not set).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "upcoming",
            "in": "query",
            "required": false,
            "description": "Only events with start_time in the future.",
            "schema": {
              "type": "boolean",
              "default": "false"
            }
          },
          {
            "name": "markets",
            "in": "query",
            "required": false,
            "description": "Set to 'false' to omit markets/selections from the response (event metadata only).",
            "schema": {
              "type": "boolean",
              "default": "true"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped per tier.",
            "schema": {
              "type": "integer",
              "default": "25 (free) / 1000 (api)"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "bookmaker_key": "sportsbet",
                      "sport_slug": "soccer_epl",
                      "event": "Arsenal vs Chelsea",
                      "start_time": "2026-09-03T19:30:00Z",
                      "competition": "English Premier League",
                      "league_slug": "soccer_epl",
                      "markets": [
                        {
                          "key": "h2h",
                          "selections": [
                            {
                              "name": "Arsenal",
                              "price": 2.1,
                              "bet_link": "https://www.sportsbet.com.au/..."
                            },
                            {
                              "name": "Draw",
                              "price": 3.4,
                              "bet_link": "https://www.sportsbet.com.au/..."
                            },
                            {
                              "name": "Chelsea",
                              "price": 3.5,
                              "bet_link": "https://www.sportsbet.com.au/..."
                            }
                          ]
                        }
                      ]
                    }
                  ],
                  "meta": {
                    "sport": "soccer_epl",
                    "bookmaker": null,
                    "brand": null,
                    "limit": 25,
                    "requested_limit": 25,
                    "total_matched": 1,
                    "truncated": false,
                    "scan_capped_bookmakers": [],
                    "direct_scrape": true,
                    "redistributable": true,
                    "license": "krokodds-direct-scrape"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/scraped-odds/bookmakers": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Alias of /api/v1/odds-feed/bookmakers.",
        "description": "Alias of /api/v1/odds-feed/bookmakers.\n\n**Note:** Directly re-exports { GET, OPTIONS } from ../../odds-feed/bookmakers/route \u2014 same handler, not a redirect. Historically was a 308 redirect but NextResponse.redirect() built the Location from internal request.url (localhost on Cloud Run/App Hosting), breaking external clients \u2014 now serves the handler directly.\n\nAlias of `/api/v1/odds-feed/bookmakers`.",
        "operationId": "get_scraped_odds_bookmakers",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [],
                  "meta": {
                    "count": 0
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/scraped-odds/racing": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Alias of /api/v1/odds-feed/racing.",
        "description": "Alias of /api/v1/odds-feed/racing.\n\n**Note:** Directly re-exports { GET, OPTIONS } from ../../odds-feed/racing/route \u2014 same handler, not a redirect.\n\nAlias of `/api/v1/odds-feed/racing`.",
        "operationId": "get_scraped_odds_racing",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [],
                  "meta": {
                    "count": 0
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/scraped-odds/sports": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Alias of /api/v1/odds-feed/sports.",
        "description": "Alias of /api/v1/odds-feed/sports.\n\n**Note:** Directly re-exports { GET, OPTIONS } from ../../odds-feed/sports/route \u2014 same handler, not a redirect.\n\nAlias of `/api/v1/odds-feed/sports`.",
        "operationId": "get_scraped_odds_sports",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [],
                  "meta": {
                    "count": 0
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/scraped-odds/sports/{sport}": {
      "get": {
        "tags": [
          "Odds Feed"
        ],
        "summary": "Alias of /api/v1/odds-feed/sports/{sport}.",
        "description": "Alias of /api/v1/odds-feed/sports/{sport}.\n\n**Note:** Directly re-exports { GET, OPTIONS } from ../../../odds-feed/sports/[sport]/route \u2014 same handler, not a redirect.\n\nAlias of `/api/v1/odds-feed/sports/{sport}`.",
        "operationId": "get_scraped_odds_sports_sport",
        "parameters": [
          {
            "name": "sport",
            "in": "path",
            "required": true,
            "description": "Passes through unchanged to the odds-feed handler.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [],
                  "meta": {
                    "count": 0
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/sports": {
      "get": {
        "tags": [
          "Sports"
        ],
        "summary": "List all supported sports/leagues with category and AU coverage level.",
        "description": "List all supported sports/leagues with category and AU coverage level.\n\n**Note:** No feature-gate/tierAllowsFeature check at all \u2014 only API-key auth + rate limit, no credit-cost debit path (verifyApiKey called without a cost arg). Available to any valid tier. `category` filter is normalised (case/space/dash/underscore insensitive) before matching. `refresh_interval_seconds` is a static heuristic (600 for keys ending `_winner`/`_preseason`, else 60) \u2014 not measured.",
        "operationId": "get_sports",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Filter by sport category, case/punctuation-insensitive (e.g. \"AFL\", \"au_sports\", \"Soccer\", \"Tennis\").",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "key": "aussierules_afl",
                      "label": "AFL",
                      "category": "AU Sports",
                      "au_coverage": "high",
                      "refresh_interval_seconds": 60
                    },
                    {
                      "key": "rugbyleague_nrl",
                      "label": "NRL",
                      "category": "AU Sports",
                      "au_coverage": "high",
                      "refresh_interval_seconds": 60
                    },
                    {
                      "key": "basketball_nba",
                      "label": "NBA",
                      "category": "Basketball",
                      "au_coverage": "high",
                      "refresh_interval_seconds": 60
                    },
                    {
                      "key": "tennis_atp_madrid_open",
                      "label": "Madrid Open (ATP)",
                      "category": "Tennis",
                      "au_coverage": "medium",
                      "refresh_interval_seconds": 60
                    },
                    {
                      "key": "soccer_epl",
                      "label": "Premier League",
                      "category": "Soccer",
                      "au_coverage": "high",
                      "refresh_interval_seconds": 60
                    }
                  ],
                  "meta": {
                    "total": 5,
                    "categories": [
                      "AU Sports",
                      "Basketball",
                      "Tennis",
                      "Soccer"
                    ],
                    "timestamp": "2026-09-03T04:00:00.000Z"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/sports/form": {
      "get": {
        "tags": [
          "Sports"
        ],
        "summary": "Form deep-dive combining recent results, head-to-head, venue form and last-5/last-10 trend stats for teams, players, and",
        "description": "Form deep-dive combining recent results, head-to-head, venue form and last-5/last-10 trend stats for teams, players, and horses.\n\n**Note:** Fans out across 5 Supabase sources (player_historical_stats, racing_runner_stats, team_game_log, game_results, player_props_results) and dedupes by synthetic id. `player`/`venue` filters only apply to the racing (horse) branch; `team` narrows sport-scoped sources. Cached 600s via unstable_cache, floored by v1Revalidate (min 300s).",
        "operationId": "get_sports_form",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Sport to analyse, e.g. \"aussierules_afl\", \"rugbyleague_nrl\", or \"horse_racing\". Case-insensitive.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Team name filter, partial/substring match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player",
            "in": "query",
            "required": false,
            "description": "Player or horse runner name filter, partial match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Venue filter, partial match \u2014 racing branch only.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results, clamped 1-200 (MAX_LIMIT).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "team_game_log_aussierules_afl_geelong",
                      "entity_type": "team",
                      "entity_name": "geelong",
                      "sport_key": "aussierules_afl",
                      "recent_results": [
                        "W",
                        "W",
                        "L",
                        "W",
                        "W"
                      ],
                      "win_rate": 0.8,
                      "streak": "W2",
                      "venue_form": null,
                      "h2h_record": null,
                      "last_5": {
                        "games": 5,
                        "wins": 4,
                        "win_pct": 0.8,
                        "points_for_avg": 92.4,
                        "points_against_avg": 71.2,
                        "margin_avg": 21.2
                      },
                      "last_10": {
                        "games": 10,
                        "wins": 7,
                        "win_pct": 0.7,
                        "points_for_avg": 89.1,
                        "points_against_avg": 76.8,
                        "margin_avg": 12.3
                      },
                      "notes": "Strong recent form. On a W2 winning streak",
                      "updated_at": ""
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "license": "krokodds-derived",
                    "filter": {
                      "sport_key": "aussierules_afl",
                      "team": "geelong",
                      "player": null,
                      "venue": null
                    },
                    "note": "Form deep-dive from game_results, player_props_results, and team logs \u2014 last 5/10 game averages, win rates, streaks, and score trends.",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/sports/injuries": {
      "get": {
        "tags": [
          "Sports"
        ],
        "summary": "Cross-sport injury report \u2014 unified schema across all supported sports, sourced from injuries_current.",
        "description": "Cross-sport injury report \u2014 unified schema across all supported sports, sourced from injuries_current.\n\n**Note:** Comment header says it 'extends /v1/injuries (racing-focused)' but this route reads only injuries_current (Supabase), not the racing injuries collection. `sport_key` also accepts a legacy `sport` alias param. Cached 300s.",
        "operationId": "get_sports_injuries",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport (AFL, NRL, NFL, NBA, NHL, MLB, etc). Alias: `sport`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Filter by team name, partial match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by raw injury status string (e.g. \"out\", \"doubtful\", \"questionable\", \"probable\"), exact match case-insensitive.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results, clamped 1-500 (MAX_LIMIT).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "inj_nba_2026090301",
                      "sport_key": "basketball_nba",
                      "player_name": "Jaylen Brown",
                      "player_slug": "jaylen-brown",
                      "team": "Boston Celtics",
                      "status": "Questionable",
                      "status_severity": "questionable",
                      "reason": "Ankle soreness",
                      "body_part": "Ankle",
                      "date": "2026-09-03",
                      "season": 2026,
                      "source": "ESPN",
                      "updated_at": "2026-09-03T02:15:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "license": "krokodds-derived",
                    "filter": {
                      "sport_key": "basketball_nba",
                      "team": null,
                      "status": null
                    },
                    "sport_breakdown": {
                      "basketball_nba": 1
                    },
                    "note": "Cross-sport injury aggregation from ESPN, official team reports, and racing stewards.",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/sports/standings": {
      "get": {
        "tags": [
          "Sports"
        ],
        "summary": "NHL and MLB league standings in a unified schema.",
        "description": "NHL and MLB league standings in a unified schema.\n\n**Note:** Only two sports covered (NHL from external_nhl_apiweb_standings, MLB from external_mlb_statsapi_standings) despite the generic path name \u2014 no AFL/NRL/NBA/NFL standings here. Cached 3600s. `points` is null for MLB rows, `games_behind`/`elimination_number` null for NHL rows (fields are sport-specific but unified into one shape).",
        "operationId": "get_sports_standings",
        "parameters": [
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "\"nhl\" or \"mlb\"; omit for both, lowercased.",
            "schema": {
              "type": "string",
              "default": "both (nhl+mlb)"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows, clamped 1-500 (MAX_LIMIT).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "sport": "MLB",
                      "team_id": "147",
                      "team": "New York Yankees",
                      "division": "AL East",
                      "league": "American League",
                      "group": null,
                      "wins": 84,
                      "losses": 58,
                      "points": null,
                      "win_pct": 0.592,
                      "games_behind": "-",
                      "streak": "W3",
                      "home": "45-25",
                      "away": "39-33",
                      "last_ten": "7-3",
                      "rank": 1,
                      "runs_scored": 712,
                      "runs_allowed": 601,
                      "run_differential": 111,
                      "clinched": false,
                      "elimination_number": null,
                      "updated_at": "2026-09-03T01:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "license": "krokodds-derived",
                    "sport": "mlb",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/sports/weather-impact": {
      "get": {
        "tags": [
          "Sports"
        ],
        "summary": "Weather impact analysis for upcoming events \u2014 correlates Open-Meteo forecasts / racing-meeting weather with tagged impac",
        "description": "Weather impact analysis for upcoming events \u2014 correlates Open-Meteo forecasts / racing-meeting weather with tagged impact notes (wind, rain, heat, cold, humidity, storm risk).\n\n**Note:** Two sources merged: external_openmeteo_forecasts (multi-sport outdoor events, skips retractable-roof venues) and racing_meetings (embedded BOM/Open-Meteo weather). `impact_tags` are rule-based thresholds (wind>30km/h, rain>5mm, temp>38C or <5C, humidity>80%, summary contains storm/thunder). Cached 300s.",
        "operationId": "get_sports_weather_impact",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport, e.g. \"AFL\", \"NRL\", \"horse_racing\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Venue filter, partial match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Event date filter, YYYY-MM-DD, exact match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results, clamped 1-200 (MAX_LIMIT).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "openmeteo_rugbyleague_nrl_accorstadium_20260906",
                      "sport_key": "NRL",
                      "venue": "Accor Stadium",
                      "date": "2026-09-06",
                      "race_type": null,
                      "track_condition": null,
                      "weather_summary": "Showers clearing, gusty southerly",
                      "temp_c": 16,
                      "humidity_pct": 78,
                      "rain_24h_mm": 6.2,
                      "wind_kmh": 34,
                      "wind_dir": null,
                      "impact_tags": [
                        "high_wind",
                        "heavy_rain"
                      ],
                      "impact_notes": "Strong wind (34 km/h) favours inside barriers and wind-assisted runners. Significant rain (6.2mm) expected \u2014 prefer on-pace runners and firm-track specialists",
                      "updated_at": "2026-09-03T03:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "license": "krokodds-derived",
                    "filter": {
                      "sport_key": "NRL",
                      "venue": null,
                      "date": null
                    },
                    "impact_summary": {
                      "high_wind": 1,
                      "heavy_rain": 1
                    },
                    "note": "Weather impact analysis from Open-Meteo forecasts and BOM/racing meeting data \u2014 temperature, wind, rain, humidity correlated with event conditions.",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/predictions": {
      "get": {
        "tags": [
          "Tips & Predictions"
        ],
        "summary": "Deprecated alias of /api/v1/tips.",
        "description": "Deprecated alias of /api/v1/tips.\n\n**Note:** QUIRK: the file's own doc-comment claims it \"proxies to tips with a sport_key=soccer filter for backwards compatibility\", but the actual code is a bare re-export (`export { GET, OPTIONS } from '../tips/route'`) \u2014 NO soccer filter is applied; it is byte-identical behaviour to /api/v1/tips, all sports included. Comment is stale/misleading. Marked deprecated 2026-08-06, \"will be removed in a future version\" (no removal date set as of 2026-09-03). Same params/response/tier as /api/v1/tips.\n\nAlias of `/api/v1/tips`.",
        "operationId": "get_predictions",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Same as /api/v1/tips \u2014 filters by sport key despite the doc-comment implying a fixed soccer filter (that filter does not actually exist in code).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_confidence",
            "in": "query",
            "required": false,
            "description": "Same as /api/v1/tips.",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Same as /api/v1/tips.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "include_settled",
            "in": "query",
            "required": false,
            "description": "Same as /api/v1/tips.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "aussierules_afl-geelong-carlton-20260906t0740",
                      "event_id": "e_afl_20260906_gee_car",
                      "sport_key": "aussierules_afl",
                      "sport": "AFL",
                      "home_team": "Geelong Cats",
                      "away_team": "Carlton Blues",
                      "commence_time": "2026-09-06T07:40:00.000Z",
                      "pick": "Geelong Cats -12.5",
                      "pick_side": "home",
                      "confidence": 4,
                      "confidence_label": "High",
                      "consensus_prob_home": 0.71,
                      "consensus_prob_away": 0.29,
                      "predicted_margin": 18.4,
                      "predicted_margin_side": "home",
                      "resolution": "pending",
                      "actual_winner": null
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "include_settled": false,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/predictions/confidence": {
      "get": {
        "tags": [
          "Tips & Predictions"
        ],
        "summary": "Model confidence breakdown across racing, sports, and player props, plus 30-day historical accuracy by confidence bucket",
        "description": "Model confidence breakdown across racing, sports, and player props, plus 30-day historical accuracy by confidence bucket.\n\n**Note:** Gates on the 'tips' feature (tierAllowsFeature(tier,'tips')) even though the path is under /predictions. `force-dynamic`/`revalidate=0` \u2014 NOT cached via unstable_cache (response header still claims `Cache-Control: public, max-age=300` which is misleading given no server cache). Sports and props predictions are ONLY loaded if `sport`/`event_id` params are respectively provided \u2014 with type=all and neither param, only racing predictions populate. `summary.breakdown` is always `{}` in the current code (allBreakdowns is declared but never populated \u2014 dead aggregation). historicalAccuracy tries Supabase settled_tips first, falls back to Firestore.",
        "operationId": "get_predictions_confidence",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "\"racing\" | \"sports\" | \"props\" | \"all\". Lowercased.",
            "schema": {
              "type": "string",
              "default": "all"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Sport key \u2014 required for the sports branch to run (ignored if type excludes sports).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "YYYY-MM-DD \u2014 racing branch only.",
            "schema": {
              "type": "string",
              "default": "today (AU)"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Specific event id \u2014 required for the props branch to run.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max predictions per type-branch; capped 20 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "success": true,
                  "meta": {
                    "type": "racing",
                    "sport": null,
                    "date": "2026-09-03",
                    "eventId": null,
                    "generatedAt": "2026-09-03T04:00:00.000Z",
                    "cacheTtl": 300
                  },
                  "summary": {
                    "overallConfidence": 0.74,
                    "breakdown": {},
                    "dataSources": [
                      "racing_form",
                      "sectionals",
                      "barrier_trial",
                      "weather"
                    ],
                    "historicalAccuracy": {
                      "totalPredictions": 812,
                      "correctPredictions": 471,
                      "accuracyRate": 0.58,
                      "byConfidenceBucket": {
                        "high": {
                          "total": 210,
                          "correct": 148,
                          "rate": 0.7
                        },
                        "medium": {
                          "total": 380,
                          "correct": 218,
                          "rate": 0.57
                        },
                        "low": {
                          "total": 222,
                          "correct": 105,
                          "rate": 0.47
                        }
                      }
                    }
                  },
                  "predictions": [
                    {
                      "id": "racing-Written By-R6",
                      "type": "racing",
                      "confidence": 0.81,
                      "dataSources": [
                        "racing_form",
                        "sectionals",
                        "barrier_trial"
                      ],
                      "breakdown": {
                        "weather": 0.6,
                        "barrier": 0.75,
                        "sectionals": 0.88,
                        "pedigree": 0.55,
                        "ensemble": 0.81
                      },
                      "reasoning": "Strong recent sectionals and barrier draw at Flemington R6."
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/tips": {
      "get": {
        "tags": [
          "Tips & Predictions"
        ],
        "summary": "In-house model tips/predictions across all covered sports (active by default, or settled history with include_settled=tr",
        "description": "In-house model tips/predictions across all covered sports (active by default, or settled history with include_settled=true).\n\n**Note:** Uses the OLDER hand-rolled auth preamble (not guardV1Request/makeV1Route) \u2014 no explicit credit-cost debit in this file, verifyApiKey called with default cost. Merges canonical tips_rounds engine picks over legacy gameday_tips (canonical wins on conflicting fixtures; legacy only fills 7 leagues the engine doesn't cover) \u2014 active-tips path only, not for include_settled=true which reads settled_tips directly. Free tier cache 120s, api tier 30s (both floored to 300s by v1Revalidate). Free tier capped at 50 rows/request, api tier 1000.",
        "operationId": "get_tips",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key. Alias: `sport`. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_confidence",
            "in": "query",
            "required": false,
            "description": "Minimum confidence 1-5, clamped.",
            "schema": {
              "type": "integer",
              "default": 1
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows; capped 50 (free) / 1000 (api) by getMaxLimit().",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "include_settled",
            "in": "query",
            "required": false,
            "description": "\"true\" to return settled_tips history (with resolution/actual_winner) instead of pending active tips.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "aussierules_afl-geelong-carlton-20260906t0740",
                      "event_id": "e_afl_20260906_gee_car",
                      "sport_key": "aussierules_afl",
                      "sport": "AFL",
                      "home_team": "Geelong Cats",
                      "away_team": "Carlton Blues",
                      "commence_time": "2026-09-06T07:40:00.000Z",
                      "pick": "Geelong Cats -12.5",
                      "pick_side": "home",
                      "confidence": 4,
                      "confidence_label": "High",
                      "consensus_prob_home": 0.71,
                      "consensus_prob_away": 0.29,
                      "predicted_margin": 18.4,
                      "predicted_margin_side": "home",
                      "resolution": "pending",
                      "actual_winner": null
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "include_settled": false,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/tips/accuracy": {
      "get": {
        "tags": [
          "Tips & Predictions"
        ],
        "summary": "Historical hit-rate / track-record for the tips model, overall and broken down by confidence bucket, optionally scoped t",
        "description": "Historical hit-rate / track-record for the tips model, overall and broken down by confidence bucket, optionally scoped to one sport.\n\n**Note:** Reads model_track_record. Per-sport lookup filters on the `sport_key` column (indexed); the unscoped (all-sport) query has no column for `total` (lives in JSONB) so it sorts in memory after fetching up to `scanSize` (min(limit,100)) rows \u2014 an all-sports call is NOT a true global ranking beyond that scan window. Same free/api cache tiers as /tips (600s/120s, floored to 300s).",
        "operationId": "get_tips_accuracy",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter to one sport. Alias: `sport`. Lowercased.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows; capped 50 (free) / 1000 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "sport_key": "rugbyleague_nrl",
                      "wins": 142,
                      "losses": 88,
                      "pushes": 3,
                      "total": 233,
                      "decided": 230,
                      "hit_rate": 0.6174,
                      "by_confidence": [
                        {
                          "confidence": "3",
                          "wins": 51,
                          "losses": 44,
                          "pushes": 1,
                          "total": 96,
                          "hit_rate": 0.5368
                        },
                        {
                          "confidence": "4",
                          "wins": 63,
                          "losses": 32,
                          "pushes": 1,
                          "total": 96,
                          "hit_rate": 0.6632
                        },
                        {
                          "confidence": "5",
                          "wins": 28,
                          "losses": 12,
                          "pushes": 1,
                          "total": 41,
                          "hit_rate": 0.7
                        }
                      ],
                      "last_settled_at": "2026-09-02T22:30:00.000Z",
                      "updated_at": "2026-09-02T22:31:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": "rugbyleague_nrl",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/tennis/elo": {
      "get": {
        "tags": [
          "Tennis"
        ],
        "summary": "Overall or surface-specific Elo ratings for ATP/WTA players (UTS/tennisabstract-derived).",
        "description": "Overall or surface-specific Elo ratings for ATP/WTA players (UTS/tennisabstract-derived).\n\n**Note:** Two very differently-shaped response rows depending on `surface`: with `surface` set it reads external_utstat_surface and returns {id, player_name, player_id, surface, elo}; without it, reads external_utstat_elo and returns the richer {rank, player_name, country_code, elo_rating, best_rank, best_rating, points_diff} shape \u2014 field names differ (elo vs elo_rating) between the two modes. `player` filter fetches up to limit*6 rows first then filters in memory (partial match).",
        "operationId": "get_tennis_elo",
        "parameters": [
          {
            "name": "player",
            "in": "query",
            "required": false,
            "description": "Player name filter, partial/substring, case-insensitive.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "surface",
            "in": "query",
            "required": false,
            "description": "\"hard\" | \"clay\" | \"grass\" \u2014 switches to the surface-Elo table/shape. Any other value is ignored (falls back to overall Elo).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows, clamped 1-500 (MAX_LIMIT).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "atp_sinner_j",
                      "rank": 1,
                      "player_name": "Jannik Sinner",
                      "player_id": "s0ag",
                      "country_code": "ITA",
                      "elo_rating": 2312,
                      "best_rank": 1,
                      "best_rating": 2340,
                      "points_diff": 45
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "license": "krokodds-derived",
                    "player": null,
                    "surface": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/tennis/fixtures": {
      "get": {
        "tags": [
          "Tennis"
        ],
        "summary": "Upcoming/scheduled tennis fixtures by tour.",
        "description": "Upcoming/scheduled tennis fixtures by tour.\n\n**Note:** Gated on the 'historical' feature despite being a forward-looking fixtures list (not a historical archive) \u2014 free-tier keys get a 402 tier-gate response here. creditCost=2 (archive-weighted, per the makeV1Route cfg comment: standard 1 / archive 5 / bulk 25 \u2014 this is priced above standard but not full archive). Built via makeV1Route/tennis_fixtures collection.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_tennis_fixtures",
        "parameters": [
          {
            "name": "tour",
            "in": "query",
            "required": false,
            "description": "Tour filter, e.g. \"ATP\" or \"WTA\" \u2014 exact match on the `tour` JSONB field.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows; capped 50 (free) / 200 (api) per limitByTier.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "tour": "ATP",
                      "tournament": "US Open",
                      "round": "QF",
                      "player1": "Carlos Alcaraz",
                      "player2": "Alexander Zverev",
                      "date": "2026-09-04",
                      "surface": "hard",
                      "venue": "USTA Billie Jean King National Tennis Center"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 100,
                    "requested_limit": 100,
                    "tour": "ATP",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 200,
                      "remaining": 998,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/tennis/matches": {
      "get": {
        "tags": [
          "Tennis"
        ],
        "summary": "Historical ATP/WTA singles match results (Jeff Sackmann dataset), filterable by season/tour/surface.",
        "description": "Historical ATP/WTA singles match results (Jeff Sackmann dataset), filterable by season/tour/surface.\n\n**Note:** Reads external_sackmann_tennis_matches. Only `season` is an indexed equality filter pushed to Supabase; `tour`/`surface` are applied in-memory after over-fetching (limit*6) when set \u2014 per the file's own query-design comment this keeps it on an indexed column. Throws V1BadRequest (400) if `season` is non-numeric.",
        "operationId": "get_tennis_matches",
        "parameters": [
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Season year, e.g. 2026. Must parse as a number or 400 V1BadRequest is returned.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "tour",
            "in": "query",
            "required": false,
            "description": "\"atp\" or \"wta\", case-insensitive, in-memory filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "surface",
            "in": "query",
            "required": false,
            "description": "\"hard\" | \"clay\" | \"grass\", case-insensitive, in-memory filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows; capped 100 (free) / 500 (api) per limitByTier.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "season": 2026,
                      "tour": "atp",
                      "surface": "hard",
                      "tournament": "US Open",
                      "round": "SF",
                      "winner_name": "Jannik Sinner",
                      "loser_name": "Novak Djokovic",
                      "score": "6-4 3-6 7-6(5) 6-2",
                      "date": "2026-09-05"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "season": 2026,
                    "tour": "atp",
                    "surface": "hard",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/tennis/rankings": {
      "get": {
        "tags": [
          "Tennis"
        ],
        "summary": "Current ATP/WTA player rankings.",
        "description": "Current ATP/WTA player rankings.\n\n**Note:** Gated on 'historical' despite being a current-state rankings list, not an archive \u2014 free-tier keys get 402. No filter params at all beyond `limit` (no tour/player filter exposed). creditCost=2 like /tennis/fixtures.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_tennis_rankings",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows; capped 100 (free) / 500 (api) per limitByTier.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "ranking": 1,
                      "tour": "ATP",
                      "player_name": "Jannik Sinner",
                      "country": "ITA",
                      "points": 11330,
                      "movement": 0
                    },
                    {
                      "ranking": 1,
                      "tour": "WTA",
                      "player_name": "Iga Swiatek",
                      "country": "POL",
                      "points": 9945,
                      "movement": 1
                    }
                  ],
                  "meta": {
                    "count": 2,
                    "tier": "api",
                    "limit": 100,
                    "requested_limit": 100,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 500,
                      "remaining": 998,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/tennis/surface-stats": {
      "get": {
        "tags": [
          "Tennis"
        ],
        "summary": "Tennis surface-specific Elo/win-rate stats (hard/clay/grass) for ATP/WTA players.",
        "description": "Tennis surface-specific Elo/win-rate stats (hard/clay/grass) for ATP/WTA players.\n\n**Note:** Reads the SAME table (external_utstat_surface) and returns the SAME shape ({id, player_name, player_id, surface, elo}) as /api/v1/tennis/elo?surface=X \u2014 effectively a duplicate/alias of that mode with a dedicated path. No win-rate field despite the doc-comment and summary saying \"win rates and Elo by surface\" \u2014 only `elo` is actually returned, no win_pct/matches_played field exists in code.",
        "operationId": "get_tennis_surface_stats",
        "parameters": [
          {
            "name": "player",
            "in": "query",
            "required": false,
            "description": "Player name filter, partial/substring, case-insensitive.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "surface",
            "in": "query",
            "required": false,
            "description": "\"hard\" | \"clay\" | \"grass\" \u2014 invalid values are silently ignored (no filter applied).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows, clamped 1-500 (MAX_LIMIT).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "atp_alcaraz_c_clay",
                      "player_name": "Carlos Alcaraz",
                      "player_id": "a0e2",
                      "surface": "clay",
                      "elo": 2280
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "license": "krokodds-derived",
                    "player": null,
                    "surface": "clay",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 949,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/historical/afl": {
      "get": {
        "tags": [
          "Historical"
        ],
        "summary": "AFL historical archive \u2014 player game logs (2010+) or match results (2018+ via Akareen, pre-2018 via AFL Tables backfill)",
        "description": "AFL historical archive \u2014 player game logs (2010+) or match results (2018+ via Akareen, pre-2018 via AFL Tables backfill).\n\n**Note:** Cache: free=3600s, api=1800s. kind=match with year<2018 silently swaps collection to external_afltables_afl_matches (identically shaped). Uses shared makeV1Route wrapper (historical-window clamp on since/from, empty-result credit refund, 204 on empty).\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_historical_afl",
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "'player' or 'match'. 400 if any other value.",
            "schema": {
              "type": "string",
              "default": "player"
            }
          },
          {
            "name": "year",
            "in": "query",
            "required": false,
            "description": "Season year filter. Values <2018 with kind=match route to the AFL Tables backfill collection instead of Akareen.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "round",
            "in": "query",
            "required": false,
            "description": "Round filter, only applied when kind=player (post-fetch filter, over-fetches limit*4 rows).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Row cap. Tier-capped: free=100, api=500.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "afl_2026_r22_j_daicos_collingwood",
                      "player": "Josh Daicos",
                      "team": "Collingwood",
                      "year": "2026",
                      "round": "22",
                      "disposals": 28,
                      "goals": 1,
                      "kicks": 19,
                      "handballs": 9,
                      "marks": 6,
                      "tackles": 4,
                      "fixtureId": 923841
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 500,
                    "requested_limit": 100,
                    "kind": "player",
                    "year": 2026,
                    "round": "22",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 500,
                      "remaining": 8421,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/historical/nrl": {
      "get": {
        "tags": [
          "Historical"
        ],
        "summary": "NRL historical archive \u2014 player/match data 2010-2023 (uselessnrlstats GH archive) or 2024+ modern data (NRL.com Match Ce",
        "description": "NRL historical archive \u2014 player/match data 2010-2023 (uselessnrlstats GH archive) or 2024+ modern data (NRL.com Match Centre).\n\n**Note:** Cache: free=3600s, api=1800s. Three distinct underlying Supabase collections keyed off 'kind', each with its own season field name (competitionYear vs season).\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_historical_nrl",
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "'player' (2010-2023 archive), 'match' (2010-2023 archive), or 'modern' (2024+ current data). 400 on any other value.",
            "schema": {
              "type": "string",
              "default": "modern"
            }
          },
          {
            "name": "season / year",
            "in": "query",
            "required": false,
            "description": "Season filter; 'season' checked first, falls back to 'year'.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=100, api=500.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "nrl_2026_r24_n_hynes_sharks",
                      "player": "Nicho Hynes",
                      "team": "Cronulla Sharks",
                      "season": "2026",
                      "tries": 1,
                      "tackles": 22,
                      "runMetres": 118,
                      "tackleBreaks": 3,
                      "fixtureId": 448120
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 500,
                    "requested_limit": 100,
                    "kind": "modern",
                    "season": 2026,
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 500,
                      "remaining": 8420,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/historical/player-game-log": {
      "get": {
        "tags": [
          "Historical"
        ],
        "summary": "Full accumulated game-by-game history for a player+stat combo (not a fixed recent-form window \u2014 depth varies per player)",
        "description": "Full accumulated game-by-game history for a player+stat combo (not a fixed recent-form window \u2014 depth varies per player).\n\n**Note:** Does NOT use makeV1Route \u2014 hand-rolled handler, no explicit creditCost field/mechanism (unlike the shared wrapper's credit-cost headers). recentGames written append-only via Firestore arrayUnion so a doc holds FULL history, not a 15-game window; history_from/history_to expose real span. Cache: free=600s, api=120s. Over-fetches limit*4 (capped 2000) then filters player/team in memory.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_historical_player_game_log",
        "parameters": [
          {
            "name": "sport_key / sport",
            "in": "query",
            "required": true,
            "description": "e.g. basketball_nba, americanfootball_nfl. 400 if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match, filtered post-fetch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match, filtered post-fetch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "stat_key / stat",
            "in": "query",
            "required": false,
            "description": "Exact-match stat key filter (e.g. 'points', 'disposals').",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Filters recentGames[] to dates >= since. Malformed values ignored. Clamped forward by clampHistoricalFrom() to the key's historicalDays window.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=50, api=2000.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "basketball_nba__nikola_jokic__points",
                      "sport_key": "basketball_nba",
                      "player": "Nikola Jokic",
                      "team": "Denver Nuggets",
                      "stat_key": "points",
                      "games_count": 3,
                      "history_to": "2026-04-12",
                      "history_from": "2025-10-22",
                      "recent_games": [
                        {
                          "date": "2026-04-12",
                          "opponent": "Los Angeles Lakers",
                          "is_home": true,
                          "stat_value": 31,
                          "fixture_id": 501233
                        },
                        {
                          "date": "2026-04-09",
                          "opponent": "Phoenix Suns",
                          "is_home": false,
                          "stat_value": 27,
                          "fixture_id": 501198
                        },
                        {
                          "date": "2026-04-06",
                          "opponent": "Golden State Warriors",
                          "is_home": true,
                          "stat_value": 24,
                          "fixture_id": 501167
                        }
                      ],
                      "updated_at": "2026-04-13T02:11:04.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 2000,
                    "requested_limit": 100,
                    "sport_key": "basketball_nba",
                    "stat_key": "points",
                    "since": "2025-10-22",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 200,
                      "remaining": 8419,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/historical/player-stats": {
      "get": {
        "tags": [
          "Historical"
        ],
        "summary": "Season averages, splits, and market lines for a player from the historical player-stats archive; single-slug lookup or s",
        "description": "Season averages, splits, and market lines for a player from the historical player-stats archive; single-slug lookup or sport-wide/team-filtered listing.\n\n**Note:** Cache: free=3600s, api=1800s. Has BOTH loadSupabase and loadBq (BQ fallback only fires if BQ_ENABLED and Supabase throws). Slug lookup bypasses the limit param entirely.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_historical_player_stats",
        "parameters": [
          {
            "name": "sport_key / sport",
            "in": "query",
            "required": true,
            "description": "400 'sport_key parameter required' if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "Canonical player slug. If present, does a direct id lookup on `${sportKey}__${slug}` (limit ignored, returns 0 or 1 row).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Case-insensitive exact match on team, filtered post-fetch (only applies when slug absent).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=100, api=500.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "basketball_nba__jayson_tatum",
                      "name": "Jayson Tatum",
                      "slug": "jayson_tatum",
                      "sport_key": "basketball_nba",
                      "team": "Boston Celtics",
                      "position": "SF",
                      "active": true,
                      "coverage": {
                        "seasons": [
                          "2023",
                          "2024",
                          "2025",
                          "2026"
                        ]
                      },
                      "season_averages": {
                        "points": 26.8,
                        "rebounds": 8.1,
                        "assists": 4.6
                      },
                      "splits": {
                        "home": {
                          "points": 27.9
                        },
                        "away": {
                          "points": 25.7
                        }
                      },
                      "market_lines": {
                        "points": 26.5,
                        "rebounds": 8.0
                      },
                      "updated_at": "2026-09-02T22:04:11.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 500,
                    "requested_limit": 100,
                    "sport_key": "basketball_nba",
                    "slug": "jayson_tatum",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 500,
                      "remaining": 8418,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/historical/shot-chart": {
      "get": {
        "tags": [
          "Historical"
        ],
        "summary": "NBA shot chart \u2014 half-court shot coordinates for a player, optionally filtered to one season.",
        "description": "NBA shot chart \u2014 half-court shot coordinates for a player, optionally filtered to one season.\n\n**Note:** Cache: free=3600s, api=1800s. One doc per (season, player) in player_shot_charts; data.shots is the shot-point array. NBA-only (Wave 3 feature).\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_historical_shot_chart",
        "parameters": [
          {
            "name": "slug / player",
            "in": "query",
            "required": true,
            "description": "Canonical player slug (spaces normalized to underscores, lowercased). 400 'slug is required' if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Exact-match season filter, applied in-memory over an over-fetched (limit*4) window.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=20, api=60 (much lower cap than other historical routes).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "2026__nikola_jokic",
                      "playerSlug": "nikola_jokic",
                      "season": "2026",
                      "team": "Denver Nuggets",
                      "shots": [
                        {
                          "x": 12.4,
                          "y": 8.1,
                          "made": true,
                          "value": 2,
                          "distance_ft": 14,
                          "date": "2026-04-12"
                        },
                        {
                          "x": -21.0,
                          "y": 22.6,
                          "made": false,
                          "value": 3,
                          "distance_ft": 27,
                          "date": "2026-04-12"
                        }
                      ],
                      "updated_at": "2026-04-13T01:50:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 60,
                    "requested_limit": 100,
                    "slug": "nikola_jokic",
                    "season": "2026",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 60,
                      "remaining": 8417,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/historical/team-game-log": {
      "get": {
        "tags": [
          "Historical"
        ],
        "summary": "Historical team-level game results (scores, periods, status) with date-range filtering.",
        "description": "Historical team-level game results (scores, periods, status) with date-range filtering.\n\n**Note:** Does NOT use makeV1Route (hand-rolled handler, mirrors historical/player-game-log structure) \u2014 no explicit per-request creditCost field. Cache: free=600s, api=120s. Ordered DESC by date server-side (unlike most sibling historical routes which have no explicit order).\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_historical_team_game_log",
        "parameters": [
          {
            "name": "sport_key / sport",
            "in": "query",
            "required": true,
            "description": "400 if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match against home OR away team, filtered post-fetch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Range start. Clamped forward by clampHistoricalFrom() to the key's historicalDays window; malformed values ignored.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Range end, validated YYYY-MM-DD only (not clamped).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=100, api=5000 (highest cap of all historical routes).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "americanfootball_nfl_2026_wk1_kc_bal",
                      "sport_key": "americanfootball_nfl",
                      "fixture_id": 700421,
                      "date": "2026-09-05",
                      "home_team": "Baltimore Ravens",
                      "away_team": "Kansas City Chiefs",
                      "home_score": 24,
                      "away_score": 27,
                      "periods": {
                        "q1": 7,
                        "q2": 3,
                        "q3": 7,
                        "q4": 7
                      },
                      "league": "NFL",
                      "league_id": 1,
                      "country": "USA",
                      "status": "final",
                      "updated_at": "2026-09-06T03:10:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 5000,
                    "requested_limit": 100,
                    "sport_key": "americanfootball_nfl",
                    "since": "2025-09-01",
                    "until": null,
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 500,
                      "remaining": 8416,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/reference/headshots": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "Static player headshot photo URLs by sport, optionally a single player by slug.",
        "description": "Static player headshot photo URLs by sport, optionally a single player by slug.\n\n**Note:** Cache: free=86400s, api=43200s (24h/12h, longest TTLs of any v1 route \u2014 static reference data). No explicit creditCost set in config \u2192 defaults to 1 (makeV1Route default).",
        "operationId": "get_reference_headshots",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": true,
            "description": "400 'sport_key parameter required' if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "Single-player lookup via doc id `${sportKey}_${slug}` (bypasses limit).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=200, api=1000.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "basketball_nba_jayson_tatum",
                      "name": "Jayson Tatum",
                      "slug": "jayson_tatum",
                      "sport_key": "basketball_nba",
                      "photo_url": "https://a.espncdn.com/i/headshots/nba/players/full/4066261.png",
                      "source": "espn",
                      "espn_athlete_id": 4066261
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 200,
                    "requested_limit": 100,
                    "sport_key": "basketball_nba",
                    "slug": "jayson_tatum",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 200,
                      "remaining": 998,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/reference/metadata": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "Team metadata (name, league, venue, etc.) from the team_metadata collection, optionally filtered by sport.",
        "description": "Team metadata (name, league, venue, etc.) from the team_metadata collection, optionally filtered by sport.\n\n**Note:** Cache: free=86400s, api=43200s. Results ordered ASC by $.name. Row shape is `r.data ?? r` \u2014 raw stored doc data passed through largely unmapped (no field renaming like other routes).\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_reference_metadata",
        "parameters": [
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Exact-match sport filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=50, api=200.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "name": "Boston Celtics",
                      "sport": "basketball_nba",
                      "league": "NBA",
                      "conference": "Eastern",
                      "division": "Atlantic",
                      "venue": "TD Garden",
                      "city": "Boston",
                      "country": "USA",
                      "abbreviation": "BOS"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 200,
                    "requested_limit": 100,
                    "sport": "basketball_nba",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 200,
                      "remaining": 8415,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/reference/players": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "Player metadata lookup by name and/or sport from the player_metadata collection.",
        "description": "Player metadata lookup by name and/or sport from the player_metadata collection.\n\n**Note:** Cache: free=86400s, api=43200s. Ordered DESC by $.fetchedAt (most recently updated first). Row shape passthrough (`r.data ?? r`).",
        "operationId": "get_reference_players",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Exact-match player name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Exact-match sport filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=50, api=200.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "name": "Jayson Tatum",
                      "sport": "basketball_nba",
                      "team": "Boston Celtics",
                      "position": "SF",
                      "jersey_number": 0,
                      "height": "6'8\"",
                      "weight": "210 lbs",
                      "birth_date": "1998-03-03",
                      "fetchedAt": "2026-09-01T10:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 100,
                    "name": "Jayson Tatum",
                    "sport": "basketball_nba",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 997,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/reference/team-logos": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "Static team logo URLs and name aliases by sport, backfilled from team_metadata when the primary logo source is sparse.",
        "description": "Static team logo URLs and name aliases by sport, backfilled from team_metadata when the primary logo source is sparse.\n\n**Note:** Cache: free=86400s, api=43200s. When primary external_team_logos rows < limit, best-effort fills gaps from team_metadata (logoUrl/badgeUrl fields), deduping by team name; fallback failures are swallowed silently. No explicit creditCost \u2192 defaults to 1.",
        "operationId": "get_reference_team_logos",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": true,
            "description": "400 'sport_key parameter required' if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=200, api=1000.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "basketball_nba_bos",
                      "names": [
                        "Boston Celtics",
                        "Celtics",
                        "BOS"
                      ],
                      "logo": "https://a.espncdn.com/i/teamlogos/nba/500/bos.png",
                      "sport_key": "basketball_nba",
                      "external_id": "bos",
                      "source": "espn"
                    },
                    {
                      "id": "team_metadata_gsw",
                      "names": [
                        "Golden State Warriors"
                      ],
                      "logo": "https://cdn.krokodds.com.au/logos/nba/gsw.png",
                      "sport_key": "basketball_nba",
                      "external_id": null,
                      "source": "team_metadata"
                    }
                  ],
                  "meta": {
                    "count": 2,
                    "tier": "free",
                    "limit": 200,
                    "requested_limit": 100,
                    "sport_key": "basketball_nba",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 200,
                      "remaining": 996,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/reference/venues": {
      "get": {
        "tags": [
          "Reference"
        ],
        "summary": "Venue metadata (location, capacity, surface, etc.) lookup by name and/or sport.",
        "description": "Venue metadata (location, capacity, surface, etc.) lookup by name and/or sport.\n\n**Note:** Cache: free=86400s, api=43200s. Ordered DESC by $.fetchedAt. Row shape passthrough (`r.data ?? r`).",
        "operationId": "get_reference_venues",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Exact-match venue name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Exact-match sport filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Tier-capped: free=50, api=200.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          },
          {
            "name": "apikey / api_key",
            "in": "query",
            "required": true,
            "description": "API key, alternatively via X-API-Key header.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "name": "Marvel Stadium",
                      "sport": "australianfootball_afl",
                      "city": "Melbourne",
                      "state": "VIC",
                      "country": "Australia",
                      "capacity": 53359,
                      "surface": "grass",
                      "roof": "retractable",
                      "fetchedAt": "2026-08-15T06:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 100,
                    "name": "Marvel Stadium",
                    "sport": "australianfootball_afl",
                    "timestamp": "2026-09-03T04:12:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 995,
                      "reset": "2026-10-01"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/prediction-markets/sentiment": {
      "get": {
        "tags": [
          "Prediction Markets"
        ],
        "summary": "Directional sentiment (bullish/bearish/neutral) derived from Polymarket + Kalshi consensus pricing, plus a heuristic mar",
        "description": "Directional sentiment (bullish/bearish/neutral) derived from Polymarket + Kalshi consensus pricing, plus a heuristic market-disagreement flag.\n\n**Note:** Cache: 300s flat (no per-tier split). Only status='open' rows queried. direction: bullish if consensus>0.6, bearish if <0.4, else neutral. disagreement: bid-ask spread>0.15 OR (2+ outcome prices, top two within 0.1 of each other) \u2014 reads as inverted/odd logic, worth flagging in docs. Uses guardV1Request/v1Success helper, not makeV1Route.",
        "operationId": "get_prediction_markets_sentiment",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "ILIKE substring match against the market question text (not a structured sport field), e.g. 'NFL', 'AFL', 'EPL'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Exact match on platform_category.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "'polymarket' or 'kalshi'. 400 on any other value.",
            "schema": {
              "type": "string",
              "default": "both"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Clamped 1-200 (MAX_LIMIT=200).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "pm_will-chiefs-win-super-bowl-lx",
                      "source": "polymarket",
                      "question": "Will the Kansas City Chiefs win Super Bowl LX?",
                      "category": "sports",
                      "consensus_probability": 0.183,
                      "volume": 842000,
                      "liquidity": 61500,
                      "price_direction": "bearish",
                      "market_disagreement": false,
                      "status": "open",
                      "end_date": "2027-02-14",
                      "fetched_at": "2026-09-03T03:45:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "license": "krokodds-derived",
                    "filter": {
                      "sport_key": "NFL",
                      "category": null,
                      "source": null
                    },
                    "sentiment_summary": {
                      "bearish": 1
                    },
                    "note": "Prediction market sentiment derived from Polymarket + Kalshi pricing. Direction is heuristic from consensus probability.",
                    "limit": 50
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/prediction-markets/universal": {
      "get": {
        "tags": [
          "Prediction Markets"
        ],
        "summary": "Full-catalog Polymarket + Kalshi markets across EVERY category (weather, politics, crypto, economics, sports) \u2014 not just",
        "description": "Full-catalog Polymarket + Kalshi markets across EVERY category (weather, politics, crypto, economics, sports) \u2014 not just sports-matched markets. Synced every 30 min by universalMarketSync Cloud Function.\n\n**Note:** Cache: 300s flat. Distinct from /api/v1/odds-feed/prediction-markets, which serves the sports-matched subset (external_prediction_markets table) \u2014 this route is the raw full catalog (universal_prediction_markets table), not sport-filtered server-side (only via question-text search).",
        "operationId": "get_prediction_markets_universal",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "'polymarket' or 'kalshi'. 400 on invalid value.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Exact match on platform_category.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "One of open|closed|settled|halted. 400 on invalid value.",
            "schema": {
              "type": "string",
              "default": "open"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Clamped 1-200.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          },
          {
            "name": "search",
            "in": "query",
            "required": false,
            "description": "Case-insensitive ILIKE match on question text.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "pm_us-fed-rate-cut-september-2026",
                      "source": "kalshi",
                      "platform_category": "economics",
                      "market_id": "FED-RATE-SEP26",
                      "question": "Will the Fed cut rates at the September 2026 FOMC meeting?",
                      "description": "Resolves YES if the FOMC announces a rate cut at its September 2026 meeting.",
                      "outcomes": [
                        "Yes",
                        "No"
                      ],
                      "outcome_prices": [
                        0.71,
                        0.29
                      ],
                      "status": "open",
                      "result": null,
                      "volume": 1250000,
                      "liquidity": 98000,
                      "best_bid": 0.7,
                      "best_ask": 0.72,
                      "end_date": "2026-09-18",
                      "created_at": "2026-07-01T00:00:00.000Z",
                      "fetched_at": "2026-09-03T03:30:00.000Z",
                      "resolved_at": null
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "license": "krokodds-direct-scrape",
                    "filter": {
                      "source": null,
                      "category": "economics",
                      "status": "open",
                      "search": null
                    },
                    "pagination": {
                      "limit": 50,
                      "offset": 0
                    },
                    "note": "Full-catalog Polymarket + Kalshi markets across every category. Synced every 30 minutes.",
                    "limit": 50
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/prediction-markets/universal/resolutions": {
      "get": {
        "tags": [
          "Prediction Markets"
        ],
        "summary": "Append-only archive of RESOLVED Polymarket + Kalshi markets (final outcome + prices at resolution), with a day-based loo",
        "description": "Append-only archive of RESOLVED Polymarket + Kalshi markets (final outcome + prices at resolution), with a day-based lookback window.\n\n**Note:** Cache: 300s flat. Separate `market_resolutions` table (distinct from universal_prediction_markets) \u2014 append-only, never mutated after resolution. No explicit historical-days tier clamp applied to `days` param (unlike historical/* routes' since/from clamp) \u2014 same 30/365-day window available to free and api tiers alike.",
        "operationId": "get_prediction_markets_universal_resolutions",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "'polymarket' or 'kalshi'. 400 on invalid value.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Exact match on platform_category.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "days",
            "in": "query",
            "required": false,
            "description": "Lookback window in days for resolved_at, clamped 1-365.",
            "schema": {
              "type": "integer",
              "default": 30
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Clamped 1-200.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "pm_us-fed-rate-cut-july-2026",
                      "source": "kalshi",
                      "market_id": "FED-RATE-JUL26",
                      "question": "Will the Fed cut rates at the July 2026 FOMC meeting?",
                      "platform_category": "economics",
                      "outcomes": [
                        "Yes",
                        "No"
                      ],
                      "result": "No",
                      "final_prices": [
                        0.05,
                        0.95
                      ],
                      "volume_at_resolution": 980000,
                      "resolved_at": "2026-07-30T18:00:00.000Z",
                      "captured_at": "2026-07-30T18:05:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "license": "krokodds-direct-scrape",
                    "filter": {
                      "source": "kalshi",
                      "category": "economics",
                      "days": 30
                    },
                    "pagination": {
                      "limit": 50,
                      "offset": 0
                    },
                    "note": "Resolved Polymarket + Kalshi markets, append-only resolution archive.",
                    "limit": 50
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/boxing/fight-results": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Boxer career fight-results ledger (record, KO count, full fight list) by boxer slug or the most recently updated boxers.",
        "description": "Boxer career fight-results ledger (record, KO count, full fight list) by boxer slug or the most recently updated boxers.\n\n**Note:** Gated on the 'gameday' feature (not 'multisport') even though it's a static career record, not a live gameday signal \u2014 likely a copy/paste from another handler. No BigQuery fallback; hand-rolled auth preamble (predates makeV1Route helper) so it has no explicit creditCost/refund-on-empty logic.",
        "operationId": "get_boxing_fight_results",
        "parameters": [
          {
            "name": "boxer_slug",
            "in": "query",
            "required": false,
            "description": "Exact boxer slug; when set, does a direct single-document lookup instead of a listing query and ignores since/limit ordering.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only return boxers updated on/after this date. Clamped to the key's historical-window entitlement.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 2000 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "boxer_slug": "marcus-oyelaran",
                      "name": "Marcus Oyelaran",
                      "total_fights": 24,
                      "wins": 21,
                      "losses": 2,
                      "draws": 1,
                      "no_contests": 0,
                      "ko_wins": 15,
                      "fights": [
                        {
                          "opponent": "Diego Salazar",
                          "date": "2026-08-15",
                          "result": "W",
                          "method": "KO",
                          "round": 6
                        },
                        {
                          "opponent": "Kwame Boateng",
                          "date": "2026-05-02",
                          "result": "W",
                          "method": "UD",
                          "round": 12
                        }
                      ],
                      "updated_at": "2026-09-02T11:14:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "boxer_slug": "marcus-oyelaran",
                    "since": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 942,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/cricket/innings": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Cricsheet ball-by-ball derived per-player batting/bowling innings lines.",
        "description": "Cricsheet ball-by-ball derived per-player batting/bowling innings lines.\n\n**Note:** Returns 400 'sport_key, match_type, or season parameter required' if none supplied. When only match_type or season is given, over-fetches limit*6 rows and filters client-side, so results can be sparse relative to the requested limit on a narrow slice.",
        "operationId": "get_cricket_innings",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": true,
            "description": "Filter by internal sport/competition key. If set, this is the single indexed equality filter; match_type/season are applied in-memory afterward.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "match_type",
            "in": "query",
            "required": true,
            "description": "e.g. 'T20', 'ODI', 'Test'. Used as the indexed filter only when sport_key is absent.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": true,
            "description": "Season label, e.g. '2026'. Stored/compared as a string, not a number.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 100 (free) / 500 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "bbl2026-m34-innings-rt-smith",
                      "sportKey": "cricket_big_bash",
                      "matchType": "T20",
                      "season": "2026",
                      "matchId": "bbl2026-m34",
                      "player": "R. Thomson-Smith",
                      "team": "Perth Scorchers",
                      "battingRuns": 62,
                      "battingBalls": 41,
                      "fours": 6,
                      "sixes": 3,
                      "wickets": 0,
                      "oversBowled": 0,
                      "runsConceded": 0,
                      "fetchedAt": "2026-09-02T13:05:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "sport_key": "cricket_big_bash",
                    "match_type": null,
                    "season": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 3,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/cricket/live": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Recent/live cricket match snapshots (from the general cricket_matches archive, most-recently-fetched first).",
        "description": "Recent/live cricket match snapshots (from the general cricket_matches archive, most-recently-fetched first).\n\n**Note:** Named 'live' but gated on 'historical' (paid-only) and reads a plain archive table sorted by fetchedAt DESC \u2014 no is-live/status filter server-side, so callers must filter status client-side. Free tier cannot access this endpoint at all.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_cricket_live",
        "parameters": [
          {
            "name": "sportKey",
            "in": "query",
            "required": false,
            "description": "Filter by sport/competition key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 20 (free) / 50 (api) \u2014 note the config cap is far below the parse default of 100.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "cm-2026-09-03-ind-vs-eng",
                      "sportKey": "cricket_test_match",
                      "status": "in_progress",
                      "homeTeam": "India",
                      "awayTeam": "England",
                      "venue": "Wankhede Stadium, Mumbai",
                      "session": "Day 3, Session 2",
                      "score": {
                        "home": "312/6",
                        "away": "not yet batted"
                      },
                      "fetchedAt": "2026-09-03T03:55:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "sportKey": "cricket_test_match",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1998,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/cycling/results": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Historical cycling race results (stage/GC placings).",
        "description": "Historical cycling race results (stage/GC placings).\n\n**Note:** Paid-only ('historical' feature). No date-range param \u2014 only exact race-name match; free tier gets a 402 regardless of params.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_cycling_results",
        "parameters": [
          {
            "name": "race",
            "in": "query",
            "required": false,
            "description": "Exact race name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 20 (free) / 50 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "race": "Vuelta a Espana",
                      "stage": 18,
                      "date": "2026-09-02",
                      "rider": "Tobias Halvorsen",
                      "team": "Visma | Lease a Bike",
                      "position": 1,
                      "time": "4:12:08",
                      "gc_position": 2,
                      "fetchedAt": "2026-09-02T18:40:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 20,
                    "requested_limit": 100,
                    "race": "Vuelta a Espana",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 20,
                      "remaining": 1997,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/cycling/riders": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Cycling rider profiles/roster records.",
        "description": "Cycling rider profiles/roster records.\n\n**Note:** Both name and team filters are exact-match equality, no partial/fuzzy search.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_cycling_riders",
        "parameters": [
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Exact rider name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Exact team name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "name": "Tobias Halvorsen",
                      "team": "Visma | Lease a Bike",
                      "nationality": "Norway",
                      "dob": "1999-04-11",
                      "specialty": "Climber",
                      "fetchedAt": "2026-08-30T09:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "name": null,
                    "team": "Visma | Lease a Bike",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1996,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/cycling/startlists": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Cycling race startlists by race and date.",
        "description": "Cycling race startlists by race and date.\n\n**Note:** Both filters exact-match; no range query for date.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_cycling_startlists",
        "parameters": [
          {
            "name": "race",
            "in": "query",
            "required": false,
            "description": "Exact race name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Exact date filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 20 (free) / 50 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "race": "Vuelta a Espana",
                      "date": "2026-09-03",
                      "stage": 19,
                      "riders": [
                        {
                          "name": "Tobias Halvorsen",
                          "team": "Visma | Lease a Bike",
                          "bib": 21
                        },
                        {
                          "name": "Elia Fontaine",
                          "team": "UAE Team Emirates",
                          "bib": 1
                        }
                      ],
                      "fetchedAt": "2026-09-03T00:30:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 20,
                    "requested_limit": 100,
                    "race": "Vuelta a Espana",
                    "date": "2026-09-03",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 20,
                      "remaining": 1995,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/esports/leagues": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Esports league/competition metadata.",
        "description": "Esports league/competition metadata.\n\n**Note:** Simple single-filter listing, no search-by-name.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_esports_leagues",
        "parameters": [
          {
            "name": "sportKey",
            "in": "query",
            "required": false,
            "description": "Filter by sport/game key, e.g. 'esports_lol'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "sportKey": "esports_lol",
                      "leagueId": "lck-2026-summer",
                      "name": "LCK 2026 Summer",
                      "region": "Korea",
                      "game": "League of Legends",
                      "season": "2026 Summer",
                      "fetchedAt": "2026-08-28T05:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "sportKey": "esports_lol",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1994,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/esports/matches": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Esports match schedule/results, ordered by start time ascending.",
        "description": "Esports match schedule/results, ordered by start time ascending.\n\n**Note:** Ordered by $.beginAt ASC (upcoming-first), unlike most sibling endpoints which order DESC by fetchedAt.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_esports_matches",
        "parameters": [
          {
            "name": "sportKey",
            "in": "query",
            "required": false,
            "description": "Filter by sport/game key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Match status filter, e.g. 'not_started', 'running', 'finished'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "sportKey": "esports_csgo",
                      "matchId": "blast-premier-fall-2026-m12",
                      "status": "not_started",
                      "league": "BLAST Premier Fall Groups 2026",
                      "teamA": "Team Vitality",
                      "teamB": "Natus Vincere",
                      "beginAt": "2026-09-04T10:00:00.000Z",
                      "bestOf": 3,
                      "fetchedAt": "2026-09-03T02:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "sportKey": "esports_csgo",
                    "status": "not_started",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1993,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/esports/teams": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Esports team profiles/roster metadata.",
        "description": "Esports team profiles/roster metadata.\n\n**Note:** Same skeleton as esports/leagues, single equality filter on sportKey.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_esports_teams",
        "parameters": [
          {
            "name": "sportKey",
            "in": "query",
            "required": false,
            "description": "Filter by sport/game key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "sportKey": "esports_lol",
                      "teamId": "t1",
                      "name": "T1",
                      "region": "Korea",
                      "roster": [
                        "Zeus",
                        "Oner",
                        "Faker",
                        "Gumayusi",
                        "Keria"
                      ],
                      "fetchedAt": "2026-08-25T06:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "sportKey": "esports_lol",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1992,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/f1/races": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Formula 1 race schedule/status (API-Sports feed) \u2014 season, circuit, weather, fastest lap.",
        "description": "Formula 1 race schedule/status (API-Sports feed) \u2014 season, circuit, weather, fastest lap.\n\n**Note:** Code comment flags a past bug fix: this was previously gated on 'racing' (AU racing tier) which wrongly locked F1 out; now correctly gated on 'multisport'. Distinct dataset from /v1/f1/results (this is the API-Sports schedule, not Ergast/Jolpica results).",
        "operationId": "get_f1_races",
        "parameters": [
          {
            "name": "race_id",
            "in": "query",
            "required": false,
            "description": "Exact race id; does a direct single-document lookup, ignoring season/since/limit.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Season year, e.g. 2026.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only races updated on/after this date. Clamped to the key's historical-window entitlement.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 2000 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "race_id": "f1-2026-r16-monza",
                      "season": 2026,
                      "competition": "Formula 1",
                      "circuit": "Autodromo Nazionale Monza",
                      "date": "2026-09-06T13:00:00.000Z",
                      "type": "Race",
                      "status": "Scheduled",
                      "laps": 53,
                      "distance": "306.72km",
                      "timezone": "Europe/Rome",
                      "weather": "Sunny, 26C",
                      "fastest_lap": null,
                      "updated_at": "2026-09-03T01:20:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "race_id": null,
                    "season": 2026,
                    "since": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 940,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/f1/results": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Formula 1 driver race results per season/round (Ergast/Jolpica feed).",
        "description": "Formula 1 driver race results per season/round (Ergast/Jolpica feed).\n\n**Note:** Separate source/collection from /v1/f1/races \u2014 do not conflate the two in docs. When 'round' is passed without narrowing further, over-fetches limit*6 rows to filter client-side, so results can undercount near the tail of large seasons.",
        "operationId": "get_f1_results",
        "parameters": [
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Season year. Returns 400 if non-numeric.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "round",
            "in": "query",
            "required": false,
            "description": "Race round number within the season. Returns 400 if non-numeric. Filtered in-memory after an over-fetch.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 100 (free) / 500 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "season": "2026",
                      "round": 16,
                      "raceName": "Italian Grand Prix",
                      "driver": "Lando Norris",
                      "constructor": "McLaren",
                      "grid": 1,
                      "position": 1,
                      "points": 25,
                      "status": "Finished",
                      "fastestLapTime": "1:21.046"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "season": 2026,
                    "round": 16,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 939,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/golf/rankings": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Golf world/tour rankings by player.",
        "description": "Golf world/tour rankings by player.\n\n**Note:** No tour/date param \u2014 single collection covers all rankings snapshots, latest by fetchedAt.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_golf_rankings",
        "parameters": [
          {
            "name": "player",
            "in": "query",
            "required": false,
            "description": "Exact player name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "player": "Ludvig Karlberg",
                      "rank": 4,
                      "points": 8.42,
                      "events_played": 19,
                      "tour": "PGA Tour",
                      "fetchedAt": "2026-09-01T12:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "player": "Ludvig Karlberg",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1991,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/golf/skills": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Golf strokes-gained / approach-shot skill ratings by player.",
        "description": "Golf strokes-gained / approach-shot skill ratings by player.\n\n**Note:** `source=approach` switches to a different underlying collection (golf_approach_skills) with an approach-shot-specific shape \u2014 document both variants.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_golf_skills",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "Which dataset: 'skills' (golf_player_skills) or 'approach' (golf_approach_skills). 400 if any other value.",
            "schema": {
              "type": "string",
              "default": "skills"
            }
          },
          {
            "name": "player",
            "in": "query",
            "required": false,
            "description": "Exact player name filter.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "player": "Ludvig Karlberg",
                      "sg_total": 2.14,
                      "sg_off_tee": 0.61,
                      "sg_approach": 0.88,
                      "sg_around_green": 0.34,
                      "sg_putting": 0.31,
                      "fetchedAt": "2026-08-30T09:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "source": "skills",
                    "player": "Ludvig Karlberg",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1990,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/golf/stats": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Golf player statistical projections (most recent snapshot).",
        "description": "Golf player statistical projections (most recent snapshot).\n\n**Note:** No query filters at all besides limit \u2014 collection is 'golf_projections' despite the route name 'stats'; there is no player/event filter param.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_golf_stats",
        "parameters": [
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "player": "Ludvig Karlberg",
                      "event": "BMW Championship",
                      "projected_finish": 6,
                      "win_prob": 0.041,
                      "top10_prob": 0.32,
                      "made_cut_prob": 0.91,
                      "fetchedAt": "2026-08-29T15:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1989,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/leaderboards/soccer": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Soccer league top-scorers or top-assists leaderboard for a league+season.",
        "description": "Soccer league top-scorers or top-assists leaderboard for a league+season.\n\n**Note:** Data shape differs from every other endpoint here: `data` is a single object (not an array) containing a `players` array. Backing docs are chunked (>1MB Firestore doc limit split into `{docId}_c{i}` chunk docs) and reassembled server-side; Supabase path falls back to Firestore on any read error. If the doc doesn't exist, returns success:true with data:null and count:0 (not a 404).",
        "operationId": "get_leaderboards_soccer",
        "parameters": [
          {
            "name": "kind",
            "in": "query",
            "required": false,
            "description": "'topscorers' or 'topassists'. Any other value returns 400.",
            "schema": {
              "type": "string",
              "default": "topscorers"
            }
          },
          {
            "name": "league_id",
            "in": "query",
            "required": true,
            "description": "League id, e.g. '39' (Premier League). Required \u2014 400 if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": true,
            "description": "Season year, e.g. '2026'. Required \u2014 400 if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max players returned. Capped at 100 (free) / 5000 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "league_id": "39",
                    "league": "Premier League",
                    "season": "2026",
                    "kind": "topscorers",
                    "players": [
                      {
                        "rank": 1,
                        "player": "Erling Haaland",
                        "team": "Manchester City",
                        "goals": 9,
                        "appearances": 4
                      },
                      {
                        "rank": 2,
                        "player": "Alexander Isak",
                        "team": "Liverpool",
                        "goals": 7,
                        "appearances": 4
                      }
                    ],
                    "count": 2,
                    "updated_at": "2026-09-02T22:00:00.000Z"
                  },
                  "meta": {
                    "count": 2,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "kind": "topscorers",
                    "league_id": "39",
                    "season": "2026",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 946,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/mma/fights": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "MMA fight card entries/results by fight id or recently-updated window.",
        "description": "MMA fight card entries/results by fight id or recently-updated window.\n\n**Note:** Same hand-rolled auth preamble pattern as boxing/fight-results and gated on 'gameday' rather than 'multisport'.",
        "operationId": "get_mma_fights",
        "parameters": [
          {
            "name": "fight_id",
            "in": "query",
            "required": false,
            "description": "Exact fight id; does a direct single-document lookup, ignoring since/limit.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Only fights updated on/after this date. Clamped to the key's historical-window entitlement.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 2000 (api).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "fight_id": "ufc-306-fontaine-vs-reyes",
                      "date": "2026-09-06",
                      "time": "22:00",
                      "slug": "ufc-306",
                      "status": "scheduled",
                      "is_main": true,
                      "category": "Welterweight",
                      "fighters": [
                        {
                          "name": "Diego Fontaine",
                          "record": "19-3-0"
                        },
                        {
                          "name": "Marcus Reyes",
                          "record": "17-2-0"
                        }
                      ],
                      "league": "UFC",
                      "country": "USA",
                      "updated_at": "2026-09-02T20:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "fight_id": null,
                    "since": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 938,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/nfl/schedule": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "NFL full-season schedule with teams, venues, kickoff times, and starting QBs.",
        "description": "NFL full-season schedule with teams, venues, kickoff times, and starting QBs.\n\n**Note:** The only endpoint in this batch built on the newer guardV1Request/v1Success helper (adds X-Krok-Data-Source: derived header and license:'krokodds-derived' in meta). Explicit creditCost=1 passed to guardV1Request. Sorted by gameday then gametime.",
        "operationId": "get_nfl_schedule",
        "parameters": [
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Season year. 400 if non-numeric when provided.",
            "schema": {
              "type": "integer",
              "default": "current calendar year"
            }
          },
          {
            "name": "week",
            "in": "query",
            "required": false,
            "description": "Week number filter. 400 if non-numeric when provided.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Case-insensitive team name substring OR exact abbreviation match, checked against both home and away.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows, 1-500.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "2026-w1-kc-bal",
                      "game_id": "2026090400",
                      "season": 2026,
                      "season_type": "REG",
                      "week": 1,
                      "gameday": "2026-09-04",
                      "weekday": "Thursday",
                      "gametime": "20:20",
                      "away_team": "Kansas City Chiefs",
                      "away_abbr": "KC",
                      "home_team": "Baltimore Ravens",
                      "home_abbr": "BAL",
                      "stadium": "M&T Bank Stadium",
                      "location": "Baltimore, MD",
                      "result": null,
                      "overtime": false,
                      "away_qb": "Patrick Mahomes",
                      "home_qb": "Lamar Jackson"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "license": "krokodds-derived",
                    "season": 2026,
                    "week": 1,
                    "team": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 950,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/soccer/bundesliga": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "OpenLigaDB Bundesliga / 2.Bundesliga / DFB-Pokal match results \u2014 fallback settlement source for German-football props.",
        "description": "OpenLigaDB Bundesliga / 2.Bundesliga / DFB-Pokal match results \u2014 fallback settlement source for German-football props.\n\n**Note:** creditCost=5 (archive-tier weight) despite the endpoint label being a plain fixtures list. `league` is mandatory. season/matchday applied in-memory after an 8x over-fetch when combined with league.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_soccer_bundesliga",
        "parameters": [
          {
            "name": "league",
            "in": "query",
            "required": true,
            "description": "One of 'bl1' (Bundesliga), 'bl2' (2. Bundesliga), 'dfb' (DFB-Pokal). Required \u2014 400 if missing or invalid.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Start-year of the season, e.g. 2025 for 2025/26.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "matchday",
            "in": "query",
            "required": false,
            "description": "Matchday/round number within the season.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 100 (free) / 500 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "matchId": 68321,
                      "date": "2026-08-30T13:30:00.000Z",
                      "matchday": "3. Spieltag",
                      "homeTeam": "Bayer 04 Leverkusen",
                      "awayTeam": "Borussia Dortmund",
                      "homeGoals": 2,
                      "awayGoals": 2,
                      "homeGoalsHT": 1,
                      "awayGoalsHT": 1,
                      "finished": true,
                      "location": "BayArena, Leverkusen"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 100,
                    "requested_limit": 100,
                    "league": "bl1",
                    "season": 2025,
                    "matchday": 3,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 1985,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/soccer/closing-odds": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "football-data.co.uk settled soccer matches with Bet365 + Pinnacle opening AND closing prices (Asian handicap, O/U 2.5) \u2014",
        "description": "football-data.co.uk settled soccer matches with Bet365 + Pinnacle opening AND closing prices (Asian handicap, O/U 2.5) \u2014 the CLV reference dataset.\n\n**Note:** creditCost=5 despite being on the free tier ('closing_lines' is a FREE_TIER_FEATURES member) \u2014 free-tier callers still burn 5 credits per call. 400 if neither league/league_code nor season is provided. from/to are plain string comparisons against a YYYY-MM-DD date field, not true date-range queries at the DB layer.",
        "operationId": "get_soccer_closing_odds",
        "parameters": [
          {
            "name": "league",
            "in": "query",
            "required": true,
            "description": "League code (e.g. 'E0' for EPL). Also accepted as league_code.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "league_code",
            "in": "query",
            "required": true,
            "description": "Alias for league.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": true,
            "description": "Season label, e.g. '2025-2026'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Inclusive start date filter (in-memory, string comparison on date).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "Inclusive end date filter (in-memory, string comparison on date).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 100 (free) / 500 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "fdcouk-e0-2025-2026-020",
                      "date": "2026-08-30",
                      "time": "17:30",
                      "league_code": "E0",
                      "season": "2025-2026",
                      "home_team": "Arsenal",
                      "away_team": "Tottenham",
                      "ftr": "H",
                      "htr": "D",
                      "ft_home_goals": 2,
                      "ft_away_goals": 1,
                      "b365_open": {
                        "home": 1.65,
                        "draw": 4.0,
                        "away": 5.5
                      },
                      "b365_close": {
                        "home": 1.58,
                        "draw": 4.1,
                        "away": 6.0
                      },
                      "pinnacle_open": {
                        "home": 1.68,
                        "draw": 3.95,
                        "away": 5.3
                      },
                      "pinnacle_close": {
                        "home": 1.6,
                        "draw": 4.05,
                        "away": 5.8
                      },
                      "asian_handicap": {
                        "line": -0.75,
                        "home_close": 1.95,
                        "away_close": 1.97
                      },
                      "over_under_25": {
                        "over_close": 1.85,
                        "under_close": 2.0
                      }
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "league_code": "E0",
                    "season": null,
                    "from": "2026-08-01",
                    "to": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 944,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/soccer/fixtures": {
      "get": {
        "tags": [
          "Other Sports"
        ],
        "summary": "Soccer fixtures/results by sport key and status, ordered by kickoff time ascending.",
        "description": "Soccer fixtures/results by sport key and status, ordered by kickoff time ascending.\n\n**Note:** Ordered by $.utcDate ASC (upcoming-first), like esports/matches, unlike the DESC-by-fetchedAt convention used by most other collection routes.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_soccer_fixtures",
        "parameters": [
          {
            "name": "sportKey",
            "in": "query",
            "required": false,
            "description": "Filter by sport/competition key, e.g. 'soccer_epl'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Match status filter, e.g. 'SCHEDULED', 'IN_PLAY', 'FINISHED'.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 (free) / 200 (api).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "sportKey": "soccer_epl",
                      "matchId": "epl-2026-gw4-avl-che",
                      "status": "SCHEDULED",
                      "utcDate": "2026-09-06T14:00:00.000Z",
                      "homeTeam": "Aston Villa",
                      "awayTeam": "Chelsea",
                      "venue": "Villa Park",
                      "matchday": 4
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 100,
                    "sportKey": "soccer_epl",
                    "status": "SCHEDULED",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 1980,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/advanced-stats": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Multiplexed advanced/derived stat archives across 14 sources (MLB Statcast, NHL/MoneyPuck skaters+goalies, NCAAB, soccer",
        "description": "Multiplexed advanced/derived stat archives across 14 sources (MLB Statcast, NHL/MoneyPuck skaters+goalies, NCAAB, soccer xG/FBref, NCAAF, NFL NGS/snaps/depth, NRL, AFL, tennis closing odds), selected via ?source=.\n\n**Note:** Built on the shared makeV1Route factory. Cache TTL: free=3600s, api=1800s. Falls back to BigQuery mirror only if loadSupabase throws AND a mirror exists; soccer_fbref has no BQ mirror (SOURCES_WITHOUT_BQ_MIRROR) so it's Supabase-only. Empty result set returns HTTP 204 and refunds the credit.",
        "operationId": "get_advanced_stats",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "One of: mlb_advanced, nhl_skaters, ncaab_schools, soccer_xg, ncaaf_player_games, nfl_ngs, nfl_snaps, nfl_depth, soccer_fbref, nrl_player_games, afl_matches, tennis_closing_odds, nhl_moneypuck_skaters, nhl_moneypuck_goalies. 400 V1BadRequest if not in this list.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Season/year filter (field name varies per source: 'season' or 'year'). Non-numeric value returns 400.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "team | playerType | league | sportKey | opponent | flavor | round | teamNickname | playerSlug | team1 | team2 | venue | tour | surface | position | situation | compId | squad",
            "in": "query",
            "required": false,
            "description": "Per-source in-memory filter, only the subset listed in that source's `extra` array is honored (e.g. mlb_advanced accepts team/playerType; nhl_moneypuck_skaters accepts team/position/playerSlug/situation, defaulting situation to 'all' when omitted).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Row cap. Free tier max 100, api tier max 500.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "abc123",
                      "season": 2026,
                      "team": "NYY",
                      "playerType": "batter",
                      "xwOBA": 0.361,
                      "barrelPct": 9.8
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "source": "mlb_advanced",
                    "season": 2026,
                    "filters": {
                      "team": "nyy"
                    },
                    "timestamp": "2026-09-03T00:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 999,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/bets/track": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "GET returns a user's tracked-bet history plus a computed P&L summary (by sport, by bookmaker, streaks, monthly breakdown",
        "description": "GET returns a user's tracked-bet history plus a computed P&L summary (by sport, by bookmaker, streaks, monthly breakdown); POST logs a new bet.\n\n**Note:** Requires a valid API key with the bet_tracking feature (via guardV1Request) PLUS a Firebase-session-derived `x-user-id` request header \u2014 the API key alone is not sufficient, so this is a hybrid api_key+session auth route despite being under v1. Reads/writes the `bets` Supabase table directly (no Firestore fallback in this file). POST returns 201 with the created row and its plaintext id (no secret hidden). GET response cached 60s via unstable_cache, keyed per user+filters.",
        "operationId": "get_bets_track",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter bets by sport.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by bet status: pending | won | lost | void.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start date filter (created_at >= from).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End date filter (created_at <= to 23:59:59).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results, clamped 1-200.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "u1_1735689600_ab12cd",
                      "user_id": "u1",
                      "event_id": "evt123",
                      "sport_key": "basketball_nba",
                      "market": "h2h",
                      "selection": "Lakers",
                      "odds": 1.85,
                      "stake": 50,
                      "potential_return": 92.5,
                      "profit_loss": null,
                      "status": "pending",
                      "created_at": "2026-09-01T00:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "license": "krokodds-derived",
                    "summary": {
                      "total_bets": 12,
                      "pending": 2,
                      "won": 7,
                      "lost": 3,
                      "void": 0,
                      "total_stake": 600,
                      "total_return": 740,
                      "net_profit": 140,
                      "roi_pct": 23.33,
                      "win_rate": 70,
                      "by_sport": {
                        "basketball_nba": {
                          "bets": 5,
                          "stake": 250,
                          "profit": 60,
                          "roi": 24,
                          "win_rate": 66.67
                        }
                      },
                      "by_bookmaker": {
                        "sportsbet": {
                          "bets": 5,
                          "stake": 250,
                          "profit": 60,
                          "roi": 24,
                          "win_rate": 66.67
                        }
                      },
                      "streak": {
                        "current": 2,
                        "type": "win",
                        "best_win": 4,
                        "best_loss": 2
                      },
                      "monthly": {
                        "2026-08": {
                          "bets": 6,
                          "stake": 300,
                          "profit": 70,
                          "roi": 23.33
                        }
                      }
                    },
                    "pagination": {
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event_id": {
                    "type": "string",
                    "description": "Event identifier."
                  },
                  "sport_key": {
                    "type": "string",
                    "description": "Sport key."
                  },
                  "market": {
                    "type": "string",
                    "default": "\"\"",
                    "description": "Market type, e.g. h2h, spread, total."
                  },
                  "selection": {
                    "type": "string",
                    "description": "What was backed."
                  },
                  "odds": {
                    "type": "number",
                    "description": "Decimal odds."
                  },
                  "stake": {
                    "type": "number",
                    "description": "Stake amount (AUD). potential_return is auto-computed as odds*stake when both are present."
                  },
                  "status": {
                    "type": "string",
                    "default": "pending",
                    "description": "pending | won | lost | void."
                  },
                  "commence_time": {
                    "type": "string",
                    "description": "Event start time; passed through into the stored `data` jsonb blob (not a dedicated column)."
                  }
                },
                "required": [
                  "event_id",
                  "sport_key"
                ]
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "GET returns a user's tracked-bet history plus a computed P&L summary (by sport, by bookmaker, streaks, monthly breakdown",
        "description": "GET returns a user's tracked-bet history plus a computed P&L summary (by sport, by bookmaker, streaks, monthly breakdown); POST logs a new bet.\n\n**Note:** Requires a valid API key with the bet_tracking feature (via guardV1Request) PLUS a Firebase-session-derived `x-user-id` request header \u2014 the API key alone is not sufficient, so this is a hybrid api_key+session auth route despite being under v1. Reads/writes the `bets` Supabase table directly (no Firestore fallback in this file). POST returns 201 with the created row and its plaintext id (no secret hidden). GET response cached 60s via unstable_cache, keyed per user+filters.",
        "operationId": "post_bets_track",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter bets by sport.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "status",
            "in": "query",
            "required": false,
            "description": "Filter by bet status: pending | won | lost | void.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start date filter (created_at >= from).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End date filter (created_at <= to 23:59:59).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max results, clamped 1-200.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Pagination offset.",
            "schema": {
              "type": "integer",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "u1_1735689600_ab12cd",
                      "user_id": "u1",
                      "event_id": "evt123",
                      "sport_key": "basketball_nba",
                      "market": "h2h",
                      "selection": "Lakers",
                      "odds": 1.85,
                      "stake": 50,
                      "potential_return": 92.5,
                      "profit_loss": null,
                      "status": "pending",
                      "created_at": "2026-09-01T00:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "license": "krokodds-derived",
                    "summary": {
                      "total_bets": 12,
                      "pending": 2,
                      "won": 7,
                      "lost": 3,
                      "void": 0,
                      "total_stake": 600,
                      "total_return": 740,
                      "net_profit": 140,
                      "roi_pct": 23.33,
                      "win_rate": 70,
                      "by_sport": {
                        "basketball_nba": {
                          "bets": 5,
                          "stake": 250,
                          "profit": 60,
                          "roi": 24,
                          "win_rate": 66.67
                        }
                      },
                      "by_bookmaker": {
                        "sportsbet": {
                          "bets": 5,
                          "stake": 250,
                          "profit": 60,
                          "roi": 24,
                          "win_rate": 66.67
                        }
                      },
                      "streak": {
                        "current": 2,
                        "type": "win",
                        "best_win": 4,
                        "best_loss": 2
                      },
                      "monthly": {
                        "2026-08": {
                          "bets": 6,
                          "stake": 300,
                          "profit": 70,
                          "roi": 23.33
                        }
                      }
                    },
                    "pagination": {
                      "limit": 50,
                      "offset": 0
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "event_id": {
                    "type": "string",
                    "description": "Event identifier."
                  },
                  "sport_key": {
                    "type": "string",
                    "description": "Sport key."
                  },
                  "market": {
                    "type": "string",
                    "default": "\"\"",
                    "description": "Market type, e.g. h2h, spread, total."
                  },
                  "selection": {
                    "type": "string",
                    "description": "What was backed."
                  },
                  "odds": {
                    "type": "number",
                    "description": "Decimal odds."
                  },
                  "stake": {
                    "type": "number",
                    "description": "Stake amount (AUD). potential_return is auto-computed as odds*stake when both are present."
                  },
                  "status": {
                    "type": "string",
                    "default": "pending",
                    "description": "pending | won | lost | void."
                  },
                  "commence_time": {
                    "type": "string",
                    "description": "Event start time; passed through into the stored `data` jsonb blob (not a dedicated column)."
                  }
                },
                "required": [
                  "event_id",
                  "sport_key"
                ]
              }
            }
          }
        }
      }
    },
    "/bookmakers": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Static-ish catalog of AU-facing bookmakers KrokOdds supports (id, name, provider, feed_group, is_aggregator, is_exchange",
        "description": "Static-ish catalog of AU-facing bookmakers KrokOdds supports (id, name, provider, feed_group, is_aggregator, is_exchange, supported flag).\n\n**Note:** 24h Cache-Control (private, max-age=86400, stale-while-revalidate=172800) \u2014 deliberately 'private' not 'public' since a shared/CDN cache would leak the auth-gated response to keyless callers (CDN keys on URL only, ignores X-API-Key). X-Krok-Version: 2 (higher than most other v1 routes, which are version 1).",
        "operationId": "get_bookmakers",
        "parameters": [
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Region filter; only 'au' returns rows, anything else (non-empty) returns an empty array \u2014 forward-compat hook, AU is the only region currently supported.",
            "schema": {
              "type": "string",
              "default": "au"
            }
          },
          {
            "name": "provider",
            "in": "query",
            "required": false,
            "description": "Filter by provider metadata (matches provider field or provider === 'both').",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_aggregators",
            "in": "query",
            "required": false,
            "description": "When true, also includes AGGREGATOR_FEEDS (deduped clone lines, no arb value) marked supported:false.",
            "schema": {
              "type": "string",
              "default": "false"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Field projection via parseFields/projectRows.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "sportsbet",
                      "name": "Sportsbet",
                      "region": "au",
                      "odds_api_key": "sportsbet",
                      "supported": true,
                      "provider": "the-odds-api",
                      "feed_group": "sportsbet",
                      "is_aggregator": false,
                      "is_exchange": false
                    }
                  ],
                  "meta": {
                    "count": 42,
                    "tier": "free",
                    "region": "au",
                    "provider": "all",
                    "include_aggregators": false,
                    "timestamp": "2026-09-03T00:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 999,
                      "reset": ""
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/bulk": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Enterprise cursor-paginated JSON bulk export of the historical archive across 13 datasets (team/player game logs, prop r",
        "description": "Enterprise cursor-paginated JSON bulk export of the historical archive across 13 datasets (team/player game logs, prop results, game results, MLB/soccer/NCAAF/NFL/NHL/NRL advanced stats, Betfair BSP/results). No ?dataset= returns the dataset manifest.\n\n**Note:** creditCost: 25 (enterprise-weighted, set explicitly in makeV1Route config). Cache TTL 3600s both tiers (archive is immutable so cached hard). Cursor pagination via supabasePageById (`.order(id).gt(id, cursor).limit(n)`) \u2014 index-cheap and resumable. Companion CSV/JSON export at /api/v1/export shares the same bulk_export gate and 25-credit cost.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_bulk",
        "parameters": [
          {
            "name": "dataset",
            "in": "query",
            "required": false,
            "description": "One of: team_game_logs, player_game_logs, player_history, prop_results, game_results, mlb_advanced, soccer_xg, ncaaf_player_games, nfl_tracking, nhl_skaters, nrl_player_games, exchange_bsp, exchange_results. Omitted \u2192 returns manifest (list of {dataset, description}) with no DB read.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport_key | sport",
            "in": "query",
            "required": false,
            "description": "Only supported for player_game_logs and nrl_player_games datasets; 400 otherwise (message points caller to /v1/historical/player-game-log).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Opaque id cursor for resumable pagination \u2014 pass back `next_cursor` from the previous response until it's null.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Row cap. Free tier max 100, api tier max 500 (though the feature itself is api-tier gated).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "0001",
                      "sportKey": "americanfootball_nfl",
                      "team": "KC",
                      "gameDate": "2026-08-30"
                    }
                  ],
                  "meta": {
                    "count": 100,
                    "tier": "api",
                    "limit": 100,
                    "requested_limit": 100,
                    "dataset": "team_game_logs",
                    "collection": "team_game_log",
                    "next_cursor": "0100",
                    "has_more": true,
                    "timestamp": "2026-09-03T00:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 49975,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/closing-lines": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Cross-event aggregator of opening vs closing H2H odds (for CLV computation at scale) \u2014 flips the single-event odds-histo",
        "description": "Cross-event aggregator of opening vs closing H2H odds (for CLV computation at scale) \u2014 flips the single-event odds-history ladder inside out into many events with opens/close/movement.\n\n**Note:** Hand-rolled route (not makeV1Route) with its own dual-source fallback: primary `odds_history` table (has opening_odds), falls back to legacy `gameday_odds_history` shape if odds_history returns 0 rows or throws \u2014 the two sources use different field casing (commence_time vs commenceTime) which the route normalizes. Cache 300s free / 60s api. Distinct from the single-event ladder at /v1/odds-history?event_id=.",
        "operationId": "get_closing_lines",
        "parameters": [
          {
            "name": "sport_key | sport",
            "in": "query",
            "required": false,
            "description": "Filter by sport.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Clamped by clampHistoricalFrom() against the key's historicalDays window.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "until",
            "in": "query",
            "required": false,
            "description": "Upper bound on commence_time.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "only_closed",
            "in": "query",
            "required": false,
            "description": "When true, only events whose commence_time has already passed are returned.",
            "schema": {
              "type": "boolean",
              "default": "true"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Free tier max 50, api tier max 2000.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "cursor",
            "in": "query",
            "required": false,
            "description": "Keyset cursor encoding {v: commence_time ISO, id}.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Field projection.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "evt-1",
                      "event_id": "evt-1",
                      "sport_key": "basketball_nba",
                      "home_team": "Lakers",
                      "away_team": "Celtics",
                      "commence_time": "2026-09-02T00:00:00.000Z",
                      "is_closed": true,
                      "opens": {
                        "home": 1.9,
                        "away": 1.95,
                        "opened_at_ms": null,
                        "home_implied_prob": 0.5263,
                        "away_implied_prob": 0.5128
                      },
                      "close": {
                        "home": 1.83,
                        "away": 2.05,
                        "home_book": "sportsbet",
                        "away_book": "sportsbet",
                        "captured_at_ms": null,
                        "home_implied_prob": 0.5464,
                        "away_implied_prob": 0.4878
                      },
                      "movement": {
                        "home_delta": -0.07,
                        "away_delta": 0.1,
                        "home_direction": "shorten",
                        "away_direction": "drift"
                      },
                      "snapshot_count": 0,
                      "updated_at": "2026-09-02T05:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": "basketball_nba",
                    "since": null,
                    "until": null,
                    "only_closed": true,
                    "next_cursor": null,
                    "timestamp": "2026-09-03T00:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 999,
                      "reset": ""
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/clv-archive/{eventId}": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Fetch the archived closing odds snapshot for a single event by path parameter.",
        "description": "Fetch the archived closing odds snapshot for a single event by path parameter.\n\n**Note:** IMPORTANT DISCREPANCY: this route uses Firebase session auth (getServerUser()), returning 401 Unauthorized if no signed-in user \u2014 it does NOT use verifyApiKey / X-API-Key at all, unlike almost every other /v1/* route. Reads Supabase `clv_archive` table by doc id first (isSupabase('clv-archive') gate), falls back to Firestore `clv_archive` collection on Supabase error. 404 if the event isn't archived.\n\n**Auth note:** this endpoint uses the dashboard's Firebase session cookie, not an API key \u2014 it is not callable by third-party API integrations.",
        "operationId": "get_clv_archive_eventId",
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "description": "Dynamic route segment [eventId]; trimmed, 400 if empty after trim.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "event_id": "evt-1",
                    "sport": "basketball_nba",
                    "sport_title": "NBA",
                    "home_team": "Lakers",
                    "away_team": "Celtics",
                    "commence_time": "2026-09-02T00:00:00.000Z",
                    "bookmakers": [],
                    "archived_at": "2026-09-02T05:00:00.000Z",
                    "expire_at": null
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "SessionAuth": []
          }
        ]
      }
    },
    "/ev-hit-rates": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Historical hit-rate / calibration stats for +EV bets, aggregated by market category (total_bets, hit_count, hit_rate, av",
        "description": "Historical hit-rate / calibration stats for +EV bets, aggregated by market category (total_bets, hit_count, hit_rate, avg_clv, avg_ev).\n\n**Note:** Hand-rolled route with a THREE-tier fallback chain: Supabase (ev_hit_rates table) -> point-get on Firestore evHitRates if category set -> BigQuery mirror -> full Firestore collection scan (capped at min(limit,500)). Cache TTL 3600s free / 600s api. Per memory note 'ev-records-empty' class issues have hit similar tables historically \u2014 verify live data before citing exact numbers in docs.",
        "operationId": "get_ev_hit_rates",
        "parameters": [
          {
            "name": "category",
            "in": "query",
            "required": false,
            "description": "Market category id \u2014 when set, does a point-get by doc id (category) instead of a scan.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Free tier max 100, api tier max 5000.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "category": "h2h",
                      "total_bets": 542,
                      "hit_count": 301,
                      "hit_rate": 0.5554,
                      "avg_clv": 1.8,
                      "avg_ev": 4.2,
                      "updated_at": "2026-09-01T00:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "category": null,
                    "timestamp": "2026-09-03T00:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 999,
                      "reset": ""
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/exchange": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "KrokOdds Exchange (Betfair) pricing suite multiplexed by ?source=: settled racing BSP, race results, sports match-odds, ",
        "description": "KrokOdds Exchange (Betfair) pricing suite multiplexed by ?source=: settled racing BSP, race results, sports match-odds, scorer props, and live pre-jump snapshots.\n\n**Note:** Built on makeV1Route, Supabase-only (no loadBq). Cache TTL 600s free / 300s api. Underlying writers are functions/src/external/betfair/*. Per CLAUDE.md, this exchange endpoint is distinct from the max-fidelity odds_archive/racing_odds_archive firehose tables (migration 056) which are internal-only, not exposed via this API.",
        "operationId": "get_exchange",
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": true,
            "description": "One of: bsp, results, match_odds, scorer_props, snapshots. 400 if not in this list.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "raceKey",
            "in": "query",
            "required": false,
            "description": "Racing filter for bsp/results, keyed `${venueSlug}_${raceNumber}`.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venueSlug | marketType",
            "in": "query",
            "required": false,
            "description": "Additional bsp filters (in-memory, post-fetch).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport",
            "in": "query",
            "required": false,
            "description": "Equality filter (indexed path) for match_odds/scorer_props/snapshots sources.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "eventId",
            "in": "query",
            "required": false,
            "description": "In-memory filter for match_odds/scorer_props/snapshots.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Free tier max 100, api tier max 500.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "flem_r5",
                      "raceKey": "flemington_5",
                      "venueSlug": "flemington",
                      "marketType": "WIN",
                      "bsp": 4.6,
                      "runnerName": "Fast Horse"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "source": "bsp",
                    "filters": {
                      "raceKey": "flemington_5"
                    },
                    "timestamp": "2026-09-03T00:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 999,
                      "reset": ""
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/export": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Enterprise bulk data export as downloadable CSV (or JSON) from the BigQuery archive mirror \u2014 companion to the JSON-only ",
        "description": "Enterprise bulk data export as downloadable CSV (or JSON) from the BigQuery archive mirror \u2014 companion to the JSON-only /v1/bulk endpoint.\n\n**Note:** Uses guardV1Request(request, 'bulk_export', 25) \u2014 same 25-credit weighting as /v1/bulk. 503 if BQ_ENABLED is false or the dataset's BQ mirror doesn't exist yet ('Dataset not yet available'). CSV response sets filename `krok-{datasetKey}-{YYYY-MM-DD}.csv` and header X-Krok-Export-Rows with the actual row count. Cache 1800s for the json format variant.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_export",
        "parameters": [
          {
            "name": "dataset",
            "in": "query",
            "required": true,
            "description": "One of: game_results, player_props_results, model_track_record, gameday_odds_history, clv_archive, ev_hit_rates. 400 if missing/unknown.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 5000 for both free and api tier (LIMIT_BY_TIER).",
            "schema": {
              "type": "integer",
              "default": 5000
            }
          },
          {
            "name": "format",
            "in": "query",
            "required": false,
            "description": "csv (file download, Content-Disposition attachment) or json (standard v1 envelope via v1Success).",
            "schema": {
              "type": "string",
              "default": "csv"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "g1",
                      "sport_key": "americanfootball_nfl",
                      "home_score": 24,
                      "away_score": 17,
                      "completed": true
                    }
                  ],
                  "meta": {
                    "dataset": "game_results",
                    "label": "Settled game results",
                    "limit": 5000,
                    "requested_limit": 5000
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/extended-markets": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Derivative/extended odds markets excluded from the core h2h/spreads/totals feed \u2014 half/period/inning splits, soccer corn",
        "description": "Derivative/extended odds markets excluded from the core h2h/spreads/totals feed \u2014 half/period/inning splits, soccer corners+cards, AFL/NRL team-totals+tries, alt-totals \u2014 merged from two labelled tiers (vendor-licensed RapidOdds + KrokOdds' own direct scrape).\n\n**Note:** Every row carries `source` ('rapidodds' | 'scraped') and `redistributable` (bool) \u2014 RapidOdds rows are FORBIDDEN_RESALE tier (b) under Terms 6.1, internal-use-only, never redistributable; KrokOdds' own scrape is redistributable. The three core markets (h2h, spreads, totals) are deliberately excluded \u2014 those live at /v1/odds. `meta.provider` field is @deprecated in favor of per-event `source`. Cache 300s free / 60s api; the two source tiers are fetched via Promise.allSettled so one failing doesn't take the other down.",
        "operationId": "get_extended_markets",
        "parameters": [
          {
            "name": "sport_key | sport",
            "in": "query",
            "required": false,
            "description": "Sport filter (used when event_id absent).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Point-read a single event across both tiers instead of a windowed scan.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Post-merge market-key filter, applied after both tiers are combined.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "all | rapidodds | scraped. 'scraped' returns only the redistributable KrokOdds-owned tier; 'rapidodds' returns only the vendor-licensed (non-redistributable) tier.",
            "schema": {
              "type": "string",
              "default": "all"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Free tier max 50, api tier max 2000; applied per-tier before merge.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "event_id": "evt-1",
                      "event_key": null,
                      "sport_key": "soccer_epl",
                      "sport_title": "EPL",
                      "home_team": "Arsenal",
                      "away_team": "Chelsea",
                      "commence_time": "2026-09-05T00:00:00.000Z",
                      "bookmakers": [
                        {
                          "key": "sportsbet",
                          "title": "Sportsbet",
                          "last_update": "2026-09-03T00:00:00.000Z",
                          "markets": [
                            {
                              "key": "corners_over_under",
                              "single_sided": false,
                              "outcomes": [
                                {
                                  "name": "Over",
                                  "description": "9.5",
                                  "price": 1.9,
                                  "point": 9.5,
                                  "bet_link": "https://..."
                                }
                              ]
                            }
                          ]
                        }
                      ],
                      "source": "scraped",
                      "redistributable": true
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": "soccer_epl",
                    "event_id": null,
                    "market": null,
                    "source": "all",
                    "provider": "rapidodds",
                    "providers": [
                      "rapidodds",
                      "scraped"
                    ],
                    "timestamp": "2026-09-03T00:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 999,
                      "reset": ""
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/me": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "API key introspection \u2014 returns the caller's tier, monthly usage/limit/remaining, per-request rate limit window, reset d",
        "description": "API key introspection \u2014 returns the caller's tier, monthly usage/limit/remaining, per-request rate limit window, reset date, and a masked key preview.\n\n**Note:** CORRECTION vs. task brief: this route is API-KEY auth (X-API-Key header / apikey / api_key query param -> verifyApiKey), NOT Firebase session auth (getServerUser) \u2014 it does not import server-auth at all. Only webhooks/route.ts and clv-archive/[eventId]/route.ts use session auth in this batch of 14. PER_TIER_REQUEST_LIMITS here: free=100, api=10000 (note: api's 10000 differs from the 500-ish caps seen on most other free/api routes).",
        "operationId": "get_me",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "tier": "free",
                    "monthly_limit": 1000,
                    "current_usage": 42,
                    "remaining": 958,
                    "usage_pct": 4.2,
                    "reset_date": "2026-10-01T00:00:00.000Z",
                    "per_request_limit": 100,
                    "rate_limit_window": {
                      "limit": 60,
                      "remaining": 55,
                      "reset": "2026-09-03T00:05:00.000Z"
                    },
                    "api_key_preview": "krok...ab12"
                  },
                  "meta": {
                    "timestamp": "2026-09-03T00:00:00.000Z"
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/status": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Public health/sync-freshness check \u2014 reports whether the arbs/snipes opportunity sync is healthy based on how recently t",
        "description": "Public health/sync-freshness check \u2014 reports whether the arbs/snipes opportunity sync is healthy based on how recently the newest doc was written.\n\n**Note:** force-dynamic route, no API key check at all. Rate-limited as tier 'free' under identifier 'anonymous' (shared bucket across all anonymous callers). 'degraded' means the newest arbs/snipes row is >5 minutes old. Reads Supabase supabaseMaxUpdatedAt('arbs'/'snipes') (indexed updated_at column) when isSupabase('opportunities'), else falls back to Firestore orderBy('detectedAt','desc').limit(1) \u2014 per an in-code historical note, an earlier version ordered by a field ('timestamp') the writer never set, which permanently reported 'degraded' (false alarm); see CLAUDE.md memory 'health-check-must-read-supabase'. Cached 30s via getCached().",
        "operationId": "get_status",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": {
                    "status": "operational",
                    "sync": {
                      "healthy": true,
                      "last_sync_seconds_ago": 42
                    },
                    "timestamp": "2026-09-03T00:00:00.000Z"
                  },
                  "meta": {}
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": []
      }
    },
    "/stream/opportunities": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "Server-Sent Events (SSE) stream of live opportunity deltas (arbs/snipes/middles/low_holds) \u2014 initial full snapshot per t",
        "description": "Server-Sent Events (SSE) stream of live opportunity deltas (arbs/snipes/middles/low_holds) \u2014 initial full snapshot per type, then add/remove/update deltas on a 5s poll, with 15s keepalive heartbeats.\n\n**Note:** SSE, not a normal JSON envelope. Hard-caps the connection at 10 minutes wall-clock (MAX_STREAM_MS) then sends a 'bye' event and closes \u2014 clients should reconnect via EventSource's built-in retry. limit is fixed per tier (free=100, api=500), not client-adjustable. Uses polling (not Firestore onSnapshot) deliberately: Admin onSnapshot listeners can't be safely torn down across cold-start boundaries in the App Router serverless runtime. Rows older than 12h past commence_time (STALE_CUTOFF_MS) are filtered out of every snapshot/delta. Reads Supabase (supabaseLatestDocs) when isSupabase('opportunities'), else Firestore with server-side orderBy/where.",
        "operationId": "get_stream_opportunities",
        "parameters": [
          {
            "name": "types",
            "in": "query",
            "required": false,
            "description": "Subset of arbs|snipes|middles|low_holds to stream. Invalid/empty result -> 400.",
            "schema": {
              "type": "string",
              "default": "arbs,snipes,middles,low_holds"
            }
          },
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter events by sport.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_value",
            "in": "query",
            "required": false,
            "description": "Minimum edge value filter, ignored for the low_holds type (which sorts by holdPct instead).",
            "schema": {
              "type": "number",
              "default": 0
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "note": "This is an SSE stream (Content-Type: text/event-stream), not a single JSON response. Example frames:",
                  "frames": [
                    "event: ready\ndata: {\"types\":[\"arbs\",\"snipes\",\"middles\",\"low_holds\"],\"sport_key\":null,\"min_value\":0,\"poll_ms\":5000}\n\n",
                    "event: snapshot\ndata: {\"type\":\"arbs\",\"rows\":[{\"id\":\"a1\",\"value\":3.2,\"sport_key\":\"basketball_nba\"}]}\n\n",
                    "event: delta\ndata: {\"type\":\"arbs\",\"added\":[],\"removed\":[\"a2\"],\"updated\":[{\"id\":\"a1\",\"value\":3.5}]}\n\n",
                    ": keepalive 1735689600000\n\n",
                    "event: bye\ndata: {\"reason\":\"max_stream_duration\"}\n\n"
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/webhooks": {
      "get": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "CRUD for outbound webhook subscriptions: GET lists the caller's webhooks, POST registers a new one (returns a one-time-v",
        "description": "CRUD for outbound webhook subscriptions: GET lists the caller's webhooks, POST registers a new one (returns a one-time-visible secret), DELETE soft-deletes (deactivates) one by id.\n\n**Note:** Auth is Firebase session (getServerUser()) via getServerUser \u2014 NOT X-API-Key. Tier gate: GET reads tier from ANY active API key belonging to the user (getUserTier -> listUserApiKeys); POST requires the user to have at least one active API key (403 'No active API key' otherwise) and uses that key's tier. Writes are Supabase-only (supabaseUpsert on POST, supabaseMerge soft-delete on DELETE) with a Firestore-read fallback only on GET's list query and on ownership lookup in DELETE. GET response always strips the `secret` field (set to undefined) except at creation time in the POST response, which is the only time the secret is ever returned. Response envelope here does NOT use the standard {success,data,meta} shape on GET (bare {webhooks:[...]}) or POST (bare fields) \u2014 inconsistent with the rest of the v1 API.\n\n**Auth note:** this endpoint uses the dashboard's Firebase session cookie, not an API key \u2014 it is not callable by third-party API integrations.",
        "operationId": "get_webhooks",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "webhooks": [
                    {
                      "id": "wh_1",
                      "userId": "u1",
                      "url": "https://example.com/hook",
                      "events": [
                        "arb",
                        "ev",
                        "middle"
                      ],
                      "minValue": 0,
                      "active": true,
                      "createdAt": "2026-09-01T00:00:00.000Z",
                      "deliveryCount": 12,
                      "failureCount": 0,
                      "secret": null
                    }
                  ]
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url (POST)": {
                    "type": "string",
                    "description": "Destination HTTPS URL. Passed through checkWebhookUrl() SSRF guard (rejects internal/private-network targets) before being accepted."
                  },
                  "events (POST)": {
                    "type": "string",
                    "default": "[\"arb\",\"ev\",\"middle\"]",
                    "description": "Event types to subscribe to."
                  },
                  "minValue (POST)": {
                    "type": "number",
                    "default": 0,
                    "description": "Minimum edge value threshold to trigger delivery."
                  },
                  "id (DELETE)": {
                    "type": "string",
                    "description": "Webhook id to deactivate; 404 if the caller doesn't own it."
                  }
                },
                "required": [
                  "url (POST)",
                  "id (DELETE)"
                ]
              }
            }
          }
        }
      },
      "post": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "CRUD for outbound webhook subscriptions: GET lists the caller's webhooks, POST registers a new one (returns a one-time-v",
        "description": "CRUD for outbound webhook subscriptions: GET lists the caller's webhooks, POST registers a new one (returns a one-time-visible secret), DELETE soft-deletes (deactivates) one by id.\n\n**Note:** Auth is Firebase session (getServerUser()) via getServerUser \u2014 NOT X-API-Key. Tier gate: GET reads tier from ANY active API key belonging to the user (getUserTier -> listUserApiKeys); POST requires the user to have at least one active API key (403 'No active API key' otherwise) and uses that key's tier. Writes are Supabase-only (supabaseUpsert on POST, supabaseMerge soft-delete on DELETE) with a Firestore-read fallback only on GET's list query and on ownership lookup in DELETE. GET response always strips the `secret` field (set to undefined) except at creation time in the POST response, which is the only time the secret is ever returned. Response envelope here does NOT use the standard {success,data,meta} shape on GET (bare {webhooks:[...]}) or POST (bare fields) \u2014 inconsistent with the rest of the v1 API.\n\n**Auth note:** this endpoint uses the dashboard's Firebase session cookie, not an API key \u2014 it is not callable by third-party API integrations.",
        "operationId": "post_webhooks",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "id": "wh_2",
                  "url": "https://example.com/hook2",
                  "events": [
                    "arb",
                    "ev",
                    "middle"
                  ],
                  "secret": "whsec_ab12...",
                  "message": "Webhook registered. Save your secret \u2014 it will not be shown again."
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url (POST)": {
                    "type": "string",
                    "description": "Destination HTTPS URL. Passed through checkWebhookUrl() SSRF guard (rejects internal/private-network targets) before being accepted."
                  },
                  "events (POST)": {
                    "type": "string",
                    "default": "[\"arb\",\"ev\",\"middle\"]",
                    "description": "Event types to subscribe to."
                  },
                  "minValue (POST)": {
                    "type": "number",
                    "default": 0,
                    "description": "Minimum edge value threshold to trigger delivery."
                  },
                  "id (DELETE)": {
                    "type": "string",
                    "description": "Webhook id to deactivate; 404 if the caller doesn't own it."
                  }
                },
                "required": [
                  "url (POST)",
                  "id (DELETE)"
                ]
              }
            }
          }
        }
      },
      "delete": {
        "tags": [
          "Infrastructure"
        ],
        "summary": "CRUD for outbound webhook subscriptions: GET lists the caller's webhooks, POST registers a new one (returns a one-time-v",
        "description": "CRUD for outbound webhook subscriptions: GET lists the caller's webhooks, POST registers a new one (returns a one-time-visible secret), DELETE soft-deletes (deactivates) one by id.\n\n**Note:** Auth is Firebase session (getServerUser()) via getServerUser \u2014 NOT X-API-Key. Tier gate: GET reads tier from ANY active API key belonging to the user (getUserTier -> listUserApiKeys); POST requires the user to have at least one active API key (403 'No active API key' otherwise) and uses that key's tier. Writes are Supabase-only (supabaseUpsert on POST, supabaseMerge soft-delete on DELETE) with a Firestore-read fallback only on GET's list query and on ownership lookup in DELETE. GET response always strips the `secret` field (set to undefined) except at creation time in the POST response, which is the only time the secret is ever returned. Response envelope here does NOT use the standard {success,data,meta} shape on GET (bare {webhooks:[...]}) or POST (bare fields) \u2014 inconsistent with the rest of the v1 API.\n\n**Auth note:** this endpoint uses the dashboard's Firebase session cookie, not an API key \u2014 it is not callable by third-party API integrations.",
        "operationId": "delete_webhooks",
        "parameters": [],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                },
                "example": {
                  "success": true
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "SessionAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "properties": {
                  "url (POST)": {
                    "type": "string",
                    "description": "Destination HTTPS URL. Passed through checkWebhookUrl() SSRF guard (rejects internal/private-network targets) before being accepted."
                  },
                  "events (POST)": {
                    "type": "string",
                    "default": "[\"arb\",\"ev\",\"middle\"]",
                    "description": "Event types to subscribe to."
                  },
                  "minValue (POST)": {
                    "type": "number",
                    "default": 0,
                    "description": "Minimum edge value threshold to trigger delivery."
                  },
                  "id (DELETE)": {
                    "type": "string",
                    "description": "Webhook id to deactivate; 404 if the caller doesn't own it."
                  }
                },
                "required": [
                  "url (POST)",
                  "id (DELETE)"
                ]
              }
            }
          }
        }
      }
    },
    "/injuries": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Latest injury reports across sports/racing, filterable by sport and team.",
        "description": "Latest injury reports across sports/racing, filterable by sport and team.\n\n**Note:** NOT an alias of /api/v1/sports/injuries. Both read the same injuries_current collection, but sports/injuries adds status_severity classification (out/doubtful/questionable/probable), body_part, and a cross-sport sport_breakdown summary in meta \u2014 its doc comment explicitly says it 'extends' this racing/general-purpose endpoint with multi-sport coverage. Treat as two distinct, overlapping endpoints, not a redirect/alias pair. Response cached 600s free / 120s api via unstable_cache.",
        "operationId": "get_injuries",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (lowercased), e.g. \"aussierules_afl\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on team name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows returned. Capped at 100 on free tier, 5000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "afl_2026_c-daniel-rioli",
                      "sport_key": "aussierules_afl",
                      "player_name": "Daniel Rioli",
                      "player_slug": "daniel-rioli",
                      "team": "Richmond",
                      "status": "Out",
                      "reason": "Hamstring strain",
                      "date": "2026-09-01",
                      "season": 2026,
                      "source": "official-team-report",
                      "updated_at": "2026-09-02T21:14:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 998,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds-history": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Per-event opening vs. closing H2H odds with movement direction, deduped across re-keyed event ids.",
        "description": "Per-event opening vs. closing H2H odds with movement direction, deduped across re-keyed event ids.\n\n**Note:** Requires the paid 'api' tier (feature 'historical'). Two backing shapes depending on data source: primary reads the permanent `odds_history` table (snake_case fields, full bookmaker+opening_odds arrays, response includes them raw); fallback (if odds_history empty/errors) reads the legacy `gameday_odds_history` mirror (camelCase opens/latest/snapshots) then the BigQuery archive. Dedup: same fixture can carry multiple event_ids after upstream re-keying \u2014 stale rows are filtered out by default via staleFixtureIds() unless include_duplicates=true.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_odds_history",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (lowercased).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "Filter to a single event.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "include_snapshots",
            "in": "query",
            "required": false,
            "description": "Set \"true\" to include the full time-series snapshot array (legacy gameday_odds_history source only).",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "include_duplicates",
            "in": "query",
            "required": false,
            "description": "Set \"true\" to include stale rows for fixtures that were re-keyed with a new event_id upstream (flagged duplicate: true). Default is freshest-row-only.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 25 on free tier, 1000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 25
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "e8f2a1-nrl-2026-09-04",
                      "event_id": "e8f2a1",
                      "sport_key": "rugbyleague_nrl",
                      "home_team": "Penrith Panthers",
                      "away_team": "Melbourne Storm",
                      "commence_time": "2026-09-04T09:30:00Z",
                      "opens": {
                        "home": 1.65,
                        "away": 2.25,
                        "opened_at_ms": null,
                        "home_implied_prob": 0.6061,
                        "away_implied_prob": 0.4444
                      },
                      "latest": {
                        "home": 1.58,
                        "away": 2.45,
                        "home_book": "Sportsbet",
                        "away_book": "Sportsbet",
                        "updated_at_ms": null,
                        "home_implied_prob": 0.6329,
                        "away_implied_prob": 0.4082
                      },
                      "movement": {
                        "home_delta": -0.07,
                        "away_delta": 0.2,
                        "home_direction": "shorten",
                        "away_direction": "drift"
                      },
                      "opening_odds": [],
                      "bookmakers": [],
                      "snapshot_count": 0,
                      "updated_at": "2026-09-03T02:11:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 25,
                    "requested_limit": 25,
                    "include_snapshots": false,
                    "include_duplicates": false,
                    "duplicates_found": 0,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 4990,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/odds/movement": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Historical odds movement for one event/market from the append-only odds_archive firehose, with steam-move and line-drive",
        "description": "Historical odds movement for one event/market from the append-only odds_archive firehose, with steam-move and line-drive detection.\n\n**Note:** Reads the `odds_archive` table (per CLAUDE.md: max-fidelity, Supabase-only, append-only, month-partitioned, never pruned). Rows grouped by (market, bookmaker) then by selection; steam_move = >=5% implied-probability shift across snapshots (n>=3), line_drive = >=60% of consecutive moves in the same direction (n>=2). Uses guardV1Request/v1Success helper (newer pattern) rather than the older per-route boilerplate.",
        "operationId": "get_odds_movement",
        "parameters": [
          {
            "name": "event_id",
            "in": "query",
            "required": true,
            "description": "Event identifier. 400 error if missing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Market filter, e.g. \"h2h\", \"spreads\", \"totals\".",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Filter to a single bookmaker.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "from",
            "in": "query",
            "required": false,
            "description": "Start date filter on captured_at.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "to",
            "in": "query",
            "required": false,
            "description": "End date filter on captured_at (queried as to+'T23:59:59').",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max snapshots per outcome group, clamped 1-500.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "event_id": "e8f2a1",
                      "sport_key": "rugbyleague_nrl",
                      "home_team": "Penrith Panthers",
                      "away_team": "Melbourne Storm",
                      "market": "h2h",
                      "outcomes": [
                        {
                          "selection": "Penrith Panthers",
                          "snapshots": [
                            {
                              "timestamp": "2026-09-01T00:00:00Z",
                              "odds": 1.65,
                              "line": null,
                              "implied_prob": 0.6061,
                              "juice": null
                            },
                            {
                              "timestamp": "2026-09-03T02:00:00Z",
                              "odds": 1.58,
                              "line": null,
                              "implied_prob": 0.6329,
                              "juice": null
                            }
                          ],
                          "steam_move": false,
                          "line_drive": true,
                          "open_odds": 1.65,
                          "close_odds": 1.58,
                          "move_pct": -4.24
                        }
                      ],
                      "detected_at": "2026-09-01T00:00:00Z",
                      "bookmaker": "sportsbet"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "event_id": "e8f2a1",
                    "market": null,
                    "bookmaker": null,
                    "from": null,
                    "to": null,
                    "limit": 100,
                    "steam_moves_detected": 0,
                    "line_drives_detected": 1,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 999,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/opportunity-history": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Archived arb/+EV opportunities that have since expired (cleaned up after 24h).",
        "description": "Archived arb/+EV opportunities that have since expired (cleaned up after 24h).\n\n**Note:** Archive-tier endpoint (CREDIT_COST=5, comment: 'archive endpoint (historical opportunities)'). Feature 'historical' gates it to the paid 'api' tier only. Reads Supabase `opportunity_history` collection ordered by archivedAt desc.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_opportunity_history",
        "parameters": [
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "\"arb\" or \"ev\" only; any other value returns 400.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Filter by sport key (lowercased).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Clamped by the key's historicalDays entitlement via clampHistoricalFrom().",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max rows. Capped at 50 on free tier, 2000 on api tier (though feature itself is api-only).",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "arb-2026-09-02-x91k",
                      "type": "arb",
                      "sport_key": "basketball_nba",
                      "event_id": "b7c3d9",
                      "event_name": "Boston Celtics vs Denver Nuggets",
                      "commence_time": "2026-09-02T00:10:00Z",
                      "market_key": "h2h",
                      "profit_pct": 2.14,
                      "ev_pct": null,
                      "stake": 100,
                      "legs": [
                        {
                          "bookmaker": "Ladbrokes",
                          "outcome": "Boston Celtics",
                          "odds": 2.05
                        },
                        {
                          "bookmaker": "TAB",
                          "outcome": "Denver Nuggets",
                          "odds": 2.15
                        }
                      ],
                      "archived_at": "2026-09-02T00:12:00.000Z",
                      "created_at": "2026-09-01T23:40:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 50,
                    "requested_limit": 50,
                    "type": null,
                    "sport_key": null,
                    "since": null,
                    "note": "Records cleaned up after 24 hours",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 200,
                      "remaining": 4995,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/player-props-results": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Settled player-prop outcomes (hit/miss/actual value) per event.",
        "description": "Settled player-prop outcomes (hit/miss/actual value) per event.\n\n**Note:** Truth source is `player_props_results.outcomes[]` per repo memory (prop-backtest-data-model). event_id path is a single doc fetch keyed on eventId (slashes replaced with underscores, truncated to 1500 chars); without event_id it's a settled_at-ordered scan.",
        "operationId": "get_player_props_results",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Post-filtered in memory (sportKey has no extracted column).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "If set, does a direct doc lookup by id instead of a scan.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player_name",
            "in": "query",
            "required": false,
            "description": "Case-insensitive partial match, filters within each event's outcomes array.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "market",
            "in": "query",
            "required": false,
            "description": "Exact match (lowercased) on outcome market_key.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Clamped by clampHistoricalFrom(); filters/orders on the indexed settled_at column.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 50 on free tier, 2000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "event_id": "b7c3d9",
                      "sport_key": "basketball_nba",
                      "event_name": "Boston Celtics vs Denver Nuggets",
                      "commence_time": "2026-09-02T00:10:00Z",
                      "completed_at": "2026-09-02T02:45:00Z",
                      "outcomes": [
                        {
                          "market_key": "player_points",
                          "player_name": "Jayson Tatum",
                          "line": 27.5,
                          "side": "over",
                          "hit": true,
                          "actual_value": 31
                        }
                      ],
                      "outcome_count": 1,
                      "updated_at": "2026-09-02T03:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": null,
                    "event_id": null,
                    "player_name": null,
                    "market": null,
                    "since": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 998,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/player-props-stats": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Aggregate hit/miss/push counts and hit rate per player/market/line/side.",
        "description": "Aggregate hit/miss/push counts and hit rate per player/market/line/side.\n\n**Note:** Backed by Supabase collection `player_props_stats` (same table used by /api/v1/players/compare for its per-player prop breakdown). hit_rate = hits / (hits+misses), pushes excluded from denominator.",
        "operationId": "get_player_props_stats",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player_slug",
            "in": "query",
            "required": false,
            "description": "Exact match, post-filtered in memory after fetch.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player_canonical",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "market_key",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "side",
            "in": "query",
            "required": false,
            "description": "Must be \"over\" or \"under\" or it is ignored.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 50 on free tier, 2000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "player_props_stats-jayson-tatum-player_points-27.5-over",
                      "sport_key": "basketball_nba",
                      "market_key": "player_points",
                      "player_name": "Jayson Tatum",
                      "player_slug": "jayson-tatum",
                      "canonical_key": "nba_jayson-tatum",
                      "line": 27.5,
                      "side": "over",
                      "hits": 34,
                      "misses": 21,
                      "pushes": 2,
                      "total": 57,
                      "decided": 55,
                      "hit_rate": 0.6182,
                      "last_result_at": "2026-09-02T03:00:00.000Z",
                      "updated_at": "2026-09-02T03:05:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "sport_key": null,
                    "player_slug": null,
                    "player_canonical": null,
                    "market_key": null,
                    "side": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 998,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/player-props/projections": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "AFL player projections branded as Krok Odds Projections, sourced from an advanced statistical model feed.",
        "description": "AFL player projections branded as Krok Odds Projections, sourced from an advanced statistical model feed.\n\n**Note:** Route comment mandates branding this 'Krok Odds Projections' \u2014 never the underlying source name. Backed by Supabase collection `external_fryzigg_player_stats` (AFL only). `confidence` is a derived field = min(1, |Rating|/10), not a true model confidence. Cache TTL fixed at 3600s regardless of tier (not tier-scaled like most other v1 routes).",
        "operationId": "get_player_props_projections",
        "parameters": [
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Season year filter; 400 if non-numeric.",
            "schema": {
              "type": "integer",
              "default": "current year"
            }
          },
          {
            "name": "round",
            "in": "query",
            "required": false,
            "description": "Round number filter; 400 if non-numeric.",
            "schema": {
              "type": "integer"
            }
          },
          {
            "name": "team",
            "in": "query",
            "required": false,
            "description": "Exact match, case-insensitive, on team.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "player",
            "in": "query",
            "required": false,
            "description": "Case-insensitive partial match on player name.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Clamped 1-500 (MAX_LIMIT).",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "fryzigg_2026_r23_marcus-bontempelli",
                      "player_name": "Marcus Bontempelli",
                      "team": "Western Bulldogs",
                      "season": 2026,
                      "round": 23,
                      "game": "WB_vs_GEE_R23",
                      "date": "2026-08-30",
                      "opposition": "Geelong",
                      "kicks": 18,
                      "marks": 6,
                      "handballs": 12,
                      "tackles": 5,
                      "goals": 1,
                      "behinds": 0,
                      "hit_outs": 0,
                      "inside_50s": 4,
                      "clearances": 7,
                      "clangers": 2,
                      "rebound_50s": 1,
                      "frees_for": 2,
                      "frees_against": 1,
                      "time_on_ground_pct": 88,
                      "rating": 132.4,
                      "disposal_rating": 27.1,
                      "confidence": 1
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "license": "krokodds-derived",
                    "season": 2026,
                    "round": null,
                    "team": null,
                    "player": null,
                    "source": "Krok Odds Projections",
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 500,
                      "remaining": 999,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/players/compare": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Compare 2-5 players' prop hit rates and season stats side by side; adds tennis H2H record and Elo when both players are ",
        "description": "Compare 2-5 players' prop hit rates and season stats side by side; adds tennis H2H record and Elo when both players are ATP/WTA.\n\n**Note:** H2H + Elo enrichment only fires when sport_key contains 'tennis' AND exactly 2 players given; Elo is looked up only for the first player's slug in the current code (second player's Elo (_eloB) is fetched but discarded/unused). Elo source table is `tennis_players`, tried under id `atp_{slug}` then `wta_{slug}`. Uses guardV1Request/v1Success pattern.",
        "operationId": "get_players_compare",
        "parameters": [
          {
            "name": "players",
            "in": "query",
            "required": true,
            "description": "Min 2, max 5 slugs/canonical keys; 400 if <2 or >5.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "season",
            "in": "query",
            "required": false,
            "description": "Accepted but currently unused inside loadPlayerStats (param name is _season).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "metrics",
            "in": "query",
            "required": false,
            "description": "Documented in the file header comment but not read/implemented in the current GET handler.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Max prop rows fetched per player, clamped 1-100.",
            "schema": {
              "type": "integer",
              "default": 20
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "slug": "novak-djokovic",
                      "name": "Novak Djokovic",
                      "sport_key": "tennis_atp",
                      "props": [
                        {
                          "market_key": "total_games_over_under",
                          "line": 21.5,
                          "side": "over",
                          "hit_rate": 62.5,
                          "sample": 16,
                          "hits": 10,
                          "misses": 6
                        }
                      ],
                      "season_stats": {
                        "wins": 38,
                        "losses": 6
                      },
                      "h2h": {
                        "opponent_slug": "carlos-alcaraz",
                        "opponent_name": "Carlos Alcaraz",
                        "wins": 6,
                        "losses": 5,
                        "meetings": [
                          {
                            "date": "2026-07-14",
                            "winner": "Carlos Alcaraz",
                            "loser": "Novak Djokovic",
                            "score": "6-4 6-7 7-6",
                            "surface": "grass"
                          }
                        ],
                        "surface_split": {
                          "hard": {
                            "a": 3,
                            "b": 3
                          },
                          "clay": {
                            "a": 2,
                            "b": 1
                          },
                          "grass": {
                            "a": 1,
                            "b": 1
                          }
                        },
                        "elo": {
                          "overall": 2231,
                          "hard": 2240,
                          "clay": 2180,
                          "grass": 2260
                        }
                      }
                    },
                    {
                      "slug": "carlos-alcaraz",
                      "name": "Carlos Alcaraz",
                      "sport_key": "tennis_atp",
                      "props": [
                        {
                          "market_key": "total_games_over_under",
                          "line": 21.5,
                          "side": "over",
                          "hit_rate": 58.3,
                          "sample": 12,
                          "hits": 7,
                          "misses": 5
                        }
                      ],
                      "season_stats": {
                        "wins": 41,
                        "losses": 5
                      }
                    }
                  ],
                  "meta": {
                    "count": 2,
                    "tier": "free",
                    "players": [
                      "novak-djokovic",
                      "carlos-alcaraz"
                    ],
                    "sport_key": "tennis_atp",
                    "season": null,
                    "limit": 20,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 20,
                      "remaining": 999,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/results": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Completed game results (scores, winner) from cold-storage archive.",
        "description": "Completed game results (scores, winner) from cold-storage archive.\n\n**Note:** Archive tier (CREDIT_COST=5, comment: 'archive endpoint (game results / cold storage)'). Empty results are refunded in full via refundApiKeyCredits and logged as HTTP-status 204 in the internal request log (though the actual response status returned is still 200). Rows with an empty resolved sport_key are silently dropped \u2014 flagged in-code as an upstream the-odds-api /scores data-quality issue.\n\n**Requires the paid API plan** (A$49/mo) \u2014 not available on the free tier.",
        "operationId": "get_results",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "Matched via an OR filter across sport_key / data->>sportKey / data->>sport_key to cover inconsistent writer casing.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "since",
            "in": "query",
            "required": false,
            "description": "Clamped by clampHistoricalFrom(); filters commence_time >= since.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 100 on free tier, 5000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "b7c3d9",
                      "game_id": "b7c3d9",
                      "sport_key": "basketball_nba",
                      "home_team": "Boston Celtics",
                      "away_team": "Denver Nuggets",
                      "home_score": 112,
                      "away_score": 104,
                      "winner": "home",
                      "commence_time": "2026-09-02T00:10:00Z",
                      "completed": true,
                      "completed_at": "2026-09-02T02:45:00Z",
                      "resolved": true,
                      "settled_at": "2026-09-02T02:50:00.000Z",
                      "resolution_source": "the-odds-api",
                      "ingested_at": "2026-09-02T02:46:00.000Z",
                      "first_published_at": "2026-09-02T02:46:00.000Z",
                      "status": "final"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "api",
                    "limit": 100,
                    "requested_limit": 100,
                    "since": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 500,
                      "remaining": 4995,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/sport-activity": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Reference list of active/inactive sport keys and groupings (which sports currently have live coverage).",
        "description": "Reference list of active/inactive sport keys and groupings (which sports currently have live coverage).\n\n**Note:** No tierAllowsFeature() call in this route at all \u2014 any valid API key on any tier can hit it, it's not gated behind a V1Feature. Reads Supabase `sport_activity_global`. Response uses `private, max-age` Cache-Control (not the 'no-store' pattern most other v1 routes use) with an explicit comment warning against ever making this response public/CDN-cached since it's auth-gated per key.",
        "operationId": "get_sport_activity",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "If set, does a direct single-doc lookup instead of a scan.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "group",
            "in": "query",
            "required": false,
            "description": "Case-insensitive exact match on group, post-filtered in memory.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "active",
            "in": "query",
            "required": false,
            "description": "\"true\" restricts the scan to active=true rows.",
            "schema": {
              "type": "boolean",
              "default": false
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 100 on free tier, 5000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "key": "aussierules_afl",
                      "group": "Australian Rules",
                      "title": "AFL",
                      "description": "Australian Football League",
                      "active": true,
                      "has_outrights": true,
                      "updated_at": "2026-09-03T01:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "sport_key": null,
                    "group": null,
                    "active": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 999,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/steam-moves": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Recently detected steam moves (sudden multi-book odds shortening/drifting) across sports.",
        "description": "Recently detected steam moves (sudden multi-book odds shortening/drifting) across sports.\n\n**Note:** Reads Supabase `steam_moves` collection (distinct from the raw-archive-derived /api/v1/odds/movement, which computes steam detection on the fly from odds_archive). This route consumes pre-computed steam-move docs instead.",
        "operationId": "get_steam_moves",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "event_id",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "direction",
            "in": "query",
            "required": false,
            "description": "Must be \"shortening\" or \"drifting\"; anything else is ignored (treated as null).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "min_move_pct",
            "in": "query",
            "required": false,
            "description": "Filters rows whose movePct is below this threshold.",
            "schema": {
              "type": "number",
              "default": 0
            }
          },
          {
            "name": "bookmaker",
            "in": "query",
            "required": false,
            "description": "Matched defensively against bookmaker/book/bookmakers/books fields, none of which are reliably populated per in-code comment.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 50 on free tier, 1000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "steam-2026-09-03-nrl-001",
                      "event_id": "e8f2a1",
                      "event": "Penrith Panthers vs Melbourne Storm",
                      "sport_key": "rugbyleague_nrl",
                      "sport_title": "NRL",
                      "outcome": "Penrith Panthers",
                      "direction": "shortening",
                      "old_odds": 1.75,
                      "new_odds": 1.55,
                      "move_pct": -11.43,
                      "old_implied_prob": 0.5714,
                      "new_implied_prob": 0.6452,
                      "implied_prob_delta": 0.0738,
                      "bookmaker_count": 6,
                      "commence_time": "2026-09-04T09:30:00Z",
                      "detected_at": "2026-09-03T03:40:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "direction": null,
                    "min_move_pct": null,
                    "filters": {
                      "sport_key": null,
                      "event_id": null,
                      "direction": null,
                      "min_move_pct": null,
                      "bookmaker": null
                    },
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 999,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/teams/canonical": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Canonical team id/slug/full-name mapping used to join team names across bookmaker feeds.",
        "description": "Canonical team id/slug/full-name mapping used to join team names across bookmaker feeds.\n\n**Note:** No tierAllowsFeature() gate in this route \u2014 reachable by any valid key/tier. Reads Supabase `team_canonical`. `external_id` is only populated when the doc id does NOT contain '__' (ids containing '__' are treated as composite/internal, not an external system id).",
        "operationId": "get_teams_canonical",
        "parameters": [
          {
            "name": "sport_key",
            "in": "query",
            "required": false,
            "description": "",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "slug",
            "in": "query",
            "required": false,
            "description": "Exact match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "name",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match on fullName, post-filtered in memory.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 100 on free tier, 5000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "afl__brisbane-lions",
                      "sport_key": "aussierules_afl",
                      "external_id": null,
                      "participant_id": "afl__brisbane-lions",
                      "full_name": "Brisbane Lions",
                      "slug": "brisbane-lions",
                      "updated_at": "2026-08-20T00:00:00.000Z"
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 100,
                    "requested_limit": 100,
                    "sport_key": null,
                    "slug": null,
                    "name_contains": null,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "rate_limit": {
                      "limit": 100,
                      "remaining": 999,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    },
    "/weather": {
      "get": {
        "tags": [
          "Additional Data"
        ],
        "summary": "Raw observed + forecast weather for upcoming AU/NZ racing meetings.",
        "description": "Raw observed + forecast weather for upcoming AU/NZ racing meetings.\n\n**Note:** NOT an alias of /api/v1/sports/weather-impact. This route only returns raw observed/forecast weather embedded on `racing_meetings/{id}` docs (BOM/Open-Meteo via racingWeatherSync), racing-only, per its own file-header comment. /api/v1/sports/weather-impact is a separate, cross-sport route that additionally reads `external_openmeteo_forecasts`, adds heuristic impact_tags/impact_notes (high_wind, heavy_rain, extreme_heat, cold_conditions, high_humidity, storm_risk), and covers AFL/NRL/NFL/MLB/EPL/NCAAF venues too, not just racing. Also uses `getAdminDb()`/Firestore as a fallback path if the Supabase read fails or isSupabase('weather') is false \u2014 the only one of these 13 routes with a live Firestore fallback.",
        "operationId": "get_weather",
        "parameters": [
          {
            "name": "race_type",
            "in": "query",
            "required": false,
            "description": "Must be T (thoroughbred), H (harness) or G (greyhound); other values ignored.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "venue",
            "in": "query",
            "required": false,
            "description": "Case-insensitive substring match.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "state",
            "in": "query",
            "required": false,
            "description": "Exact match, uppercased, e.g. NSW/VIC/QLD.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "date",
            "in": "query",
            "required": false,
            "description": "Exact match; also checked against the key's historicalDays window \u2014 403 if too far in the past.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Capped at 50 on free tier, 1000 on api tier.",
            "schema": {
              "type": "integer",
              "default": 50
            }
          },
          {
            "name": "fields",
            "in": "query",
            "required": false,
            "description": "Field-projection allowlist parsed via parseFields()/projectRows() to trim the response shape.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Successful response",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SuccessEnvelope"
                },
                "example": {
                  "success": true,
                  "data": [
                    {
                      "id": "randwick-2026-09-06",
                      "venue": "Royal Randwick",
                      "state": "NSW",
                      "race_type": "T",
                      "date": "2026-09-06",
                      "track_hint": "Good 4",
                      "weather_updated_at": "2026-09-03T01:00:00.000Z",
                      "observed": {
                        "summary": "Partly cloudy",
                        "temp_c": 17,
                        "rain_24h_mm": 0,
                        "wind_kmh": 14,
                        "wind_dir": "SE",
                        "station": "Sydney Airport",
                        "observed_at": "2026-09-03T00:00:00.000Z"
                      },
                      "forecast": {
                        "date": "2026-09-06",
                        "min_c": 11,
                        "max_c": 19,
                        "precis": "Shower or two",
                        "rain_chance_pct": 60,
                        "rain_range_mm": "1-5",
                        "state": "NSW"
                      }
                    }
                  ],
                  "meta": {
                    "count": 1,
                    "tier": "free",
                    "limit": 50,
                    "requested_limit": 50,
                    "timestamp": "2026-09-03T04:00:00.000Z",
                    "coverage_note": "Racing meetings only (AU/NZ). Stadium/event-level weather not yet ingested.",
                    "rate_limit": {
                      "limit": 50,
                      "remaining": 999,
                      "reset": "2026-10-01T00:00:00.000Z"
                    }
                  }
                }
              }
            },
            "headers": {
              "X-RateLimit-Tier": {
                "schema": {
                  "type": "string"
                },
                "description": "Caller's tier (free|api)"
              },
              "X-RateLimit-Limit": {
                "schema": {
                  "type": "integer"
                },
                "description": "Per-request row cap for this tier"
              },
              "X-RateLimit-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Monthly credits remaining"
              },
              "X-RateLimit-Reset": {
                "schema": {
                  "type": "string",
                  "format": "date-time"
                },
                "description": "ISO date the monthly counter resets"
              },
              "X-Credits-Cost": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits debited for this request (0 if result was empty and refunded)"
              },
              "X-Credits-Remaining": {
                "schema": {
                  "type": "integer"
                },
                "description": "Credits remaining this month after this request"
              },
              "X-Cache-Age": {
                "schema": {
                  "type": "integer"
                },
                "description": "Enforced cache TTL in seconds for this response"
              },
              "X-Krok-Version": {
                "schema": {
                  "type": "string"
                },
                "description": "API version, currently '1'"
              }
            }
          },
          "204": {
            "description": "No content \u2014 query matched zero rows (credit refunded, not billed)"
          },
          "400": {
            "description": "Bad request \u2014 invalid query parameter",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "401": {
            "description": "Missing or invalid API key",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "402": {
            "description": "Monthly credit limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "403": {
            "description": "Your tier does not include this feature \u2014 upgrade required",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "429": {
            "description": "Rate limit exceeded",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          },
          "500": {
            "description": "Internal server error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorEnvelope"
                }
              }
            }
          }
        },
        "security": [
          {
            "ApiKeyHeader": []
          },
          {
            "ApiKeyQuery": []
          },
          {
            "ApiKeyQueryAlt": []
          }
        ]
      }
    }
  }
}