openapi: 3.1.0
info:
  title: Omni Terminal Public API
  version: '1.4.0'
  summary: Public market-data, news, market-regime, builder-fee and Ask Omni endpoints for Omni Terminal.
  description: |
    The public Omni Terminal API exposes read-only market data, AI news, ML market-regime
    projections, Hyperliquid builder-fee accounting, public wallet profiles, and the
    API-key-gated **Ask Omni** risk engine. Most routes are served by the `omni_api` backend;
    Market Regimes is a same-origin, read-only projection served only on
    `https://omniterminal.app`.

    Authenticated trading, account, and IBKR endpoints (`/api/hl/*`, `/api/ibkr/*`,
    `/api/v2/admin/*`) require a wallet/JWT session and are **not** part of this public
    reference. The primary machine hostname is `https://api.omniterminal.app`;
    app-host API paths remain available for compatibility.

    ## Authentication
    Most terminal endpoints are unauthenticated. The paid News API requires a public API key
    in the `x-api-key` header and a product-scoped News API entitlement. News API keys are not
    accepted in query strings. Customer keys are named, displayed only once, stored as one-way
    hashes, and newly issued keys require the `news_api:read` scope. `POST /api/v1/ask-omni`
    supports the existing general API-key authentication described on that operation. Public
    Market Regimes reads do not require an API key. A News API key does not authorize Market
    Regimes operator actions; those remain behind an authenticated Omni admin/owner session and
    a separate server-side operator token and are intentionally omitted from this public contract.

    ## WebSocket streams
    Real-time data is delivered over WSS, not REST. Streams live under `/terminal/ws/*` and
    use a subscribe model; many send an initial snapshot on connect. Representative streams:

    | Stream | Path |
    | --- | --- |
    | Trades | `/terminal/ws/market/trades/{exchange}/{symbol}` |
    | Ticker | `/terminal/ws/market/ticker/{exchange}/{symbol}` |
    | Orderbook (L2) | `/terminal/ws/market/orderbook/{exchange}/{symbol}` |
    | Orderbook (L4) | `/terminal/ws/market/orderbook-l4/{exchange}/{symbol}` |
    | Candles | `/terminal/ws/market/candles/{exchange}/{symbol}` |
    | Liquidations | `/terminal/ws/market/liquidations/{exchange}/{symbol}` |
    | Funding | `/terminal/ws/market/funding/{exchange}/{symbol}` |
    | Paid AI news | `POST /api/v1/news/ws-ticket`, then `/ws/v1/news?ticket=ONE_TIME_TICKET` |

    Paid AI news tickets are single-use. Pro allows 2 concurrent news sockets and Enterprise
    allows 10. Clients should send `{"type":"ping"}` at least every 60 seconds; a public news
    socket closes after 120 seconds without client activity. Client frames and messages are
    limited to 4 KiB. These controls do not apply to the terminal's private internal stream.
  contact:
    name: Omni Terminal
    url: https://omniterminal.app/docs
servers:
  - url: https://api.omniterminal.app
    description: Production API (primary)
  - url: https://omniterminal.app
    description: Production app-host compatibility route
  - url: https://dev.omniterminal.app
    description: Development
tags:
  - name: Health
    description: Service liveness.
  - name: Market Data
    description: Public instruments and candles.
  - name: News
    description: |
      Deduplicated, clustered, AI-enriched market events from 50+ active sources. Public results
      use the versioned `news_event.v1` contract and include model, prompt, schema, and validation
      lineage. Send a stable, descriptive `User-Agent`; generic automation signatures may be
      rejected by edge security. The private source inventory and raw ingest payloads are not exposed.
  - name: Market Regimes
    description: |
      Read-only ML regime health, strategy, Hyperliquid-universe, and chart projections exposed
      through Omni Terminal's same-origin proxy. These endpoints do not start training, refresh
      market data, place orders, or mutate paper state. Operator routes are not public API routes.
  - name: Ask Omni
    description: API-key-gated AI risk engine.
  - name: Builder Fees
    description: Hyperliquid builder-fee accounting (DefiLlama integration).
  - name: Profiles
    description: Public on-chain wallet profiles.
