{
  "openapi": "3.1.0",
  "info": {
    "title": "PinnFeed API",
    "version": "1.0.0",
    "description": "PinnFeed is a self-serve API for real-time Pinnacle odds: normalized REST snapshots plus a WebSocket of deltas, no-vig fair prices, and closing-line history.\n\n## Authentication\nPass your API key (starts with `ps_live_`) via:\n- Header `x-api-key` (preferred for REST)\n- Header `Authorization: Bearer <key>`\n- Query `?key=` (browsers / WebSocket clients that cannot set headers)\n\n## WebSocket (not expressible as an OpenAPI path)\nConnect to `wss://pinnfeed.io/v1/stream` with the same key (`?key=` or `x-api-key` on the upgrade). Free and Hobby keys receive HTTP 403 `plan_lacks_ws` — WebSocket starts at Builder. After connect you receive a `welcome` frame; send `{\"type\":\"subscribe\",...}` within 30 seconds (or pass `sports` / `events` on the URL). Protocol details: https://pinnfeed.io/docs/websocket. Extension object: `x-websocket`.\n\n## Errors\nREST errors are JSON `{ \"error\": { \"code\", \"message\", ... } }`. See https://pinnfeed.io/docs/errors.\n\n## MCP\nStreamable HTTP MCP is at `POST https://pinnfeed.io/mcp` (same API key). Documented at https://pinnfeed.io/docs/mcp — listed below with a generic JSON-RPC body.\n",
    "contact": {
      "name": "PinnFeed support",
      "email": "support@pinnfeed.io",
      "url": "https://pinnfeed.io"
    },
    "termsOfService": "https://pinnfeed.io/legal/terms",
    "license": {
      "name": "Proprietary",
      "url": "https://pinnfeed.io/legal/terms"
    }
  },
  "servers": [
    {
      "url": "https://pinnfeed.io"
    }
  ],
  "tags": [
    {
      "name": "Account",
      "description": "Key and plan metadata"
    },
    {
      "name": "Odds",
      "description": "Sports, events, markets, drops"
    },
    {
      "name": "Closing lines",
      "description": "Historical closing odds (Builder and above)"
    },
    {
      "name": "MCP",
      "description": "Model Context Protocol (Streamable HTTP)"
    }
  ],
  "components": {
    "securitySchemes": {
      "ApiKeyHeader": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "Preferred. Example: ps_live_..."
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "ps_live_...",
        "description": "Authorization: Bearer ps_live_..."
      },
      "ApiKeyQuery": {
        "type": "apiKey",
        "in": "query",
        "name": "key",
        "description": "Query-string alternative for clients that cannot set headers"
      }
    },
    "parameters": {
      "sport": {
        "name": "sport",
        "in": "query",
        "description": "Sport slug(s) or id(s), comma-separated (alias: sports). Required on /v1/odds.",
        "schema": {
          "type": "string",
          "examples": [
            "soccer",
            "1,2",
            "basketball"
          ]
        }
      },
      "sports": {
        "name": "sports",
        "in": "query",
        "description": "Alias of sport.",
        "schema": {
          "type": "string"
        }
      },
      "league": {
        "name": "league",
        "in": "query",
        "description": "League id(s), comma-separated (alias: leagues).",
        "schema": {
          "type": "string",
          "examples": [
            "1980,2627"
          ]
        }
      },
      "leagues": {
        "name": "leagues",
        "in": "query",
        "description": "Alias of league.",
        "schema": {
          "type": "string"
        }
      },
      "event": {
        "name": "event",
        "in": "query",
        "description": "Event id(s), comma-separated (alias: events).",
        "schema": {
          "type": "string"
        }
      },
      "events": {
        "name": "events",
        "in": "query",
        "description": "Alias of event.",
        "schema": {
          "type": "string"
        }
      },
      "market": {
        "name": "market",
        "in": "query",
        "description": "Market type(s): moneyline, spread, total, team_total (alias: markets).",
        "schema": {
          "type": "string",
          "examples": [
            "moneyline,total"
          ]
        }
      },
      "markets": {
        "name": "markets",
        "in": "query",
        "description": "Alias of market.",
        "schema": {
          "type": "string"
        }
      },
      "live": {
        "name": "live",
        "in": "query",
        "description": "true / false / live / prematch. Omit for both.",
        "schema": {
          "type": "string",
          "examples": [
            "true",
            "false"
          ]
        }
      },
      "main": {
        "name": "main",
        "in": "query",
        "description": "If true, main lines only.",
        "schema": {
          "type": "string",
          "examples": [
            "true"
          ]
        }
      },
      "specials": {
        "name": "specials",
        "in": "query",
        "description": "If false, exclude specials/props (when the plan allows specials).",
        "schema": {
          "type": "string",
          "examples": [
            "false"
          ]
        }
      },
      "q": {
        "name": "q",
        "in": "query",
        "description": "Case-insensitive substring match on home, away, or league name.",
        "schema": {
          "type": "string"
        }
      },
      "limit": {
        "name": "limit",
        "in": "query",
        "description": "Page size. Defaults and caps vary by endpoint (see operation description).",
        "schema": {
          "type": "integer",
          "minimum": 1
        }
      },
      "offset": {
        "name": "offset",
        "in": "query",
        "description": "Pagination offset (events or closing rows).",
        "schema": {
          "type": "integer",
          "minimum": 0,
          "default": 0
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "code",
              "message"
            ],
            "properties": {
              "code": {
                "type": "string",
                "examples": [
                  "missing_key",
                  "invalid_key",
                  "rate_limited",
                  "daily_quota",
                  "invalid_filter",
                  "sport_not_in_plan",
                  "plan_lacks_history",
                  "not_found"
                ]
              },
              "message": {
                "type": "string"
              },
              "retryAfterMs": {
                "type": "integer",
                "description": "Present on rate_limited responses"
              },
              "upgrade_url": {
                "type": "string",
                "format": "uri",
                "description": "On plan-limit errors (403 plan_lacks_history, sport_not_in_plan; 429 rate_limited, daily_quota on Free/Hobby/Builder): link that starts the 3-day free trial of the plan that lifts the limit."
              },
              "upgrade": {
                "type": "object",
                "description": "Same target as upgrade_url: plan, price_eur_month, trial_days, url, pricing_url.",
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          }
        }
      },
      "League": {
        "type": "object",
        "properties": {
          "id": {
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "type": [
              "string",
              "null"
            ]
          },
          "group": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "Outcome": {
        "type": "object",
        "description": "Normalized outcome as built in src/core/normalize.js",
        "properties": {
          "name": {
            "type": "string"
          },
          "american": {
            "type": [
              "integer",
              "null"
            ]
          },
          "decimal": {
            "type": [
              "number",
              "null"
            ]
          },
          "points": {
            "type": "number"
          },
          "participantId": {
            "type": "integer"
          },
          "fair": {
            "type": "number",
            "description": "No-vig decimal when computable"
          },
          "fairProb": {
            "type": "number",
            "description": "No-vig probability when computable"
          }
        }
      },
      "Market": {
        "type": "object",
        "description": "Normalized market from src/core/normalize.js",
        "properties": {
          "id": {
            "type": "string"
          },
          "eventId": {
            "type": "integer"
          },
          "key": {
            "type": "string"
          },
          "type": {
            "type": "string",
            "enum": [
              "moneyline",
              "spread",
              "total",
              "team_total"
            ]
          },
          "period": {
            "type": "integer"
          },
          "side": {
            "type": [
              "string",
              "null"
            ]
          },
          "line": {
            "type": [
              "number",
              "null"
            ]
          },
          "alternate": {
            "type": "boolean"
          },
          "main": {
            "type": "boolean"
          },
          "live": {
            "type": "boolean"
          },
          "maxStake": {
            "type": [
              "number",
              "null"
            ]
          },
          "cutoffAt": {
            "type": [
              "string",
              "null"
            ]
          },
          "margin": {
            "type": [
              "number",
              "null"
            ]
          },
          "outcomes": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Outcome"
            }
          }
        }
      },
      "Event": {
        "type": "object",
        "description": "Normalized event from src/core/normalize.js",
        "properties": {
          "id": {
            "type": "integer"
          },
          "sportId": {
            "type": "integer"
          },
          "sport": {
            "type": "string"
          },
          "league": {
            "oneOf": [
              {
                "$ref": "#/components/schemas/League"
              },
              {
                "type": "null"
              }
            ]
          },
          "home": {
            "type": [
              "string",
              "null"
            ]
          },
          "away": {
            "type": [
              "string",
              "null"
            ]
          },
          "startTime": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "type": [
              "string",
              "null"
            ]
          },
          "live": {
            "type": "boolean"
          },
          "parentId": {
            "type": [
              "integer",
              "null"
            ]
          },
          "units": {
            "type": "string"
          },
          "special": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "category": {
                    "type": [
                      "string",
                      "null"
                    ]
                  },
                  "description": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                }
              }
            ]
          },
          "participants": {
            "type": "array",
            "description": "Present on specials only",
            "items": {
              "type": "object",
              "properties": {
                "id": {
                  "type": [
                    "integer",
                    "null"
                  ]
                },
                "name": {
                  "type": "string"
                }
              }
            }
          },
          "score": {
            "oneOf": [
              {
                "type": "null"
              },
              {
                "type": "object",
                "properties": {
                  "home": {
                    "type": [
                      "number",
                      "null"
                    ]
                  },
                  "away": {
                    "type": [
                      "number",
                      "null"
                    ]
                  }
                }
              }
            ]
          },
          "clock": {
            "type": [
              "integer",
              "null"
            ]
          },
          "markets": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Market"
            },
            "description": "Included on /v1/odds and /v1/events/{id}"
          }
        }
      },
      "Drop": {
        "type": "object",
        "description": "Price-drop row from DropTracker.query (src/core/drops.js)",
        "properties": {
          "eventId": {
            "type": "integer"
          },
          "sport": {
            "type": "string"
          },
          "league": {
            "type": [
              "string",
              "null"
            ]
          },
          "home": {
            "type": [
              "string",
              "null"
            ]
          },
          "away": {
            "type": [
              "string",
              "null"
            ]
          },
          "startTime": {
            "type": [
              "string",
              "null"
            ]
          },
          "live": {
            "type": "boolean"
          },
          "marketId": {
            "type": "string"
          },
          "market": {
            "type": "string"
          },
          "period": {
            "type": "integer"
          },
          "line": {
            "type": [
              "number",
              "null"
            ]
          },
          "outcome": {
            "type": "string"
          },
          "points": {
            "type": [
              "number",
              "null"
            ]
          },
          "from": {
            "type": "number"
          },
          "to": {
            "type": "number"
          },
          "dropPct": {
            "type": "number"
          },
          "movedAt": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ClosingRecord": {
        "type": "object",
        "description": "rowToClosing shape from src/closing.js",
        "properties": {
          "eventId": {
            "type": "integer"
          },
          "sportId": {
            "type": "integer"
          },
          "league": {
            "type": "object",
            "properties": {
              "id": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "home": {
            "type": [
              "string",
              "null"
            ]
          },
          "away": {
            "type": [
              "string",
              "null"
            ]
          },
          "startTime": {
            "type": "string"
          },
          "capturedAt": {
            "type": "string"
          },
          "market": {
            "type": "object",
            "properties": {
              "key": {
                "type": "string"
              },
              "type": {
                "type": "string"
              },
              "period": {
                "type": "integer"
              },
              "line": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "main": {
                "type": "boolean"
              },
              "margin": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "maxStake": {
                "type": [
                  "number",
                  "null"
                ]
              },
              "outcomes": {
                "description": "JSON stored at capture time; structure matches Outcome[] when present",
                "type": "array",
                "items": {
                  "type": "object",
                  "additionalProperties": true
                }
              }
            }
          }
        }
      },
      "Account": {
        "type": "object",
        "properties": {
          "plan": {
            "type": "string",
            "examples": [
              "free",
              "hobby",
              "builder",
              "pro"
            ]
          },
          "key": {
            "type": "object",
            "properties": {
              "id": {
                "type": "integer"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              }
            }
          },
          "limits": {
            "type": "object",
            "properties": {
              "ratePerSec": {
                "type": "number"
              },
              "perDay": {
                "type": [
                  "integer",
                  "null"
                ]
              },
              "wsConnections": {
                "type": "integer"
              },
              "sports": {
                "type": "array",
                "items": {
                  "type": "integer"
                }
              },
              "delayedSeconds": {
                "type": "integer"
              },
              "mainMarketsOnly": {
                "type": "boolean"
              },
              "history": {
                "type": "boolean"
              }
            }
          },
          "usage": {
            "type": "object",
            "properties": {
              "restRequestsToday": {
                "type": "integer"
              }
            }
          }
        }
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "Missing or invalid API key",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Plan restriction (sport or history)",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "NotFound": {
        "description": "Resource not found",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "BadRequest": {
        "description": "Invalid filter",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      },
      "TooManyRequests": {
        "description": "Rate limit or daily quota",
        "headers": {
          "Retry-After": {
            "schema": {
              "type": "integer"
            },
            "description": "Seconds (rate_limited)"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    }
  },
  "security": [
    {
      "ApiKeyHeader": []
    },
    {
      "BearerAuth": []
    },
    {
      "ApiKeyQuery": []
    }
  ],
  "paths": {
    "/v1/account": {
      "get": {
        "summary": "Account and plan limits",
        "description": "Returns plan id, key metadata, limits and today's REST usage for the authenticated key (`svc.account`).",
        "tags": [
          "Account"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Plan": {
                "schema": {
                  "type": "string"
                },
                "description": "Plan id of the authenticated key"
              },
              "X-Data-Delay-Seconds": {
                "schema": {
                  "type": "integer"
                },
                "description": "Present on delayed (Free) plans"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Account"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "operationId": "getAccount"
      }
    },
    "/v1/sports": {
      "get": {
        "summary": "List sports",
        "description": "Sports with live/prematch event counts and whether each sport is in the key's plan (`svc.sports`).",
        "tags": [
          "Odds"
        ],
        "parameters": [],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Plan": {
                "schema": {
                  "type": "string"
                },
                "description": "Plan id of the authenticated key"
              },
              "X-Data-Delay-Seconds": {
                "schema": {
                  "type": "integer"
                },
                "description": "Present on delayed (Free) plans"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sports": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "id": {
                            "type": "integer"
                          },
                          "slug": {
                            "type": "string"
                          },
                          "inPlan": {
                            "type": "boolean"
                          },
                          "live": {
                            "type": "integer"
                          },
                          "prematch": {
                            "type": "integer"
                          }
                        }
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "operationId": "listSports"
      }
    },
    "/v1/events": {
      "get": {
        "summary": "List events",
        "description": "Events without markets. Default `limit` 500, max 1000. Optional text filter `q`.",
        "tags": [
          "Odds"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/sport"
          },
          {
            "$ref": "#/components/parameters/sports"
          },
          {
            "$ref": "#/components/parameters/league"
          },
          {
            "$ref": "#/components/parameters/leagues"
          },
          {
            "$ref": "#/components/parameters/event"
          },
          {
            "$ref": "#/components/parameters/events"
          },
          {
            "$ref": "#/components/parameters/market"
          },
          {
            "$ref": "#/components/parameters/markets"
          },
          {
            "$ref": "#/components/parameters/live"
          },
          {
            "$ref": "#/components/parameters/main"
          },
          {
            "$ref": "#/components/parameters/specials"
          },
          {
            "$ref": "#/components/parameters/q"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Plan": {
                "schema": {
                  "type": "string"
                },
                "description": "Plan id of the authenticated key"
              },
              "X-Data-Delay-Seconds": {
                "schema": {
                  "type": "integer"
                },
                "description": "Present on delayed (Free) plans"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "operationId": "listEvents"
      }
    },
    "/v1/odds": {
      "get": {
        "summary": "Events with markets",
        "description": "Events including filtered markets — REST equivalent of a WebSocket snapshot. **`sport` (or `sports`) is required.** Default `limit` 200, max 1000. Optional `withMarkets` (truthy) is used by MCP to drop events with empty market lists.",
        "tags": [
          "Odds"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/sport"
          },
          {
            "$ref": "#/components/parameters/sports"
          },
          {
            "$ref": "#/components/parameters/league"
          },
          {
            "$ref": "#/components/parameters/leagues"
          },
          {
            "$ref": "#/components/parameters/event"
          },
          {
            "$ref": "#/components/parameters/events"
          },
          {
            "$ref": "#/components/parameters/market"
          },
          {
            "$ref": "#/components/parameters/markets"
          },
          {
            "$ref": "#/components/parameters/live"
          },
          {
            "$ref": "#/components/parameters/main"
          },
          {
            "$ref": "#/components/parameters/specials"
          },
          {
            "$ref": "#/components/parameters/q"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          },
          {
            "name": "withMarkets",
            "in": "query",
            "description": "If truthy, only events that currently have matching markets (and upcoming/live).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Plan": {
                "schema": {
                  "type": "string"
                },
                "description": "Plan id of the authenticated key"
              },
              "X-Data-Delay-Seconds": {
                "schema": {
                  "type": "integer"
                },
                "description": "Present on delayed (Free) plans"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "count": {
                      "type": "integer"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "events": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Event"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "operationId": "listOdds"
      }
    },
    "/v1/events/{id}": {
      "get": {
        "summary": "Single event with markets",
        "description": "One event plus its markets subject to filters and plan (`svc.event`).",
        "tags": [
          "Odds"
        ],
        "parameters": [
          {
            "name": "id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          },
          {
            "$ref": "#/components/parameters/sport"
          },
          {
            "$ref": "#/components/parameters/sports"
          },
          {
            "$ref": "#/components/parameters/league"
          },
          {
            "$ref": "#/components/parameters/leagues"
          },
          {
            "$ref": "#/components/parameters/event"
          },
          {
            "$ref": "#/components/parameters/events"
          },
          {
            "$ref": "#/components/parameters/market"
          },
          {
            "$ref": "#/components/parameters/markets"
          },
          {
            "$ref": "#/components/parameters/live"
          },
          {
            "$ref": "#/components/parameters/main"
          },
          {
            "$ref": "#/components/parameters/specials"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "event": {
                      "$ref": "#/components/schemas/Event"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "operationId": "getEvent"
      }
    },
    "/v1/drops": {
      "get": {
        "summary": "Recent price drops",
        "description": "Biggest shortening moves per market outcome over a rolling window (`svc.drops` / DropTracker).",
        "tags": [
          "Odds"
        ],
        "parameters": [
          {
            "$ref": "#/components/parameters/sport"
          },
          {
            "$ref": "#/components/parameters/sports"
          },
          {
            "$ref": "#/components/parameters/league"
          },
          {
            "$ref": "#/components/parameters/leagues"
          },
          {
            "$ref": "#/components/parameters/event"
          },
          {
            "$ref": "#/components/parameters/events"
          },
          {
            "$ref": "#/components/parameters/market"
          },
          {
            "$ref": "#/components/parameters/markets"
          },
          {
            "$ref": "#/components/parameters/live"
          },
          {
            "$ref": "#/components/parameters/main"
          },
          {
            "$ref": "#/components/parameters/specials"
          },
          {
            "name": "minutes",
            "in": "query",
            "description": "Window 1–60; default 15.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 60,
              "default": 15
            }
          },
          {
            "name": "min_pct",
            "in": "query",
            "description": "Minimum drop % (alias minPct); default 3; clamped 0.5–90.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "minPct",
            "in": "query",
            "description": "Alias of min_pct.",
            "schema": {
              "type": "number"
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "Default 50, max 200.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 200,
              "default": 50
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Plan": {
                "schema": {
                  "type": "string"
                },
                "description": "Plan id of the authenticated key"
              },
              "X-Data-Delay-Seconds": {
                "schema": {
                  "type": "integer"
                },
                "description": "Present on delayed (Free) plans"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "minutes": {
                      "type": "integer"
                    },
                    "minPct": {
                      "type": "number"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "drops": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Drop"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "operationId": "listDrops"
      }
    },
    "/v1/closing/{eventId}": {
      "get": {
        "summary": "Closing lines for one event",
        "description": "Requires plan.history (Builder and above). `svc.closing`.",
        "tags": [
          "Closing lines"
        ],
        "parameters": [
          {
            "name": "eventId",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "eventId": {
                      "type": "integer"
                    },
                    "markets": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ClosingRecord"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "operationId": "getClosingForEvent"
      }
    },
    "/v1/closing": {
      "get": {
        "summary": "Closing lines by UTC day",
        "description": "Requires plan.history. Query `date=YYYY-MM-DD` (default today UTC). Filters: sport/sports, main. Default limit 500, max 2000.",
        "tags": [
          "Closing lines"
        ],
        "parameters": [
          {
            "name": "date",
            "in": "query",
            "description": "UTC day YYYY-MM-DD",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
            }
          },
          {
            "$ref": "#/components/parameters/sport"
          },
          {
            "$ref": "#/components/parameters/sports"
          },
          {
            "$ref": "#/components/parameters/main"
          },
          {
            "$ref": "#/components/parameters/limit"
          },
          {
            "$ref": "#/components/parameters/offset"
          }
        ],
        "responses": {
          "200": {
            "description": "OK",
            "headers": {
              "X-Plan": {
                "schema": {
                  "type": "string"
                },
                "description": "Plan id of the authenticated key"
              },
              "X-Data-Delay-Seconds": {
                "schema": {
                  "type": "integer"
                },
                "description": "Present on delayed (Free) plans"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "date": {
                      "type": "string"
                    },
                    "offset": {
                      "type": "integer"
                    },
                    "limit": {
                      "type": "integer"
                    },
                    "count": {
                      "type": "integer"
                    },
                    "closing": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ClosingRecord"
                      }
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/BadRequest"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "429": {
            "$ref": "#/components/responses/TooManyRequests"
          }
        },
        "operationId": "listClosingByDay"
      }
    },
    "/mcp": {
      "post": {
        "summary": "MCP Streamable HTTP endpoint",
        "description": "JSON-RPC over Streamable HTTP. Authenticate with the same API key schemes as REST. Tool list and shapes: https://pinnfeed.io/docs/mcp. Request/response bodies are not fully enumerated here (JSON-RPC envelope).",
        "tags": [
          "MCP"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "description": "JSON-RPC 2.0 request envelope (method, params, id). Exact tool params are documented at /docs/mcp — left generic here.",
                "additionalProperties": true
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "JSON-RPC response",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "description": "JSON-RPC 2.0 response envelope",
                  "additionalProperties": true
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "405": {
            "description": "GET/DELETE not allowed; POST only"
          }
        },
        "operationId": "mcpJsonRpc"
      }
    }
  },
  "x-websocket": {
    "url": "wss://pinnfeed.io/v1/stream",
    "auth": [
      "query key",
      "header x-api-key"
    ],
    "subscribeTimeoutSeconds": 30,
    "pingIntervalSeconds": 20,
    "clientMessages": [
      "subscribe",
      "unsubscribe",
      "ping",
      "pong"
    ],
    "serverMessages": [
      "welcome",
      "subscribed",
      "unsubscribed",
      "snapshot",
      "delta",
      "ping",
      "pong",
      "error"
    ],
    "docs": "https://pinnfeed.io/docs/websocket",
    "planNote": "Builder (€99/mo) and above; Free/Hobby receive 403 plan_lacks_ws on upgrade"
  }
}
