openapi: 3.0.3
info:
  title: StockAlert.pro API
  version: 1.1.0
  contact:
    name: StockAlert.pro Support
    email: support@stockalert.pro
    url: https://stockalert.pro/imprint
  license:
    name: Proprietary
    url: https://stockalert.pro/terms
  termsOfService: https://stockalert.pro/terms
  description: >
    Unified v1 API for alerts, watchlists, stocks, webhooks and API keys.


    Authentication: API Key (`X-API-Key`), Bearer JWT (`Authorization: Bearer
    <token>`) or Session (first‑party web).
externalDocs:
  description: Complete API documentation and guides
  url: https://docs.stockalert.pro/api
servers:
  - url: https://stockalert.pro
    description: Production
tags:
  - name: Alerts
    description: Manage price and technical alerts
  - name: Watchlist
    description: Manage your watchlist items and AI analysis
  - name: Webhooks
    description: Manage outgoing webhooks for alert events
  - name: API Keys
    description: Manage API keys for programmatic access
  - name: Stocks
    description: Stocks catalog and details
  - name: Market
    description: Internal market insights endpoints
  - name: Feedback
    description: Collect and manage user feedback (internal)
  - name: User
    description: Account, session and preferences (internal)
  - name: Auth
    description: Authentication and session management
  - name: Utilities
    description: Shared utility endpoints (redirects)
  - name: Cron
    description: Internal scheduled jobs (Vercel Cron)
