openapi: 3.0.3
info:
  title: Sentinel Aleph V2 API
  description: Smart Market Intelligence Platform API
  version: 2.0.0
  contact:
    name: Sentinel Aleph Team

servers:
  - url: http://localhost:8080
    description: Development
  - url: https://api.sentinel-aleph.com
    description: Production

tags:
  - name: Auth
    description: Authentication & session management
  - name: Signals
    description: Signal detection and management
  - name: Scanner
    description: Market scanner operations
  - name: Strategies
    description: User strategy CRUD
  - name: Billing
    description: Stripe payment integration
  - name: Preferences
    description: User preferences
  - name: Admin
    description: Admin panel operations
  - name: Market
    description: Market data endpoints
  - name: ML
    description: Machine learning metrics
  - name: Stream
    description: Real-time SSE streaming
  - name: Health
    description: Health & readiness probes

paths:
  /api/v1/auth/register:
    post:
      tags: [Auth]
      summary: Register new user
      description: Creates a new user account and sends verification email.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password, terms_accepted, privacy_accepted, risk_disclaimer_accepted, terms_version, privacy_version, risk_disclaimer_version]
              properties:
                email: { type: string, format: email }
                password: { type: string, minLength: 8 }
                display_name: { type: string }
                terms_accepted: { type: boolean, enum: [true] }
                privacy_accepted: { type: boolean, enum: [true] }
                risk_disclaimer_accepted: { type: boolean, enum: [true] }
                terms_version: { type: string, example: "2026-06-19" }
                privacy_version: { type: string, example: "2026-06-19" }
                risk_disclaimer_version: { type: string, example: "2026-06-19" }
      responses:
        '201':
          description: Registration successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  user_id: { type: string }
                  email: { type: string }
                  message: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '409':
          description: Email already registered
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/v1/auth/login:
    post:
      tags: [Auth]
      summary: Login
      description: Authenticates user and returns access + refresh tokens.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email, password]
              properties:
                email: { type: string, format: email }
                password: { type: string }
      responses:
        '200':
          description: Login successful
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AuthTokenResponse' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403':
          description: Account suspended
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/v1/auth/refresh:
    post:
      tags: [Auth]
      summary: Refresh tokens
      description: Rotates refresh token and issues new access token (15min expiry).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [refresh_token]
              properties:
                refresh_token: { type: string }
      responses:
        '200':
          description: Tokens refreshed
          content:
            application/json:
              schema: { $ref: '#/components/schemas/AuthTokenResponse' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/auth/forgot-password:
    post:
      tags: [Auth]
      summary: Request password reset
      description: Sends reset email if account exists. Always returns 200.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
      responses:
        '200':
          description: Reset email sent (if account exists)
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }

  /api/v1/auth/reset-password:
    post:
      tags: [Auth]
      summary: Reset password
      description: Resets password using the token from the reset email.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [token, password]
              properties:
                token: { type: string }
                password: { type: string, minLength: 8 }
      responses:
        '200':
          description: Password reset successful
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }

  /api/v1/auth/verify-email:
    get:
      tags: [Auth]
      summary: Verify email address
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Email verified
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }

  /api/v1/auth/dev-token:
    post:
      tags: [Auth]
      summary: Get dev token (dev mode only)
      description: Issues a JWT for development/testing. Only available when ENV=development.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [email]
              properties:
                email: { type: string, format: email }
                tier: { type: string, enum: [free, gold, platinum, aleph], default: free }
      responses:
        '200':
          description: Dev token issued
          content:
            application/json:
              schema:
                type: object
                properties:
                  token: { type: string }
        '403':
          description: Endpoint disabled in production
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/v1/auth/session:
    delete:
      tags: [Auth]
      summary: Logout current session
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: Session revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/auth/sessions:
    delete:
      tags: [Auth]
      summary: Logout all sessions
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: All sessions revoked
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/signals:
    get:
      tags: [Signals]
      summary: List signals
      description: Returns paginated signals. Limit enforced by tier.
      security: [{ BearerAuth: [] }]
      parameters:
        - name: limit
          in: query
          schema: { type: integer, default: 50 }
      responses:
        '200':
          description: Signal list
          content:
            application/json:
              schema:
                type: object
                properties:
                  signals: { type: array, items: { $ref: '#/components/schemas/Signal' } }
                  total: { type: integer }
                  data: { type: array, items: { $ref: '#/components/schemas/Signal' } }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '429': { $ref: '#/components/responses/RateLimited' }

  /api/v1/signals/{id}:
    get:
      tags: [Signals]
      summary: Get signal by ID
      security: [{ BearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Signal detail
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/Signal' }
        '404': { $ref: '#/components/responses/NotFound' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/signals/{id}/outcome:
    post:
      tags: [Signals]
      summary: Record signal outcome
      description: Records exit price and calculates P&L for a signal.
      security: [{ BearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [exit_price, exit_type]
              properties:
                exit_price: { type: number, minimum: 0 }
                exit_type: { type: string, enum: [tp_hit, sl_hit, manual_close, expired] }
      responses:
        '200':
          description: Outcome recorded
          content:
            application/json:
              schema:
                type: object
                properties:
                  pnl_percent: { type: number }
                  rr_achieved: { type: number }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }

  /api/v1/signals/history:
    get:
      tags: [Signals]
      summary: Get signal history with outcomes
      security: [{ BearerAuth: [] }]
      parameters:
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: page_size
          in: query
          schema: { type: integer, default: 20, maximum: 100 }
        - name: mode
          in: query
          schema: { type: string }
        - name: symbol
          in: query
          schema: { type: string }
        - name: direction
          in: query
          schema: { type: string, enum: [long, short] }
        - name: exit_type
          in: query
          schema: { type: string, enum: [tp_hit, sl_hit, manual_close, expired] }
        - name: date_from
          in: query
          schema: { type: string, format: date }
        - name: date_to
          in: query
          schema: { type: string, format: date }
      responses:
        '200':
          description: History page
          content:
            application/json:
              schema:
                type: object
                properties:
                  items: { type: array, items: { type: object } }
                  total: { type: integer }
                  page: { type: integer }
                  page_size: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/signals/stats:
    get:
      tags: [Signals]
      summary: Get P&L statistics
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: Aggregated P&L stats for authenticated user
          content:
            application/json:
              schema:
                type: object
                properties:
                  total_trades: { type: integer }
                  win_rate: { type: number }
                  total_pnl: { type: number }
                  avg_rr: { type: number }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/scanner/signals:
    get:
      tags: [Scanner]
      summary: Get filtered scanner signals
      security: [{ BearerAuth: [] }]
      parameters:
        - name: mode
          in: query
          schema: { type: string, enum: [smc_only, ind_only, hybrid] }
        - name: engines
          in: query
          description: Comma-separated engine names
          schema: { type: string }
        - name: direction
          in: query
          schema: { type: string, enum: [long, short] }
        - name: min_confidence
          in: query
          schema: { type: number }
        - name: symbol
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 20 }
        - name: sort
          in: query
          schema: { type: string }
      responses:
        '200':
          description: Filtered signals
          content:
            application/json:
              schema:
                type: object
                properties:
                  signals: { type: array, items: { $ref: '#/components/schemas/Signal' } }
                  total: { type: integer }
                  filtered: { type: integer }
                  tier_limit: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /api/v1/scanner/status:
    get:
      tags: [Scanner]
      summary: Get scanner status
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: Scanner status with statistics
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { $ref: '#/components/schemas/ScannerStats' }
                  engines: { type: array, items: { $ref: '#/components/schemas/Engine' } }
                  mode: { type: string }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/scanner/engines:
    get:
      tags: [Scanner]
      summary: List scanner engines
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: Static engine list
          content:
            application/json:
              schema:
                type: object
                properties:
                  engines: { type: array, items: { $ref: '#/components/schemas/Engine' } }
                  mode: { type: string }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/strategies:
    get:
      tags: [Strategies]
      summary: List user strategies
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: Strategy list
          content:
            application/json:
              schema:
                type: object
                properties:
                  strategies: { type: array, items: { $ref: '#/components/schemas/Strategy' } }
                  count: { type: integer }
                  limit: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }
    post:
      tags: [Strategies]
      summary: Create strategy
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/StrategyInput' }
      responses:
        '201':
          description: Strategy created
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Strategy' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '403':
          description: Strategy limit reached for tier
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/v1/strategies/{id}:
    get:
      tags: [Strategies]
      summary: Get strategy by ID
      security: [{ BearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '200':
          description: Strategy detail
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Strategy' }
        '404': { $ref: '#/components/responses/NotFound' }
    put:
      tags: [Strategies]
      summary: Update strategy
      description: Supports partial updates — only provided fields are updated.
      security: [{ BearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/StrategyInput' }
      responses:
        '200':
          description: Strategy updated
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Strategy' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '404': { $ref: '#/components/responses/NotFound' }
    delete:
      tags: [Strategies]
      summary: Delete strategy
      security: [{ BearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
      responses:
        '204': { description: Strategy deleted }
        '404': { $ref: '#/components/responses/NotFound' }

  /api/v1/billing/checkout:
    post:
      tags: [Billing]
      summary: Create checkout session
      description: Creates a Stripe Checkout session for tier upgrade.
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tier]
              properties:
                tier: { type: string, enum: [gold, platinum, aleph] }
      responses:
        '200':
          description: Checkout URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  checkout_url: { type: string, format: uri }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/billing/portal:
    post:
      tags: [Billing]
      summary: Create billing portal session
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: Portal URL
          content:
            application/json:
              schema:
                type: object
                properties:
                  portal_url: { type: string, format: uri }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/billing/status:
    get:
      tags: [Billing]
      summary: Get subscription status
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: Current subscription info
          content:
            application/json:
              schema:
                type: object
                properties:
                  tier: { type: string, enum: [free, gold, platinum, aleph] }
                  status: { type: string, enum: [none, active, past_due, cancelled] }
                  current_period_end: { type: string, format: date-time, nullable: true }
                  cancel_at_period_end: { type: boolean }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/webhooks/stripe:
    post:
      tags: [Billing]
      summary: Stripe webhook
      description: Receives Stripe events (checkout.session.completed, invoice.paid, etc).
      parameters:
        - name: Stripe-Signature
          in: header
          required: true
          schema: { type: string }
      requestBody:
        required: true
        content:
          application/json:
            schema: { type: object }
      responses:
        '200': { description: Event acknowledged }

  /api/v1/preferences:
    get:
      tags: [Preferences]
      summary: Get user preferences
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: User preferences
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Preferences' }
        '401': { $ref: '#/components/responses/Unauthorized' }
    put:
      tags: [Preferences]
      summary: Update preferences
      description: Partial update — only provided fields are overwritten.
      security: [{ BearerAuth: [] }]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/PreferencesInput' }
      responses:
        '200':
          description: Updated preferences
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Preferences' }
        '400': { $ref: '#/components/responses/BadRequest' }
        '401': { $ref: '#/components/responses/Unauthorized' }

  /api/v1/admin/users:
    get:
      tags: [Admin]
      summary: List users (admin)
      security: [{ BearerAuth: [] }]
      parameters:
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: page_size
          in: query
          schema: { type: integer, default: 20, maximum: 100 }
        - name: search
          in: query
          schema: { type: string }
        - name: tier
          in: query
          schema: { type: string, enum: [free, gold, platinum, aleph] }
        - name: sort
          in: query
          description: "last_seen orders by last_seen_at, newest first; default is created_at."
          schema: { type: string, enum: [last_seen] }
      responses:
        '200':
          description: Paginated user list
          content:
            application/json:
              schema:
                type: object
                properties:
                  users:
                    type: array
                    items: { $ref: '#/components/schemas/AdminUser' }
                  total: { type: integer }
                  page: { type: integer }
        '401': { $ref: '#/components/responses/Unauthorized' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /api/v1/admin/users/{id}/tier:
    put:
      tags: [Admin]
      summary: Update user tier (admin)
      security: [{ BearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [tier]
              properties:
                tier: { type: string, enum: [free, gold, platinum, aleph] }
      responses:
        '200':
          description: Tier updated
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
                  tier: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /api/v1/admin/users/{id}/suspend:
    put:
      tags: [Admin]
      summary: Suspend/reactivate user (admin)
      security: [{ BearerAuth: [] }]
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string, format: uuid }
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [suspended]
              properties:
                suspended: { type: boolean }
      responses:
        '200':
          description: Status changed
          content:
            application/json:
              schema:
                type: object
                properties:
                  message: { type: string }
        '400': { $ref: '#/components/responses/BadRequest' }
        '403': { $ref: '#/components/responses/Forbidden' }

  /api/v1/admin/stats:
    get:
      tags: [Admin]
      summary: System statistics (admin)
      security: [{ BearerAuth: [] }]
      responses:
        '200':
          description: Aggregate system stats
          content:
            application/json:
              schema:
                type: object
                properties:
                  total_users: { type: integer }
                  active_users: { type: integer }
                  users_by_tier: { type: object, additionalProperties: { type: integer } }
                  total_signals: { type: integer }
                  signals_today: { type: integer }
                  active_subscriptions: { type: integer }
                  presence:
                    type: object
                    description: Signed-in users and anonymous browsers, reported apart (visitors include signed-in browsing).
                    properties:
                      online_window_minutes: { type: integer, example: 5 }
                      online_users: { type: integer, description: Distinct users with a live session stamped within the window. }
                      online_visitors: { type: integer, description: Distinct hashed IPs with a page view within the window. }
                      active_users_24h: { type: integer }
                      active_users_7d: { type: integer }
        '403': { $ref: '#/components/responses/Forbidden' }

  /api/v1/admin/signals:
    get:
      tags: [Admin]
      summary: List all signals (admin)
      security: [{ BearerAuth: [] }]
      parameters:
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: page_size
          in: query
          schema: { type: integer, default: 50, maximum: 100 }
      responses:
        '200':
          description: Paginated signal list
          content:
            application/json:
              schema:
                type: object
                properties:
                  signals:
                    type: array
                    items: { $ref: '#/components/schemas/AdminSignal' }
                  total: { type: integer }
                  page: { type: integer }
        '403': { $ref: '#/components/responses/Forbidden' }

  /api/v1/market/ticker:
    get:
      tags: [Market]
      summary: Live market ticker
      description: Top 25 crypto prices from Binance with sentiment analysis. Cached 15s.
      responses:
        '200':
          description: Ticker data
          content:
            application/json:
              schema: { $ref: '#/components/schemas/TickerResponse' }
        '503':
          description: Market data unavailable
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/v1/market/btc-macro:
    get:
      tags: [Market]
      summary: BTC macro regime analysis
      description: Returns HMM-based regime state for BTC. Cached 30s.
      responses:
        '200':
          description: BTC macro state
          content:
            application/json:
              schema:
                type: object
                properties:
                  regime: { type: string, enum: [Bull, Bear, Sideways, Transition] }
                  confidence: { type: number }
                  updated_at: { type: string, format: date-time }
        '503':
          description: Analysis not yet available
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Error' }

  /api/v1/ml/stats:
    get:
      tags: [ML]
      summary: ML outcome statistics
      description: Aggregated win/loss stats across ML sources. Cached 60s.
      responses:
        '200':
          description: ML stats
          content:
            application/json:
              schema:
                type: object
                properties:
                  total_outcomes: { type: integer }
                  win_rate: { type: number }
                  source_breakdown:
                    type: object
                    additionalProperties:
                      type: object
                      properties:
                        total: { type: integer }
                        wins: { type: integer }
                        win_rate: { type: number }

  /api/v1/ml/outcomes:
    get:
      tags: [ML]
      summary: ML outcome history
      description: Paginated individual outcome records.
      parameters:
        - name: page
          in: query
          schema: { type: integer, default: 1 }
        - name: pageSize
          in: query
          schema: { type: integer, default: 20, maximum: 100 }
      responses:
        '200':
          description: Outcome records
          content:
            application/json:
              schema:
                type: object
                properties:
                  data:
                    type: array
                    items: { $ref: '#/components/schemas/OutcomeRecord' }
                  total: { type: integer }
                  page: { type: integer }
                  pageSize: { type: integer }
                  hasMore: { type: boolean }

  /api/v1/stream:
    get:
      tags: [Stream]
      summary: SSE signal stream
      description: Server-Sent Events stream for real-time signals. Auth via query param token.
      parameters:
        - name: token
          in: query
          required: true
          schema: { type: string }
          description: JWT access token
      responses:
        '200':
          description: SSE stream established
          content:
            text/event-stream:
              schema:
                type: string

  /health:
    get:
      tags: [Health]
      summary: Liveness probe
      responses:
        '200':
          description: Service alive
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HealthResponse' }

  /health/ready:
    get:
      tags: [Health]
      summary: Readiness probe
      responses:
        '200':
          description: Service ready
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HealthResponse' }
        '503':
          description: Not ready
          content:
            application/json:
              schema: { $ref: '#/components/schemas/HealthResponse' }

  /health/deep:
    get:
      tags: [Health]
      summary: Deep health check
      description: Component-level health status.
      responses:
        '200':
          description: Component statuses
          content:
            application/json:
              schema:
                type: object
                properties:
                  status: { type: string, enum: [healthy, degraded] }
                  service: { type: string }
                  components:
                    type: array
                    items:
                      type: object
                      properties:
                        name: { type: string }
                        status: { type: string, enum: [up, down, disconnected] }

  /metrics:
    get:
      tags: [Health]
      summary: Prometheus metrics
      responses:
        '200':
          description: Prometheus text format
          content:
            text/plain:
              schema: { type: string }

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

  responses:
    BadRequest:
      description: Invalid request
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Unauthorized:
      description: Missing or invalid token
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    Forbidden:
      description: Insufficient permissions
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }
    RateLimited:
      description: Rate limit exceeded
      content:
        application/json:
          schema: { $ref: '#/components/schemas/Error' }

  schemas:
    Error:
      type: object
      properties:
        error: { type: string }

    AuthTokenResponse:
      type: object
      properties:
        access_token: { type: string }
        refresh_token: { type: string }
        expires_in: { type: integer, description: Seconds until access token expires (900) }
        user:
          type: object
          properties:
            id: { type: string }
            email: { type: string }
            tier: { type: string }
            display_name: { type: string }

    Signal:
      type: object
      properties:
        id: { type: string }
        symbol: { type: string }
        direction: { type: string, enum: [long, short] }
        mode: { type: string, enum: [smc_only, ind_only, hybrid] }
        entry: { type: number }
        tp: { type: array, items: { type: number } }
        sl: { type: number }
        confidence: { type: number }
        regime: { type: string, enum: [Bull, Bear, Sideways, Transition] }
        confluence:
          type: array
          items:
            type: object
            properties:
              source: { type: string }
              score: { type: number }
              weighted: { type: number }
        rr: { type: number }
        investment_score: { type: number }
        expires_at: { type: string, format: date-time }
        created_at: { type: string, format: date-time }

    ScannerStats:
      type: object
      properties:
        symbols_watched: { type: integer }
        signals_emitted: { type: integer }
        last_scan_time: { type: string, format: date-time }
        scans_total: { type: integer }
        running: { type: boolean }

    Engine:
      type: object
      properties:
        name: { type: string }
        status: { type: string, enum: [ready, starting, error] }
        weight: { type: number }

    Strategy:
      type: object
      properties:
        id: { type: string }
        user_id: { type: string }
        name: { type: string }
        description: { type: string }
        mode: { type: string, enum: [smc_only, ind_only, hybrid] }
        engines: { type: array, items: { type: string } }
        min_confidence: { type: number }
        direction: { type: string }
        symbols: { type: array, items: { type: string } }
        config: { type: object, additionalProperties: true }
        created_at: { type: string, format: date-time }
        updated_at: { type: string, format: date-time }

    StrategyInput:
      type: object
      required: [name]
      properties:
        name: { type: string }
        description: { type: string }
        mode: { type: string, enum: [smc_only, ind_only, hybrid], default: hybrid }
        engines: { type: array, items: { type: string } }
        min_confidence: { type: number, default: 60 }
        direction: { type: string, default: all }
        symbols: { type: array, items: { type: string } }
        config: { type: object, additionalProperties: true }

    Preferences:
      type: object
      properties:
        engine_selection:
          type: object
          properties:
            mode: { type: string }
            engines: { type: array, items: { type: string } }
        notification_settings:
          type: object
          properties:
            email_enabled: { type: boolean }
            push_enabled: { type: boolean }
            min_confidence: { type: number }
        ui_config:
          type: object
          properties:
            theme: { type: string }
            density: { type: string }
        updated_at: { type: string, format: date-time }

    PreferencesInput:
      type: object
      properties:
        engine_selection:
          type: object
          properties:
            mode: { type: string }
            engines: { type: array, items: { type: string } }
        notification_settings:
          type: object
          properties:
            email_enabled: { type: boolean }
            push_enabled: { type: boolean }
            min_confidence: { type: number }
        ui_config:
          type: object
          properties:
            theme: { type: string }
            density: { type: string }

    AdminUser:
      type: object
      properties:
        id: { type: string }
        email: { type: string }
        display_name: { type: string }
        tier: { type: string }
        role: { type: string }
        is_active: { type: boolean }
        email_verified: { type: boolean }
        created_at: { type: string, format: date-time }
        last_seen_at: { type: string, format: date-time, description: Latest session activity; absent when never signed in. }
        online: { type: boolean, description: A live session stamped within the last 5 minutes. }

    AdminSignal:
      type: object
      properties:
        id: { type: string }
        symbol: { type: string }
        timeframe: { type: string }
        direction: { type: string }
        mode: { type: string }
        entry_price: { type: number }
        stop_loss: { type: number }
        take_profit: { type: number }
        confidence: { type: number }
        regime: { type: string }
        confluence: { type: string }
        reward_risk: { type: number }
        created_at: { type: string, format: date-time }

    TickerResponse:
      type: object
      properties:
        items:
          type: array
          items:
            type: object
            properties:
              symbol: { type: string }
              price: { type: number }
              change_pct: { type: number }
              direction: { type: string, enum: [up, down] }
        snippets: { type: array, items: { type: string } }
        updated_at: { type: string, format: date-time }

    OutcomeRecord:
      type: object
      properties:
        signal_id: { type: string }
        source: { type: string }
        symbol: { type: string }
        direction: { type: string }
        confidence: { type: number }
        outcome: { type: boolean }
        pnl_percent: { type: number }
        features: { type: string }
        evaluated_at: { type: string, format: date-time }

    HealthResponse:
      type: object
      properties:
        status: { type: string }
        service: { type: string }
        version: { type: string }
