{
  "openapi": "3.1.0",
  "info": {
    "title": "Investment Bets API",
    "version": "1.0.0",
    "description": "API used by the Investment Bets web app. Authenticated operations use an HttpOnly JWT cookie named auth_token."
  },
  "servers": [
    {
      "url": "https://api.investment-bets.com",
      "description": "Production API"
    }
  ],
  "security": [
    {
      "cookieAuth": []
    }
  ],
  "paths": {
    "/": {
      "get": {
        "summary": "Health check",
        "security": [],
        "responses": {
          "200": {
            "description": "API health response"
          }
        }
      }
    },
    "/register": {
      "post": {
        "summary": "Create an account",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Account created. No session cookie is set; call /login to authenticate."
          },
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "description": "Username or email already exists",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Database error",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/login": {
      "post": {
        "summary": "Start a cookie session",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Login succeeded and auth_token cookie was set",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/logout": {
      "post": {
        "summary": "Clear the current cookie session",
        "security": [],
        "responses": {
          "200": {
            "description": "Logged out"
          }
        }
      }
    },
    "/me": {
      "get": {
        "summary": "Return the current user",
        "responses": {
          "200": {
            "description": "Current user",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/User"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/leaderboard": {
      "get": {
        "summary": "List ranked users",
        "security": [],
        "responses": {
          "200": {
            "description": "Leaderboard rows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/LeaderboardUser"
                  }
                }
              }
            }
          }
        }
      }
    },
    "/portfolio": {
      "get": {
        "summary": "List active bets for the current user",
        "responses": {
          "200": {
            "description": "Active bets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PortfolioBet"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/history": {
      "get": {
        "summary": "List closed bets for the current user",
        "responses": {
          "200": {
            "description": "Closed bets",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/ClosedBet"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/feed": {
      "get": {
        "summary": "List activity from followed users",
        "responses": {
          "200": {
            "description": "Feed rows",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/FeedItem"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/following": {
      "get": {
        "summary": "List user IDs followed by the current user",
        "responses": {
          "200": {
            "description": "Followed user IDs",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "type": "integer"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/users/search": {
      "get": {
        "summary": "Search users by username",
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": false,
            "description": "Username substring to match. A missing or empty value returns an empty list.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Matching users",
            "content": {
              "application/json": {
                "schema": {
                  "type": "array",
                  "items": {
                    "$ref": "#/components/schemas/PublicUser"
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/user/{user_id}/public": {
      "get": {
        "summary": "Get a public user profile",
        "security": [],
        "parameters": [
          {
            "name": "user_id",
            "in": "path",
            "required": true,
            "schema": {
              "type": "integer"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Public profile, active portfolio, and trade history",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "user": {
                      "$ref": "#/components/schemas/PublicUser"
                    },
                    "portfolio": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/PortfolioBet"
                      }
                    },
                    "history": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ClosedBet"
                      }
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/price/{ticker}": {
      "get": {
        "summary": "Fetch the current server-side price for a ticker",
        "parameters": [
          {
            "name": "ticker",
            "in": "path",
            "required": true,
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Ticker price",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "ticker": {
                      "type": "string"
                    },
                    "price": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/bet/open": {
      "post": {
        "summary": "Open a paper-trading bet",
        "description": "User action. Requires explicit user authorization.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenBetRequest"
              }
            }
          }
        },
        "responses": {
          "400": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "200": {
            "description": "Bet opened",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "message": {
                      "type": "string"
                    },
                    "bet_id": {
                      "type": "integer"
                    },
                    "entry_price": {
                      "type": "number"
                    }
                  }
                }
              }
            }
          }
        }
      }
    },
    "/bet/close": {
      "post": {
        "summary": "Close an open bet",
        "description": "User action. Requires explicit user authorization.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "bet_id"
                ],
                "properties": {
                  "bet_id": {
                    "type": "integer"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Bet closed"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "Bet not found, already closed, or not owned by the caller",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Could not fetch current price",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/follow": {
      "post": {
        "summary": "Follow or unfollow another user",
        "description": "User action. Requires explicit user authorization.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "followed_id",
                  "action"
                ],
                "properties": {
                  "followed_id": {
                    "type": "integer"
                  },
                  "action": {
                    "type": "string",
                    "enum": [
                      "follow",
                      "unfollow"
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Follow state changed"
          },
          "403": {
            "$ref": "#/components/responses/Error"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/api/stripe/plans": {
      "get": {
        "summary": "List public subscription plans",
        "security": [],
        "responses": {
          "200": {
            "description": "Subscription plans"
          }
        }
      }
    },
    "/api/stripe/checkout": {
      "post": {
        "summary": "Create a Stripe checkout session",
        "description": "User billing action. Requires explicit user authorization.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            },
            "description": "Checkout URL"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "409": {
            "description": "Already subscribed",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Failed to create checkout session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "price_id"
                ],
                "properties": {
                  "price_id": {
                    "type": "string",
                    "description": "Stripe price id from GET /api/stripe/plans."
                  }
                }
              }
            }
          }
        }
      }
    },
    "/api/stripe/portal": {
      "post": {
        "summary": "Create a Stripe customer portal session",
        "description": "User billing action. Requires explicit user authorization.",
        "responses": {
          "200": {
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "url": {
                      "type": "string",
                      "format": "uri"
                    }
                  }
                }
              }
            },
            "description": "Portal URL"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          },
          "400": {
            "description": "No Stripe customer found",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          },
          "500": {
            "description": "Failed to create portal session",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Error"
                }
              }
            }
          }
        }
      }
    },
    "/telegram/status": {
      "get": {
        "summary": "Return Telegram notification link status",
        "responses": {
          "200": {
            "description": "Telegram link status",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "linked": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/telegram/link-code": {
      "post": {
        "summary": "Create a Telegram link code",
        "description": "User action. Opens a Telegram linking flow.",
        "responses": {
          "200": {
            "description": "Link code and deep link"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    },
    "/telegram/unlink": {
      "post": {
        "summary": "Disable Telegram notifications",
        "description": "User action. Requires explicit user authorization.",
        "responses": {
          "200": {
            "description": "Telegram unlinked"
          },
          "401": {
            "$ref": "#/components/responses/Error"
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "cookieAuth": {
        "type": "apiKey",
        "in": "cookie",
        "name": "auth_token",
        "description": "HttpOnly JWT session cookie set by /login."
      }
    },
    "responses": {
      "Error": {
        "description": "Error response",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            }
          }
        }
      }
    },
    "schemas": {
      "RegisterRequest": {
        "type": "object",
        "required": [
          "username",
          "email",
          "password"
        ],
        "properties": {
          "username": {
            "type": "string"
          },
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string",
            "minLength": 15
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": [
          "email",
          "password"
        ],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string"
          }
        }
      },
      "OpenBetRequest": {
        "type": "object",
        "required": [
          "ticker",
          "direction"
        ],
        "properties": {
          "ticker": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "LONG",
              "SHORT"
            ]
          },
          "target_date": {
            "type": "string",
            "format": "date",
            "description": "Optional target close date."
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "username": {
            "type": "string"
          },
          "email": {
            "type": "string"
          },
          "follow_capacity": {
            "type": "integer"
          },
          "stripe_subscription_id": {
            "type": [
              "string",
              "null"
            ]
          }
        }
      },
      "PublicUser": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "username": {
            "type": "string"
          }
        }
      },
      "LeaderboardUser": {
        "allOf": [
          {
            "$ref": "#/components/schemas/PublicUser"
          },
          {
            "type": "object",
            "properties": {
              "total_score": {
                "type": "number"
              },
              "total_trades": {
                "type": "integer"
              }
            }
          }
        ]
      },
      "PortfolioBet": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "ticker": {
            "type": "string"
          },
          "direction": {
            "type": "string",
            "enum": [
              "LONG",
              "SHORT"
            ]
          },
          "entry": {
            "type": "number"
          },
          "current": {
            "type": "number"
          },
          "unrealized_gain": {
            "type": "number"
          },
          "target_date": {
            "type": [
              "string",
              "null"
            ],
            "format": "date"
          }
        }
      },
      "ClosedBet": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer"
          },
          "ticker": {
            "type": "string"
          },
          "direction": {
            "type": "string"
          },
          "entry_price": {
            "type": "number"
          },
          "exit_price": {
            "type": "number"
          },
          "percentage_gain": {
            "type": "number"
          },
          "exit_date": {
            "type": "string",
            "format": "date-time"
          },
          "entry_date": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "FeedItem": {
        "allOf": [
          {
            "$ref": "#/components/schemas/ClosedBet"
          },
          {
            "type": "object",
            "properties": {
              "user_id": {
                "type": "integer"
              },
              "username": {
                "type": "string"
              },
              "status": {
                "type": "string"
              }
            }
          }
        ]
      },
      "Error": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          }
        }
      }
    }
  }
}