paths:
  /api/v1/stocks/{symbol}:
    get:
      tags:
        - Stocks
      operationId: stocksGet
      summary: Get stock detail
      description: Returns a single stock with optional fundamentals.
      x-required-scopes:
        - stocks:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
        - name: fields
          in: query
          schema:
            type: array
            items:
              type: string
          style: form
          explode: false
          description: "CSV projection: `fundamentals` (include stock_fundamentals)"
      responses:
        "200":
          description: Successful
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                oneOf:
                  - $ref: "#/components/schemas/ApiSuccessStockEnvelope"
                  - $ref: "#/components/schemas/ApiSuccessStockWithFundamentalsEnvelope"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/Validation"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/watchlist:
    get:
      tags:
        - Watchlist
      operationId: watchlistList
      summary: List watchlist items
      description: Returns current user's watchlist items.
      x-required-scopes:
        - watchlist:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWatchlistListEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      - id: 00000000-0000-4000-8000-000000000001
                        stock_symbol: AAPL
                        intention: buy
                        target_price: 200
                        initial_price: 189.2
                        auto_alerts_enabled: true
                        stocks:
                          symbol: AAPL
                          name: Apple Inc.
                          last_price: 189.2
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      tags:
        - Watchlist
      operationId: watchlistCreate
      summary: Add item to watchlist
      description: Creates a new watchlist item.
      x-required-scopes:
        - watchlist:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateWatchlistItemRequest"
      responses:
        "201":
          description: Created
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWatchlistItemEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      id: 00000000-0000-4000-8000-000000000002
                      stock_symbol: MSFT
                      intention: sell
                      initial_price: 300
                      auto_alerts_enabled: false
                      stocks:
                        symbol: MSFT
                        name: Microsoft Corp.
                        last_price: 300.1
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "409":
          $ref: "#/components/responses/Conflict"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/watchlist/{id}:
    patch:
      tags:
        - Watchlist
      operationId: watchlistUpdate
      summary: Update watchlist item
      description: Update fields of an existing watchlist item.
      x-required-scopes:
        - watchlist:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateWatchlistItemRequest"
      responses:
        "200":
          description: Updated
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWatchlistItemEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      id: 00000000-0000-4000-8000-000000000002
                      stock_symbol: MSFT
                      intention: sell
                      target_price: 305
                      initial_price: 300
                      auto_alerts_enabled: false
                      stocks:
                        symbol: MSFT
                        name: Microsoft Corp.
                        last_price: 301.5
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      tags:
        - Watchlist
      operationId: watchlistDelete
      summary: Delete watchlist item
      description: Delete an item from the watchlist.
      x-required-scopes:
        - watchlist:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWatchlistDeletedEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      deleted: true
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/watchlist/order:
    put:
      tags:
        - Watchlist
      operationId: watchlistSwapIntention
      summary: Swap intention (buy/sell) and reanalyze
      description: Resets initial price and queues analysis for intention change.
      x-required-scopes:
        - watchlist:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SwapIntentionRequest"
      responses:
        "200":
          description: Updated
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWatchlistOrderEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      item:
                        id: 00000000-0000-4000-8000-000000000003
                        stock_symbol: AAPL
                        intention: buy
                        initial_price: 189.2
                        auto_alerts_enabled: true
                        stocks:
                          symbol: AAPL
                          name: Apple Inc.
                          last_price: 190
                      deleted_alerts: 2
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/watchlist/paper-trades:
    get:
      tags:
        - Watchlist
      operationId: watchlistPaperTrades
      summary: List watchlist paper trades
      description: Returns simulated paper trades for the current user.
      x-required-scopes:
        - watchlist:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWatchlistPaperTradesEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      - id: 00000000-0000-4000-8000-000000000010
                        user_id: 00000000-0000-4000-8000-000000000001
                        watchlist_id: 00000000-0000-4000-8000-000000000002
                        watchlist_item_id: 00000000-0000-4000-8000-000000000003
                        symbol: NVDA
                        direction: long
                        notional_usd: 1000
                        quantity: 2.5
                        entry_price: 400
                        entry_at: 2024-10-01T14:35:00Z
                        status: open
                        exit_price: null
                        exit_at: null
                        realized_pnl_usd: null
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/watchlist/performance:
    get:
      tags:
        - Watchlist
      operationId: watchlistPerformance
      summary: Watchlist paper portfolio performance vs. S&P 500
      description: >
        Returns the simulated paper portfolio performance for the current user,
        compared against an S&P 500 benchmark, normalized to 100 at the first
        available day in the selected range.
      x-required-scopes:
        - watchlist:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - in: query
          name: range
          schema:
            type: string
            enum:
              - 3m
              - 6m
              - 1y
              - max
          required: false
          description: >
            Time range for the performance series. Defaults to `max` (from first
            paper trade until today).
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWatchlistPerformanceEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      range: 1y
                      base_date: 2024-01-01
                      series:
                        watchlist:
                          - date: 2024-01-01
                            value: 100
                          - date: 2024-02-01
                            value: 105.3
                          - date: 2024-03-01
                            value: 98.7
                        sp_500:
                          - date: 2024-01-01
                            value: 100
                          - date: 2024-02-01
                            value: 101.2
                          - date: 2024-03-01
                            value: 103
                      summary:
                        watchlist_return_pct: 5.3
                        sp500_return_pct: 3
                        alpha_pct: 2.3
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/alerts:
    get:
      tags:
        - Alerts
      operationId: alertsList
      summary: List alerts
      description: Get a paginated list of alerts.
      x-required-scopes:
        - alerts:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/LimitAlertsList"
        - $ref: "#/components/parameters/Status"
        - $ref: "#/components/parameters/ConditionFilter"
        - $ref: "#/components/parameters/Search"
        - $ref: "#/components/parameters/SortField"
        - $ref: "#/components/parameters/SortDirection"
        - $ref: "#/components/parameters/Minimal"
        - $ref: "#/components/parameters/Extended"
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertsListEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      - id: a5d7e3f0-1234-4d8a-9c2b-111111111111
                        symbol: AAPL
                        condition: price_above
                        threshold: 200
                        notification: email
                        status: active
                        created_at: 2025-01-01T12:00:00.000Z
                        initial_price: 189.2
                        parameters: null
                    meta:
                      pagination:
                        page: 1
                        limit: 50
                        total: 1
                        total_pages: 1
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      tags:
        - Alerts
      operationId: alertsCreate
      summary: Create alert
      description: >
        Create a new alert.

        - Guest (no auth): allowed only when an `email` is supplied. The alert
        is created as `inactive` and must be verified via `POST
        /api/v1/alerts/verify`.

        - Basic accounts can only create Price and Time alerts. Technical,
        Volume, Fundamental, and Dividend alerts require Premium or Early Bird.
      x-required-scopes:
        - alerts:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CreateAlertRequest"
      responses:
        "200":
          description: Existing alert returned (idempotency)
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      id: 2f8d0f7b-2222-4c3a-b1c3-222222222222
                      symbol: AAPL
                      condition: price_above
                      threshold: 200
                      notification: email
                      status: active
                      created_at: 2025-01-01T12:00:00.000Z
                      initial_price: 189.2
                      parameters:
                        onboarding_id: dup-1
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "201":
          description: Created
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      id: 2f8d0f7b-2222-4c3a-b1c3-222222222222
                      symbol: AAPL
                      condition: price_above
                      threshold: 200
                      notification: email
                      status: active
                      created_at: 2025-01-01T12:00:00.000Z
                      initial_price: 189.2
                      parameters:
                        onboarding_id: dup-1
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
                daily_reminder_after_close:
                  value:
                    success: true
                    data:
                      id: 7c2d9eab-3333-4a7b-9c3b-333333333333
                      symbol: MSFT
                      condition: daily_reminder
                      threshold: null
                      notification: email
                      status: active
                      created_at: 2025-01-02T12:00:00.000Z
                      initial_price: 370.5
                      parameters:
                        delivery_time: after_market_close
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/Validation"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/alerts/{id}:
    get:
      tags:
        - Alerts
      operationId: alertsGet
      summary: Get alert
      description: Get alert by id.
      x-required-scopes:
        - alerts:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Successful
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      id: 2f8d0f7b-2222-4c3a-b1c3-222222222222
                      symbol: AAPL
                      condition: price_above
                      threshold: 200
                      notification: email
                      status: active
                      created_at: 2025-01-01T12:00:00.000Z
                      triggered_at: null
                      initial_price: 189.2
                      parameters: null
                      stock:
                        name: Apple Inc.
                        last_price: 190.1
                        high_52w: 210
                        low_52w: 120
                        rsi: 52
                        ma_50: 185
                        ma_200: 170
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/Validation"
        "429":
          $ref: "#/components/responses/RateLimited"
    put:
      tags:
        - Alerts
      operationId: alertsUpdate
      summary: Update alert
      description: Full update of an alert.
      x-required-scopes:
        - alerts:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/UpdateAlertRequest"
      responses:
        "200":
          description: Updated
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertEnvelope"
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/Validation"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      tags:
        - Alerts
      operationId: alertsDelete
      summary: Delete alert
      description: Delete an alert by id.
      x-required-scopes:
        - alerts:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Deleted
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertDeletedEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      alert_id: 2f8d0f7b-2222-4c3a-b1c3-222222222222
                      status: deleted
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/Validation"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/alerts/{id}/pause:
    post:
      tags:
        - Alerts
      operationId: alertsPause
      summary: Pause alert
      description: Pause an active alert.
      x-required-scopes:
        - alerts:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Paused
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertPausedEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      alert_id: 2f8d0f7b-2222-4c3a-b1c3-222222222222
                      status: paused
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/Validation"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/alerts/{id}/activate:
    post:
      tags:
        - Alerts
      operationId: alertsActivate
      summary: Activate alert
      description: Reactivate a paused or inactive alert.
      x-required-scopes:
        - alerts:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
      responses:
        "200":
          description: Activated
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertActivatedEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      alert_id: 2f8d0f7b-2222-4c3a-b1c3-222222222222
                      status: active
                      initial_price: 189.2
                    meta:
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "422":
          $ref: "#/components/responses/Validation"
        "429":
          $ref: "#/components/responses/RateLimited"
        "503":
          $ref: "#/components/responses/ServiceUnavailable"
  /api/v1/alerts/{id}/history:
    get:
      tags:
        - Alerts
      operationId: alertsHistory
      summary: Alert history
      description: Get history of an alert.
      x-required-scopes:
        - alerts:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
            format: uuid
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/LimitHistory"
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessAlertHistoryEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      - alert_id: 2f8d0f7b-2222-4c3a-b1c3-222222222222
                        symbol: AAPL
                        action_type: triggered
                        action_timestamp: 2025-01-01T12:34:56.000Z
                        previous_status: active
                        new_status: triggered
                        trigger_price: 205.5
                    meta:
                      pagination:
                        page: 1
                        limit: 50
                        total: 1
                        total_pages: 1
                      rate_limit:
                        limit: 200
                        remaining: 199
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
  /api/v1/webhooks:
    get:
      tags:
        - Webhooks
      operationId: webhooksList
      summary: List webhooks
      description: Returns configured webhooks.
      x-required-scopes:
        - webhooks:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - SessionCookieAuth: []
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWebhooksListEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      - id: 00000000-0000-4000-8000-000000000005
                        user_id: 00000000-0000-4000-8000-0000000000aa
                        url: https://example.com/webhook
                        events:
                          - alert.triggered
                        is_active: true
                        created_at: 2025-01-01T12:00:00.000Z
                    meta:
                      rate_limit:
                        limit: 1000
                        remaining: 999
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
    post:
      summary: Create webhook
      tags:
        - Webhooks
      operationId: webhooksCreate
      description: >
        Create a new webhook.


        Deliveries sent to your endpoint include signed headers for
        verification:

        - `X-StockAlert-Event`: the event type (e.g. `alert.triggered`,
        `alert.paused`, `alert.deleted`)

        - `X-StockAlert-Timestamp`: Unix epoch milliseconds when the event was
        generated

        - `X-StockAlert-Signature`: HMAC SHA-256 signature in the format
        `sha256=<hex>` computed over `${timestamp}.${rawBody}` using your
        webhook secret


        The JSON body of deliveries follows the `WebhookEvent` schema.
      x-required-scopes:
        - webhooks:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - SessionCookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
                - events
              properties:
                url:
                  type: string
                  format: uri
                events:
                  type: array
                  description: "Allowed values: alert.triggered"
                  items:
                    type: string
                    enum:
                      - alert.triggered
                secret:
                  type: string
      responses:
        "201":
          description: Created
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWebhookCreatedEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      id: 00000000-0000-4000-8000-000000000006
                      url: https://example.com/webhook
                      events:
                        - alert.triggered
                      secret: redacted
                      is_active: true
                      created_at: 2025-01-01T12:00:00.000Z
                    meta:
                      rate_limit:
                        limit: 1000
                        remaining: 999
                        reset: 1736180400000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/webhooks/{id}:
    get:
      summary: Get webhook
      tags:
        - Webhooks
      operationId: webhooksGet
      description: Get webhook by id.
      x-required-scopes:
        - webhooks:read
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - SessionCookieAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWebhookEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      id: 00000000-0000-4000-8000-000000000005
                      user_id: 00000000-0000-4000-8000-0000000000aa
                      url: https://example.com/webhook
                      events:
                        - alert.triggered
                      is_active: true
                      created_at: 2025-01-01T12:00:00.000Z
                    meta:
                      rate_limit:
                        limit: 1000
                        remaining: 999
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
    delete:
      summary: Delete webhook
      tags:
        - Webhooks
      operationId: webhooksDelete
      description: Delete a webhook by id.
      x-required-scopes:
        - webhooks:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - SessionCookieAuth: []
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Deleted
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWebhookDeletedEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      id: 00000000-0000-4000-8000-000000000005
                    meta:
                      rate_limit:
                        limit: 1000
                        remaining: 999
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          $ref: "#/components/responses/Forbidden"
        "404":
          $ref: "#/components/responses/NotFound"
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/webhooks/test:
    post:
      summary: Send test webhook
      tags:
        - Webhooks
      operationId: webhooksTest
      description: >
        Sends a test event to your webhook URL.


        The outgoing request includes the following headers on your endpoint:

        - `X-StockAlert-Event`

        - `X-StockAlert-Timestamp`

        - `X-StockAlert-Signature` (HMAC SHA-256, `sha256=<hex>`) using your
        webhook secret


        The payload conforms to `WebhookEvent`.
      x-required-scopes:
        - webhooks:write
      security:
        - ApiKeyAuth: []
        - BearerAuth: []
        - SessionCookieAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required:
                - url
                - secret
              properties:
                url:
                  type: string
                  format: uri
                secret:
                  type: string
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessWebhookTestEnvelope"
              examples:
                example:
                  value:
                    success: true
                    data:
                      status: 200
                      status_text: OK
                      response: ok
                    meta:
                      rate_limit:
                        limit: 5
                        remaining: 4
                        reset: 1736180400000
        "400":
          $ref: "#/components/responses/BadRequest"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "403":
          description: Forbidden (missing scope)
        "429":
          $ref: "#/components/responses/RateLimited"
  /api/v1/user/subscription:
    get:
      tags:
        - User
      operationId: userSubscription
      summary: Get subscription, quotas and usage
      description: Returns the user’s subscription status, quotas, usage, and counts
        for alerts/watchlist.
      x-required-scopes:
        - user:read
      security:
        - BearerAuth: []
        - ApiKeyAuth: []
      responses:
        "200":
          description: Successful response
          headers:
            X-RateLimit-Limit:
              $ref: "#/components/headers/X-RateLimit-Limit"
            X-RateLimit-Remaining:
              $ref: "#/components/headers/X-RateLimit-Remaining"
            X-RateLimit-Reset:
              $ref: "#/components/headers/X-RateLimit-Reset"
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/ApiSuccessUserSubscriptionEnvelope"
              examples:
                premium:
                  summary: Premium user example
                  value:
                    success: true
                    data:
                      id: sub_123
                      account_type: premium
                      status: active
                      is_early_bird: false
                      is_early_bird_eligible: true
                      is_premium: true
                      cancel_at_period_end: false
                      quotas:
                        sms: 50
                      usage:
                        count: 12
                      current_period:
                        start: 2025-01-01T00:00:00.000Z
                        end: 2025-02-01T00:00:00.000Z
                      alerts:
                        counts:
                          total: 7
                          by_status:
                            active: 7
                            paused: 0
                            triggered: 0
                            inactive: 0
                        quota:
                          limit: null
                          remaining: null
                          unlimited: true
                      watchlist_items_count: 9
                      watchlist_quota: 100
                    meta:
                      rate_limit:
                        limit: 30
                        remaining: 29
                        reset: 1736180400000
                basic:
                  summary: Basic user example
                  value:
                    success: true
                    data:
                      account_type: basic
                      status: active
                      is_early_bird: false
                      is_early_bird_eligible: true
                      is_premium: false
                      cancel_at_period_end: null
                      quotas:
                        sms: 0
                      usage:
                        count: 0
                      current_period:
                        start: null
                        end: null
                      alerts:
                        counts:
                          total: 2
                          by_status:
                            active: 2
                            paused: 0
                            triggered: 0
                            inactive: 0
                        quota:
                          limit: 5
                          remaining: 3
                          unlimited: false
                      watchlist_items_count: 2
                      watchlist_quota: 2
                    meta:
                      rate_limit:
                        limit: 30
                        remaining: 29
                        reset: 1736180400000
        "401":
          $ref: "#/components/responses/Unauthorized"
        "429":
          $ref: "#/components/responses/RateLimited"
        "500":
          $ref: "#/components/responses/InternalError"