paths:
  /health:
    get:
      tags: [Health]
      operationId: getHealth
      summary: Service health
      security: []
      responses:
        '200':
          description: Service is healthy.
          content:
            application/json:
              schema:
                type: object
                properties:
                  status:
                    type: string
                    example: ok
  /terminal/instruments:
    get:
      tags: [Market Data]
      operationId: listInstruments
      summary: List instruments
      description: Returns the available symbols and per-symbol instrument metadata for an exchange.
      security: []
      parameters:
        - name: exchange
          in: query
          required: false
          schema:
            type: string
            default: hyperliquid
            enum: [hyperliquid, ibkr]
          description: Exchange to list. `ibkr` requires an email/Google session.
      responses:
        '200':
          description: Instrument catalog.
          content:
            application/json:
              schema:
                type: object
                properties:
                  symbols:
                    type: array
                    items:
                      type: string
                    example: ['BTC', 'ETH', 'SOL']
                  instruments:
                    type: object
                    additionalProperties: true
                    description: Instrument metadata keyed by symbol.
  /terminal/market/candles/{exchange}/{symbol}:
    get:
      tags: [Market Data]
      operationId: getMarketCandles
      summary: Historical candles
      security: []
      parameters:
        - name: exchange
          in: path
          required: true
          schema:
            type: string
            example: hyperliquid
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: BTC
        - name: interval
          in: query
          required: false
          schema:
            type: string
            default: '1h'
            example: '1h'
        - name: from
          in: query
          required: false
          schema:
            type: integer
            format: int64
          description: Start time (unix ms).
        - name: to
          in: query
          required: false
          schema:
            type: integer
            format: int64
          description: End time (unix ms).
        - name: limit
          in: query
          required: false
          schema:
            type: integer
            default: 500
            maximum: 2000
            minimum: 1
      responses:
        '200':
          description: Candle series.
          content:
            application/json:
              schema:
                type: object
                properties:
                  exchange:
                    type: string
                  symbol:
                    type: string
                  interval:
                    type: string
                  candles:
                    type: array
                    items:
                      $ref: '#/components/schemas/Candle'
  /terminal/news:
    get:
      tags: [News]
      operationId: listNews
      summary: List news items
      security: []
      parameters:
        - $ref: '#/components/parameters/NewsLimit'
        - $ref: '#/components/parameters/NewsLookbackDays'
        - $ref: '#/components/parameters/NewsBeforeTimestamp'
        - $ref: '#/components/parameters/NewsMarket'
        - name: analysis_type
          in: query
          required: false
          schema:
            type: string
        - name: source_channel
          in: query
          required: false
          schema:
            type: string
        - name: topics
          in: query
          required: false
          schema:
            type: string
          description: Comma-separated topic filters.
      responses:
        '200':
          description: News items (newest first).
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsResponse'
  /terminal/news/{symbol}:
    get:
      tags: [News]
      operationId: listNewsForSymbol
      summary: News for a symbol
      security: []
      parameters:
        - name: symbol
          in: path
          required: true
          schema:
            type: string
            example: BTC
        - $ref: '#/components/parameters/NewsLimit'
        - $ref: '#/components/parameters/NewsLookbackDays'
        - $ref: '#/components/parameters/NewsBeforeTimestamp'
        - $ref: '#/components/parameters/NewsMarket'
      responses:
        '200':
          description: News items for the symbol.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsResponse'
  /api/v1/news/health:
    get:
      tags: [News]
      operationId: getPublicNewsHealth
      summary: Paid News API health
      description: Read-only API availability and upstream fanout status. Does not inspect or mutate ingestion.
      security: []
      responses:
        '200':
          description: News API is available.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api/v1/news:
    get:
      tags: [News]
      operationId: listPublicNews
      summary: List AI-analyzed market news
      description: |
        Paid, API-key-authenticated access to the same durable ClickHouse/Redis outputs
        that power Omni Terminal. Pro requests are capped at 500 items and seven days;
        Enterprise requests are capped at 2,000 items and 30 days.
      security:
        - NewsApiKeyAuth: []
      parameters:
        - $ref: '#/components/parameters/NewsObjectVersion'
        - $ref: '#/components/parameters/NewsLimit'
        - $ref: '#/components/parameters/NewsLookbackDays'
        - $ref: '#/components/parameters/NewsBeforeTimestamp'
        - $ref: '#/components/parameters/NewsMarket'
        - name: analysis_type
          in: query
          schema: { type: string }
        - name: topics
          in: query
          description: Comma-separated topic filters.
          schema: { type: string }
      responses:
        '200':
          description: AI-analyzed news, newest first.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicNewsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: API key missing or invalid.
        '403':
          description: Pro or Enterprise plan required.
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/v1/news/{symbol}:
    get:
      tags: [News]
      operationId: listPublicNewsForSymbol
      summary: List AI news for a symbol
      security:
        - NewsApiKeyAuth: []
      parameters:
        - name: symbol
          in: path
          required: true
          schema: { type: string, example: BTC }
        - $ref: '#/components/parameters/NewsObjectVersion'
        - $ref: '#/components/parameters/NewsLimit'
        - $ref: '#/components/parameters/NewsLookbackDays'
        - $ref: '#/components/parameters/NewsBeforeTimestamp'
        - $ref: '#/components/parameters/NewsMarket'
      responses:
        '200':
          description: Symbol-filtered AI news.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/PublicNewsResponse'
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: API key missing or invalid.
        '403':
          description: Pro or Enterprise plan required.
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/v1/news/ws-ticket:
    post:
      tags: [News]
      operationId: createPublicNewsWebSocketTicket
      summary: Create a single-use WebSocket ticket
      description: |
        Exchanges the header-authenticated News API key for a single-use ticket that expires in
        60 seconds. Put the returned ticket in `/ws/v1/news?ticket=...` during the WebSocket
        upgrade. The long-lived API key is never placed in the WebSocket URL. Pro allows 2
        concurrent News WebSocket connections and Enterprise allows 10. A ticket is consumed by
        its first upgrade attempt, including an attempt rejected by the concurrent-connection cap.
      security:
        - NewsApiKeyAuth: []
      responses:
        '201':
          description: WebSocket ticket created.
          headers:
            X-RateLimit-Limit:
              $ref: '#/components/headers/RateLimitLimit'
            X-RateLimit-Remaining:
              $ref: '#/components/headers/RateLimitRemaining'
            X-RateLimit-Reset:
              $ref: '#/components/headers/RateLimitReset'
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/NewsWebSocketTicket'
        '401':
          description: API key missing or invalid.
        '403':
          description: News API entitlement required.
        '429':
          $ref: '#/components/responses/RateLimited'
  /api/market-regimes/health/live:
    servers:
      - url: https://omniterminal.app
        description: Production app host
    get:
      tags: [Market Regimes]
      operationId: getMarketRegimesHealth
      summary: Market Regimes liveness and safety mode
      description: |
        Returns liveness plus the active operating and write-authentication safety state. This
        read does not trigger training, inference, source refreshes, or strategy execution.
      security: []
      responses:
        '200':
          description: Market Regimes is live.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketRegimesHealth'
        '404':
          $ref: '#/components/responses/MarketRegimesUnavailable'
        '502':
          $ref: '#/components/responses/MarketRegimesProxyFailure'
  /api/market-regimes/strategy/report:
    servers:
      - url: https://omniterminal.app
        description: Production app host
    get:
      tags: [Market Regimes]
      operationId: getMarketRegimesStrategyReport
      summary: Current market-regime strategy report
      description: |
        Returns current target-level regime, validation, source-freshness, decision, and paper-state
        projections for the guarded `bear_accumulation` research strategy. This is decision support,
        not an order or a guarantee of future performance.
      security: []
      parameters:
        - $ref: '#/components/parameters/MarketRegimesStrategyName'
      responses:
        '200':
          description: Current strategy report.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketRegimesStrategyReport'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/MarketRegimesUnavailable'
        '502':
          $ref: '#/components/responses/MarketRegimesProxyFailure'
  /api/market-regimes/strategy/composite-report:
    servers:
      - url: https://omniterminal.app
        description: Production app host
    get:
      tags: [Market Regimes]
      operationId: getMarketRegimesCompositeReport
      summary: Current multi-timeframe composite regime report
      description: Returns the current composite signal projection without mutating model or strategy state.
      security: []
      parameters:
        - $ref: '#/components/parameters/MarketRegimesStrategyName'
      responses:
        '200':
          description: Current composite report.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketRegimesCompositeReport'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          $ref: '#/components/responses/MarketRegimesUnavailable'
        '502':
          $ref: '#/components/responses/MarketRegimesProxyFailure'
  /api/market-regimes/hyperliquid/dashboard:
    servers:
      - url: https://omniterminal.app
        description: Production app host
    get:
      tags: [Market Regimes]
      operationId: getMarketRegimesHyperliquidDashboard
      summary: Hyperliquid regime-universe dashboard
      description: |
        Returns public-safe universe readiness, history progress, source freshness, persisted
        predictions, validation display state, and guarded decisions. Markets may remain visible as
        warming up until their configured closed-candle requirement is met.
      security: []
      responses:
        '200':
          description: Current Hyperliquid regime-universe projection.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketRegimesHyperliquidDashboard'
        '404':
          $ref: '#/components/responses/MarketRegimesUnavailable'
        '502':
          $ref: '#/components/responses/MarketRegimesProxyFailure'
  /api/market-regimes/charts/figure:
    servers:
      - url: https://omniterminal.app
        description: Production app host
    get:
      tags: [Market Regimes]
      operationId: getMarketRegimesChartFigure
      summary: Regime-coloured price and projection figure
      description: |
        Returns a Plotly-compatible JSON figure for one exact target. `refresh=true` only rebuilds
        the bounded figure cache from persisted data; it does not fetch market data, train a model,
        run inference, or mutate strategy/paper state.
      security: []
      parameters:
        - name: target_id
          in: query
          required: true
          description: Exact target identifier returned by a strategy or Hyperliquid dashboard report.
          schema:
            type: string
            example: ibkr:stock:STK:INTC:NASDAQ:USD:1d
        - name: lookback_days
          in: query
          required: false
          schema:
            type: integer
            minimum: 90
            maximum: 3650
            default: 1095
        - name: refresh
          in: query
          required: false
          schema:
            type: boolean
            default: false
        - name: prediction_ts
          in: query
          required: false
          description: Optional latest-prediction unix-ms value used to version the figure cache.
          schema:
            type: integer
            format: int64
            minimum: 0
      responses:
        '200':
          description: Plotly-compatible figure JSON.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/MarketRegimesFigure'
        '400':
          $ref: '#/components/responses/BadRequest'
        '404':
          description: Market Regimes is disabled, the target is unknown, or no persisted regime history exists.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '502':
          $ref: '#/components/responses/MarketRegimesProxyFailure'
  /integrations/defillama/v1/builder-fees:
    get:
      tags: [Builder Fees]
      operationId: getBuilderFees
      summary: Hyperliquid builder fees
      description: |
        Daily USDC builder-fee totals for the configured builder address over a time window.
        Window must be <= 366 days. If `OMNI_DEFILLAMA_SECRET` is configured server-side,
        the `X-DefiLlama-Secret` header is required.
      security: []
      parameters:
        - name: startTimestamp
          in: query
          required: true
          schema:
            type: integer
            format: int64
          description: Window start (unix seconds, inclusive). Accepts `start_timestamp` too.
        - name: endTimestamp
          in: query
          required: true
          schema:
            type: integer
            format: int64
          description: Window end (unix seconds, exclusive). Accepts `end_timestamp` too.
        - name: X-DefiLlama-Secret
          in: header
          required: false
          schema:
            type: string
          description: Required only when the server is configured with a shared secret.
      responses:
        '200':
          description: Builder-fee totals.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '400':
          $ref: '#/components/responses/BadRequest'
        '401':
          description: Invalid or missing `X-DefiLlama-Secret`.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
  /api/hl/public-profile:
    get:
      tags: [Profiles]
      operationId: getPublicProfile
      summary: Public wallet profile
      security: []
      parameters:
        - name: address
          in: query
          required: true
          schema:
            type: string
            example: '0x0000000000000000000000000000000000000000'
          description: Hyperliquid wallet address.
      responses:
        '200':
          description: Public profile for the address.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
  /api/v1/ask-omni:
    post:
      tags: [Ask Omni]
      operationId: askOmni
      summary: Ask Omni risk engine
      description: |
        Runs an AI risk/market analysis. Requires a public API key (`x-api-key` header or
        `?api_key=`) on an API-enabled plan (`pro` or `enterprise`).
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/AskOmniRequest'
      responses:
        '200':
          description: Analysis result.
          content:
            application/json:
              schema:
                type: object
                additionalProperties: true
        '401':
          description: Missing or invalid API key.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
        '403':
          description: Plan does not include API access.
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/Error'
components:
  securitySchemes:
    NewsApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: Product-scoped News API key. Header only; query-string API keys are rejected.
    ApiKeyAuth:
      type: apiKey
      in: header
      name: x-api-key
      description: Public API key. May also be supplied as `?api_key=` query parameter.
  headers:
    RateLimitLimit:
      description: Maximum requests allowed in the current one-minute bucket.
      schema: { type: integer, format: int64 }
    RateLimitRemaining:
      description: Requests remaining in the current one-minute bucket.
      schema: { type: integer, format: int64 }
    RateLimitReset:
      description: Unix timestamp in seconds when the current bucket resets.
      schema: { type: integer, format: int64 }
  parameters:
    NewsObjectVersion:
      name: object_version
      in: query
      required: false
      description: Requested event object contract. Unsupported versions return HTTP 400.
      schema:
        type: string
        default: news_event.v1
        enum: [news_event.v1]
    NewsLimit:
      name: limit
      in: query
      required: false
      schema:
        type: integer
        default: 50
        minimum: 1
        maximum: 2000
    NewsLookbackDays:
      name: lookback_days
      in: query
      required: false
      schema:
        type: integer
        format: int64
    NewsBeforeTimestamp:
      name: before_timestamp
      in: query
      required: false
      schema:
        type: integer
        format: int64
      description: Page backwards from this unix-ms cursor.
    NewsMarket:
      name: market
      in: query
      required: false
      schema:
        type: string
        enum: [crypto, tradfi]
      description: Filter by market bias.
    MarketRegimesStrategyName:
      name: strategy_name
      in: query
      required: false
      description: Stable research-strategy identifier.
      schema:
        type: string
        default: bear_accumulation
        enum: [bear_accumulation]
  responses:
    BadRequest:
      description: Invalid request parameters.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    RateLimited:
      description: The plan-specific rate limit was exceeded.
      headers:
        Retry-After:
          description: Seconds until another request should be attempted.
          schema: { type: integer }
        X-RateLimit-Limit:
          $ref: '#/components/headers/RateLimitLimit'
        X-RateLimit-Remaining:
          $ref: '#/components/headers/RateLimitRemaining'
        X-RateLimit-Reset:
          $ref: '#/components/headers/RateLimitReset'
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/Error'
    MarketRegimesUnavailable:
      description: Market Regimes is disabled for this environment or route.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MarketRegimesProxyError'
    MarketRegimesProxyFailure:
      description: Omni Terminal could not obtain a valid JSON response from the private Market Regimes service.
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/MarketRegimesProxyError'
  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
        detail:
          type: string
        supported_versions:
          type: array
          items: { type: string }
    Candle:
      type: object
      description: OHLCV candle. Field names follow the upstream feed.
      additionalProperties: true
      properties:
        t:
          type: integer
          format: int64
          description: Open time (unix ms).
        o:
          type: number
        h:
          type: number
        l:
          type: number
        c:
          type: number
        v:
          type: number
    NewsResponse:
      type: object
      properties:
        data:
          type: array
          items:
            $ref: '#/components/schemas/NewsEvent'
        items:
          type: array
          items:
            type: object
            additionalProperties: true
          description: Alias of `data` for client compatibility.
        next_before_timestamp:
          type: [integer, 'null']
          format: int64
          description: Cursor for the next page, or null when exhausted.
        has_more:
          type: boolean
    PublicNewsResponse:
      type: object
      required: [object, api_version, tier, data, pagination]
      properties:
        object:
          type: string
          const: list
        api_version:
          type: string
          example: '2026-07-14'
        tier:
          type: string
          enum: [pro, enterprise]
        data:
          type: array
          items:
            $ref: '#/components/schemas/NewsEvent'
        pagination:
          type: object
          required: [limit, has_more]
          properties:
            limit:
              type: integer
            next_before_timestamp:
              type: [integer, 'null']
              format: int64
            has_more:
              type: boolean
    NewsEvent:
      type: object
      additionalProperties: false
      required:
        [
          object,
          id,
          event_id,
          timestamp,
          headline,
          summary,
          direction,
          sentiment,
          bias,
          impact,
          tickers,
          confidence,
          source,
          analysis_type,
          topics,
          theme,
          event_type,
          importance,
          analysis,
          revision,
          market_context
        ]
      properties:
        object:
          type: string
          const: news_event.v1
        id:
          type: string
          description: Immutable identifier for this analysis revision.
        event_id:
          type: string
          description: Stable logical-event identifier shared by linked corrections or reanalysis revisions.
        timestamp:
          type: integer
          format: int64
          description: Event time in unix milliseconds.
        headline: { type: string }
        summary: { type: string }
        direction: { type: string, example: neutral }
        sentiment: { type: number }
        bias: { type: string, example: both }
        impact: { type: string, example: high }
        tickers:
          type: array
          items: { type: string }
          example: [BTC, ETH]
        confidence: { type: number }
        source:
          type: string
          description: Public-safe source label; private feed inventory is not exposed.
        analysis_type: { type: string }
        topics:
          type: array
          items: { type: string }
        theme: { type: string }
        event_type: { type: string }
        importance: { type: number }
        analysis:
          $ref: '#/components/schemas/NewsAnalysisLineage'
        revision:
          $ref: '#/components/schemas/NewsRevision'
        market_context:
          oneOf:
            - $ref: '#/components/schemas/NewsMarketContext'
            - type: 'null'
    NewsAnalysisLineage:
      type: object
      additionalProperties: false
      required: [schema_version, prompt_version, model, validation_status, generated_at]
      properties:
        schema_version: { type: string, example: '1' }
        prompt_version: { type: string, example: 'v2' }
        model: { type: string, example: 'gpt-5.4-mini' }
        validation_status: { type: string, example: valid }
        generated_at:
          type: [string, 'null']
          format: date-time
          description: Time this analysis revision was generated; null for legacy records.
    NewsRevision:
      type: object
      additionalProperties: false
      required: [kind, supersedes_id, generated_at]
      properties:
        kind:
          type: string
          enum: [original, correction, backfill, reanalysis]
        supersedes_id:
          type: [string, 'null']
          description: Earlier analysis revision replaced or extended by this record, when linked.
        generated_at:
          type: [string, 'null']
          format: date-time
          description: Time this revision was generated; null for legacy records.
    NewsMarketContext:
      type: object
      additionalProperties: false
      required:
        [
          object,
          summary,
          direction,
          sentiment_rating,
          confidence,
          notable_tickers,
          window_hours,
          cluster_count,
          timeline
        ]
      properties:
        object: { type: string, const: market_context.v1 }
        summary: { type: string }
        direction: { type: string }
        sentiment_rating: { type: number }
        confidence: { type: number }
        notable_tickers:
          type: array
          items: { type: string }
        window_hours: { type: [integer, 'null'] }
        cluster_count: { type: [integer, 'null'] }
        timeline:
          type: array
          items:
            type: object
            additionalProperties: false
            required: [summary, direction, sentiment_rating, confidence, category, tickers]
            properties:
              summary: { type: string }
              direction: { type: string }
              sentiment_rating: { type: number }
              confidence: { type: number }
              category: { type: string }
              tickers:
                type: array
                items: { type: string }
    NewsWebSocketTicket:
      type: object
      additionalProperties: false
      required:
        - ticket
        - expires_in
        - single_use
        - websocket_path
        - max_connections
        - idle_timeout_seconds
        - recommended_ping_interval_seconds
        - max_client_message_bytes
      properties:
        ticket:
          type: string
          minLength: 32
          maxLength: 32
        expires_in:
          type: integer
          const: 60
        single_use:
          type: boolean
          const: true
        websocket_path:
          type: string
          const: /ws/v1/news
        max_connections:
          type: integer
          enum: [2, 10]
          description: Concurrent public News WebSocket cap for the authenticated plan.
        idle_timeout_seconds:
          type: integer
          const: 120
        recommended_ping_interval_seconds:
          type: integer
          const: 60
        max_client_message_bytes:
          type: integer
          const: 4096
    MarketRegimesProxyError:
      type: object
      required: [error]
      properties:
        error:
          type: string
          enum:
            [
              market_regimes_disabled,
              market_regimes_route_not_found,
              market_regimes_unavailable,
              market_regimes_invalid_response,
              market_regimes_invalid_json
            ]
        message:
          type: string
        status:
          type: integer
    MarketRegimesWriteAuth:
      type: object
      required: [mode, configured]
      properties:
        mode:
          type: string
          enum: [required, disabled]
        configured:
          type: boolean
    MarketRegimesHealth:
      type: object
      required:
        [
          status,
          operation_mode,
          run_on_start,
          train_on_start,
          internal_scheduler_enabled,
          internal_scheduler_running,
          hyperliquid_automation_enabled,
          write_auth
        ]
      properties:
        status:
          type: string
          const: ok
        operation_mode:
          type: string
          enum: [ad_hoc, scheduled]
        run_on_start: { type: boolean }
        train_on_start: { type: boolean }
        internal_scheduler_enabled: { type: boolean }
        internal_scheduler_running: { type: boolean }
        hyperliquid_automation_enabled: { type: boolean }
        write_auth:
          $ref: '#/components/schemas/MarketRegimesWriteAuth'
    MarketRegimesTarget:
      type: object
      additionalProperties: true
      required: [target_id, exchange, category, symbol, interval, source_freshness_status]
      properties:
        target_id: { type: string }
        exchange: { type: string }
        category: { type: string }
        symbol: { type: string }
        interval: { type: string }
        latest_prediction_ts: { type: [integer, 'null'], format: int64 }
        latest_source_candle_ts: { type: [integer, 'null'], format: int64 }
        expected_source_candle_ts: { type: [integer, 'null'], format: int64 }
        source_freshness_status: { type: string }
        universe_status: { type: [string, 'null'] }
        universe_status_reason: { type: [string, 'null'] }
        prediction: { type: [object, 'null'], additionalProperties: true }
        validation: { type: [object, 'null'], additionalProperties: true }
        decision: { type: [object, 'null'], additionalProperties: true }
        warnings:
          type: array
          items: { type: string }
    MarketRegimesStrategyReport:
      type: object
      additionalProperties: true
      required: [generated_at, strategy_name, summary, targets]
      properties:
        generated_at:
          {
            type: integer,
            format: int64,
            description: Report generation time in unix milliseconds.
          }
        strategy_name: { type: string, const: bear_accumulation }
        summary: { type: object, additionalProperties: true }
        composite: { type: object, additionalProperties: true }
        targets:
          type: array
          items:
            $ref: '#/components/schemas/MarketRegimesTarget'
    MarketRegimesCompositeReport:
      type: object
      additionalProperties: true
      required: [generated_at, strategy_name, enabled, schema_version, summary, signals]
      properties:
        generated_at: { type: integer, format: int64 }
        strategy_name: { type: string, const: bear_accumulation }
        enabled: { type: boolean }
        schema_version: { type: string }
        required_intervals:
          type: array
          items: { type: string }
        summary: { type: object, additionalProperties: true }
        signals:
          type: array
          items: { type: object, additionalProperties: true }
    MarketRegimesHyperliquidDashboard:
      type: object
      additionalProperties: true
      required: [schema_version, status, generated_at, summary, targets]
      properties:
        schema_version: { type: string }
        status: { type: string }
        discovered_at: { type: [integer, string, 'null'] }
        generated_at: { type: integer, format: int64 }
        summary: { type: object, additionalProperties: true }
        cadence: { type: object, additionalProperties: true }
        composite: { type: object, additionalProperties: true }
        targets:
          type: array
          items:
            $ref: '#/components/schemas/MarketRegimesTarget'
    MarketRegimesFigure:
      type: object
      required: [data, layout]
      properties:
        data:
          type: array
          items:
            type: object
            additionalProperties: true
        layout:
          type: object
          additionalProperties: true
    AskOmniRequest:
      type: object
      properties:
        question:
          type: string
          description: Free-form question. Omit when using `presetId`.
        presetId:
          type: string
          description: One of the built-in presets (e.g. `risk_overview`, `liquidation_check`).
        extraContext:
          type: string
        scopeAddress:
          type: string
          description: Wallet address to scope the analysis to.
        selectedSymbol:
          type: string
        liquidationScope:
          type: string