components:
  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
    SessionCookieAuth:
      type: apiKey
      in: cookie
      name: sb-access-token
      description: First-party browser session cookie authentication (dashboard/web app).
    CronSecret:
      type: apiKey
      in: header
      name: Authorization
      description: Bearer token matching CRON_SECRET environment variable (set by
        Vercel for cron jobs)
  headers:
    X-RateLimit-Limit:
      description: Requests allowed in the current window
      schema:
        type: integer
    X-RateLimit-Remaining:
      description: Requests remaining in the current window
      schema:
        type: integer
    X-RateLimit-Reset:
      description: Reset time as Unix epoch milliseconds
      schema:
        type: integer
        format: int64
  parameters:
    Page:
      name: page
      in: query
      schema:
        type: integer
        default: 1
        minimum: 1
      description: Page number (1-based)
    LimitAlertsList:
      name: limit
      in: query
      schema:
        type: integer
        default: 50
        maximum: 100
        minimum: 1
      description: Items per page (max 100)
    LimitHistory:
      name: limit
      in: query
      schema:
        type: integer
        default: 50
        maximum: 200
        minimum: 1
      description: History items per page (max 200)
    Status:
      name: status
      in: query
      schema:
        $ref: "#/components/schemas/AlertStatus"
    ConditionFilter:
      name: condition
      in: query
      schema:
        $ref: "#/components/schemas/AlertCondition"
    Search:
      name: search
      in: query
      schema:
        type: string
    SortField:
      name: sort_field
      in: query
      schema:
        type: string
        default: created_at
    SortDirection:
      name: sort_direction
      in: query
      schema:
        type: string
        enum:
          - asc
          - desc
        default: desc
    Minimal:
      name: minimal
      in: query
      schema:
        type: boolean
      description: If true, reduces returned fields
    Extended:
      name: extended
      in: query
      schema:
        type: boolean
      description: If true, returns extended fields when available
  responses:
    Unauthorized:
      description: Unauthorized
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
    Forbidden:
      description: Forbidden (missing scope)
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
    Validation:
      description: Validation error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
    RateLimited:
      description: Rate limit exceeded
      headers:
        X-RateLimit-Limit:
          $ref: "#/components/headers/X-RateLimit-Limit"
        X-RateLimit-Remaining:
          $ref: "#/components/headers/X-RateLimit-Remaining"
        X-RateLimit-Reset:
          $ref: "#/components/headers/X-RateLimit-Reset"
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
    NotFound:
      description: Not found
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
    BadRequest:
      description: Bad request
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
    Conflict:
      description: Conflict
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
    ServiceUnavailable:
      description: Upstream service unavailable
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
    InternalError:
      description: Internal server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/ApiErrorEnvelope"
  schemas:
    MetaPagination:
      type: object
      properties:
        page:
          type: integer
        limit:
          type: integer
        total:
          type: integer
        total_pages:
          type: integer
    MetaRateLimit:
      type: object
      properties:
        limit:
          type: integer
          description: Requests allowed in the current window
        remaining:
          type: integer
          description: Requests remaining in the current window
        reset:
          type: integer
          format: int64
          description: Reset time (epoch ms)
    ApiError:
      type: object
      properties:
        code:
          type: string
        message:
          type: string
        details:
          type: object
          nullable: true
    ApiErrorEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          $ref: "#/components/schemas/ApiError"
    StockSummary:
      type: object
      properties:
        symbol:
          type: string
        name:
          type: string
          nullable: true
        last_price:
          type: number
          nullable: true
        previous_close:
          type: number
          nullable: true
        ma_50:
          type: number
          nullable: true
        ma_200:
          type: number
          nullable: true
        rsi:
          type: number
          nullable: true
        type:
          type: string
          nullable: true
        is_active:
          type: boolean
    StockDetails:
      type: object
      properties:
        country:
          type: string
          nullable: true
        sector:
          type: string
          nullable: true
        industry:
          type: string
          nullable: true
        exchange:
          type: string
          nullable: true
        isin:
          type: string
          nullable: true
        alt_symbols:
          type: object
          nullable: true
    Stock:
      allOf:
        - $ref: "#/components/schemas/StockSummary"
        - type: object
          properties:
            high_52w:
              type: number
              nullable: true
            low_52w:
              type: number
              nullable: true
            volume:
              type: number
              nullable: true
            volume_ma_20:
              type: number
              nullable: true
            volume_ma_100:
              type: number
              nullable: true
            details:
              $ref: "#/components/schemas/StockDetails"
    ApiSuccessStockEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          $ref: "#/components/schemas/Stock"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessStockWithFundamentalsEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          allOf:
            - $ref: "#/components/schemas/Stock"
            - type: object
              properties:
                fundamentals:
                  type: object
                  additionalProperties: true
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    WatchlistItem:
      type: object
      properties:
        id:
          type: string
          format: uuid
        stock_symbol:
          type: string
        intention:
          type: string
          enum:
            - buy
            - sell
        target_price:
          type: number
          nullable: true
        notes:
          type: string
          nullable: true
        initial_price:
          type: number
          nullable: true
        auto_alerts_enabled:
          type: boolean
    StockBrief:
      type: object
      properties:
        symbol:
          type: string
        name:
          type: string
        last_price:
          type: number
          nullable: true
    WatchlistItemWithStock:
      allOf:
        - $ref: "#/components/schemas/WatchlistItem"
        - type: object
          properties:
            stocks:
              type: object
              allOf:
                - $ref: "#/components/schemas/StockBrief"
              nullable: true
            active_alert_count:
              type: integer
              nullable: true
            paused_alert_count:
              type: integer
              nullable: true
    CreateWatchlistItemRequest:
      type: object
      required:
        - stock_symbol
        - intention
      properties:
        stock_symbol:
          type: string
        stock_name:
          type: string
        intention:
          type: string
          enum:
            - buy
            - sell
        target_price:
          type: number
        notes:
          type: string
    UpdateWatchlistItemRequest:
      type: object
      properties:
        target_price:
          type: number
          nullable: true
        notes:
          type: string
          nullable: true
        is_active:
          type: boolean
        auto_alerts_enabled:
          type: boolean
    SwapIntentionRequest:
      type: object
      required:
        - item_id
        - new_intention
        - stock_symbol
      properties:
        item_id:
          type: string
        new_intention:
          type: string
          enum:
            - buy
            - sell
        stock_symbol:
          type: string
    ApiSuccessWatchlistListEnvelope:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: array
          items:
            $ref: "#/components/schemas/WatchlistItemWithStock"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessWatchlistItemEnvelope:
      type: object
      properties:
        success:
          type: boolean
        data:
          $ref: "#/components/schemas/WatchlistItemWithStock"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessWatchlistDeletedEnvelope:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            deleted:
              type: boolean
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessWatchlistOrderEnvelope:
      type: object
      properties:
        success:
          type: boolean
        data:
          type: object
          properties:
            item:
              $ref: "#/components/schemas/WatchlistItemWithStock"
            deleted_alerts:
              type: integer
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    WatchlistPaperTrade:
      type: object
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        watchlist_id:
          type: string
          format: uuid
        watchlist_item_id:
          type: string
          format: uuid
        symbol:
          type: string
        direction:
          type: string
          enum:
            - long
            - short
        notional_usd:
          type: number
        quantity:
          type: number
        entry_price:
          type: number
        entry_at:
          type: string
          format: date-time
        status:
          type: string
          enum:
            - open
            - closed
        exit_price:
          type: number
          nullable: true
        exit_at:
          type: string
          format: date-time
          nullable: true
        realized_pnl_usd:
          type: number
          nullable: true
    ApiSuccessWatchlistPaperTradesEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: array
          items:
            $ref: "#/components/schemas/WatchlistPaperTrade"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    WatchlistPerformancePoint:
      type: object
      properties:
        date:
          type: string
          format: date
        value:
          type: number
          description: Normalized index value (base 100 at first day)
    WatchlistPerformancePayload:
      type: object
      properties:
        range:
          type: string
          enum:
            - 3m
            - 6m
            - 1y
            - max
        base_date:
          type: string
          format: date
          nullable: true
          description: First date in the series used as normalization base.
        series:
          type: object
          properties:
            watchlist:
              type: array
              items:
                $ref: "#/components/schemas/WatchlistPerformancePoint"
            sp_500:
              type: array
              items:
                $ref: "#/components/schemas/WatchlistPerformancePoint"
        summary:
          type: object
          properties:
            watchlist_return_pct:
              type: number
              description: Total percentage return of the simulated watchlist paper portfolio
                over the range.
            sp_500_return_pct:
              type: number
              description: Total percentage return of the S&P 500 benchmark over the range.
            alpha_pct:
              type: number
              description: Difference between watchlist_return_pct and sp500_return_pct.
    ApiSuccessWatchlistPerformanceEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          $ref: "#/components/schemas/WatchlistPerformancePayload"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    AlertCondition:
      type: string
      enum:
        - price_above
        - price_below
        - price_change_up
        - price_change_down
        - new_high
        - new_low
        - reminder
        - daily_reminder
        - ma_crossover_golden
        - ma_crossover_death
        - ma_touch_above
        - ma_touch_below
        - volume_change
        - rsi_limit
        - pe_ratio_below
        - pe_ratio_above
        - forward_pe_below
        - forward_pe_above
        - earnings_announcement
        - dividend_ex_date
        - dividend_payment
        - insider_transactions
    AlertStatus:
      type: string
      enum:
        - active
        - paused
        - triggered
        - inactive
    CreateAlertRequest:
      type: object
      required:
        - symbol
        - condition
      properties:
        symbol:
          type: string
        condition:
          $ref: "#/components/schemas/AlertCondition"
        email:
          type: string
          format: email
          description: Optional for guest creation
        threshold:
          type: number
          nullable: true
        notification:
          type: string
          enum:
            - email
            - sms
          default: email
        parameters:
          type: object
          additionalProperties: true
          description: >
            Optional metadata object. For mobile onboarding idempotency, include
            `onboarding_id` (string).

            For `daily_reminder` alerts, use `deliveryTime`: `market_open`
            (default) or `after_market_close`.
    Alert:
      type: object
      properties:
        id:
          type: string
          format: uuid
        symbol:
          type: string
        condition:
          $ref: "#/components/schemas/AlertCondition"
        threshold:
          type: number
          nullable: true
        notification:
          type: string
          enum:
            - email
            - sms
        status:
          $ref: "#/components/schemas/AlertStatus"
        created_at:
          type: string
          format: date-time
        triggered_at:
          type: string
          format: date-time
          nullable: true
        initial_price:
          type: number
        parameters:
          type: object
          nullable: true
          additionalProperties: true
        stock:
          type: object
          nullable: true
          properties:
            name:
              type: string
            last_price:
              type: number
    UpdateAlertRequest:
      type: object
      description: Partial update of an alert.
      properties:
        condition:
          $ref: "#/components/schemas/AlertCondition"
        threshold:
          type: number
          nullable: true
        notification:
          type: string
          enum:
            - email
            - sms
        parameters:
          type: object
          nullable: true
          additionalProperties: true
    AlertHistory:
      type: object
      properties:
        alert_id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
          nullable: true
        email:
          type: string
          nullable: true
        symbol:
          type: string
        action_type:
          type: string
          enum:
            - created
            - deleted
            - triggered
            - paused
            - reactivated
            - verified
            - notification_sent
            - notification_failed
        action_timestamp:
          type: string
          format: date-time
        previous_status:
          type: string
          allOf:
            - $ref: "#/components/schemas/AlertStatus"
          nullable: true
        new_status:
          type: string
          oneOf:
            - $ref: "#/components/schemas/AlertStatus"
            - type: string
              enum:
                - deleted
          nullable: true
        alert_data:
          type: object
          additionalProperties: true
        trigger_price:
          type: number
          nullable: true
        notification_status:
          type: string
          nullable: true
    ApiSuccessAlertEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          $ref: "#/components/schemas/Alert"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessAlertsListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: array
          items:
            $ref: "#/components/schemas/Alert"
        meta:
          type: object
          properties:
            pagination:
              $ref: "#/components/schemas/MetaPagination"
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessAlertDeletedEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            alert_id:
              type: string
              format: uuid
            status:
              type: string
              example: deleted
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessAlertHistoryEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: array
          items:
            $ref: "#/components/schemas/AlertHistory"
        meta:
          type: object
          properties:
            pagination:
              $ref: "#/components/schemas/MetaPagination"
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessAlertPausedEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            alert_id:
              type: string
              format: uuid
            status:
              type: string
              enum:
                - paused
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessAlertActivatedEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            alert_id:
              type: string
              format: uuid
            status:
              type: string
              enum:
                - active
            initial_price:
              type: number
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    Webhook:
      type: object
      properties:
        id:
          type: string
          format: uuid
        user_id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum:
              - alert.triggered
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
    WebhookCreated:
      type: object
      properties:
        id:
          type: string
          format: uuid
        url:
          type: string
          format: uri
        events:
          type: array
          items:
            type: string
            enum:
              - alert.triggered
        secret:
          type: string
        is_active:
          type: boolean
        created_at:
          type: string
          format: date-time
    ApiSuccessWebhookEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          $ref: "#/components/schemas/Webhook"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessWebhookCreatedEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          $ref: "#/components/schemas/WebhookCreated"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessWebhooksListEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: array
          items:
            $ref: "#/components/schemas/Webhook"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessWebhookDeletedEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            id:
              type: string
              format: uuid
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    ApiSuccessWebhookTestEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          type: object
          properties:
            status:
              type: integer
              example: 200
            status_text:
              type: string
              example: OK
            response:
              type: string
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
    UserSubscription:
      type: object
      properties:
        id:
          type: string
          nullable: true
        account_type:
          type: string
          enum:
            - basic
            - premium
            - early_bird
        status:
          type: string
        is_early_bird:
          type: boolean
        is_early_bird_eligible:
          type: boolean
        is_premium:
          type: boolean
        cancel_at_period_end:
          type: boolean
          nullable: true
        quotas:
          type: object
          properties:
            sms:
              type: integer
        usage:
          type: object
          properties:
            count:
              type: integer
        current_period:
          type: object
          properties:
            start:
              type: string
              format: date-time
              nullable: true
            end:
              type: string
              format: date-time
              nullable: true
        alerts:
          type: object
          description: Aggregated alert information for the current user
          properties:
            counts:
              type: object
              properties:
                total:
                  type: integer
                  description: Total alerts across all statuses
                by_status:
                  type: object
                  additionalProperties:
                    type: integer
                  description: Alert counts grouped by status (e.g., active, paused, triggered,
                    inactive)
            quota:
              type: object
              properties:
                limit:
                  type: integer
                  nullable: true
                  description: Maximum active alerts allowed; null for unlimited
                remaining:
                  type: integer
                  nullable: true
                  description: Remaining active alerts quota; null when unlimited
                unlimited:
                  type: boolean
                  description: Whether the alert quota is unlimited
        watchlist_items_count:
          type: integer
        watchlist_quota:
          type: integer
    ApiSuccessUserSubscriptionEnvelope:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          $ref: "#/components/schemas/UserSubscription"
        meta:
          type: object
          properties:
            rate_limit:
              $ref: "#/components/schemas/MetaRateLimit"
