asyncapi: 2.6.0
info:
  title: PredictAsiaX WebSocket API
  version: "1.0.0"
  description: |
    PAX WS API — single WebSocket endpoint (`wss://predictasiax.com/ws`) with
    channel subscriptions. Client uses 4 method verbs (`SUBSCRIBE` / `UNSUBSCRIBE` /
    `AUTH` / `LOCALE`) to control message stream.

    **Design choices** (from Binance + Deriv research):
    - Single connection per client — server multiplexes ~70 event types via `type`
      field. Reduces TCP overhead vs one-connection-per-channel model (Kraken pattern).
    - Server multiplexes broadcasts across the backend fleet — subscriptions are
      stateful per-connection.
    - AUTH is optional. Public channels (fast_tick, ticker, markets_snapshot, orderbook)
      work anonymously. Private channels (account, deposit, withdrawal_status_changed,
      security_alert, session_device_mismatch) require AUTH.
    - `LOCALE` binds i18n on the connection — subsequent broadcasts render human-facing
      text (titles, toasts) in the client's language.

    **Reconnect**: heartbeat ping every 30s; if pong missed twice, connection is
    reaped. Client should exp-backoff reconnect from 1s to 60s max, resubscribe
    to same channels on reconnect.
  contact:
    email: partners@predictasiax.com
    url: https://docs.predictasiax.com
  license:
    name: PAX API Terms
    url: https://predictasiax.com/legal/api-terms

servers:
  production:
    url: wss://predictasiax.com/ws
    protocol: wss
    description: Production WebSocket (real market data + real trades)
  sandbox:
    url: wss://sandbox.predictasiax.com/ws
    protocol: wss
    description: Sandbox WebSocket (fake data + resettable)

# ─────────────────────────────────────────────────────────────────────────────
# Channels — single "/" channel, message-type multiplexed
# ─────────────────────────────────────────────────────────────────────────────
# NOTE (AsyncAPI 2.6 direction semantics):
#   `publish` = client publishes to server (server receives)
#   `subscribe` = server publishes to client (client receives / subscribes)
channels:
  /:
    description: Root WebSocket connection — all messages flow here, filtered by `type`.
    publish:
      summary: Client → Server commands
      operationId: sendCommand
      message:
        oneOf:
          - $ref: '#/components/messages/CmdSubscribe'
          - $ref: '#/components/messages/CmdUnsubscribe'
          - $ref: '#/components/messages/CmdAuth'
          - $ref: '#/components/messages/CmdLocale'
    subscribe:
      summary: Server → Client events (multiplexed via `type` field)
      operationId: receiveEvent
      message:
        oneOf:
          # Meta / handshake
          - $ref: '#/components/messages/EvSubscribed'
          - $ref: '#/components/messages/EvUnsubscribed'
          - $ref: '#/components/messages/EvAuthenticated'
          - $ref: '#/components/messages/EvLocaleSet'
          - $ref: '#/components/messages/EvForceLogout'
          # Market data (public — no AUTH required)
          - $ref: '#/components/messages/EvFastTick'
          - $ref: '#/components/messages/EvTicker'
          - $ref: '#/components/messages/EvMarketsSnapshot'
          - $ref: '#/components/messages/EvMarketsUpdated'
          - $ref: '#/components/messages/EvOrderbook'
          - $ref: '#/components/messages/EvTradeExecuted'
          - $ref: '#/components/messages/EvWhaleTrade'
          - $ref: '#/components/messages/EvNormalMarket'
          - $ref: '#/components/messages/EvCreatorMarketCreate'
          # Fast round lifecycle (public + subscription-gated)
          - $ref: '#/components/messages/EvFastRoundCommit'
          - $ref: '#/components/messages/EvFastRoundSettled'
          - $ref: '#/components/messages/EvFastSettlementStatement'
          - $ref: '#/components/messages/EvFastSettlementToast'
          # Signals & AI (subscription-gated but not user-scoped)
          - $ref: '#/components/messages/EvSignal'
          - $ref: '#/components/messages/EvSignalInsight'
          - $ref: '#/components/messages/EvSignalsSnapshot'
          - $ref: '#/components/messages/EvAiAnalysis'
          - $ref: '#/components/messages/EvAiSummaryRefresh'
          - $ref: '#/components/messages/EvNews'
          # Account (private — AUTH required)
          - $ref: '#/components/messages/EvAccount'
          - $ref: '#/components/messages/EvDeposit'
          - $ref: '#/components/messages/EvCustodyDeposit'
          - $ref: '#/components/messages/EvCustodyDepositCredit'
          - $ref: '#/components/messages/EvDepositCredit'
          - $ref: '#/components/messages/EvWithdrawalStatusChanged'
          - $ref: '#/components/messages/EvQuickWithdrawal'
          - $ref: '#/components/messages/EvLimitOrderFill'
          - $ref: '#/components/messages/EvParlayBet'
          - $ref: '#/components/messages/EvParlaySettled'
          - $ref: '#/components/messages/EvReferralBonus'
          - $ref: '#/components/messages/EvReferralEarningRealtime'
          - $ref: '#/components/messages/EvAchievementUnlocked'
          # Security / anti-abuse (private)
          - $ref: '#/components/messages/EvSecurityAlert'
          - $ref: '#/components/messages/EvSessionDeviceMismatch'
          - $ref: '#/components/messages/EvBonusAbuse'
          - $ref: '#/components/messages/EvCreditAddrMismatch'
          - $ref: '#/components/messages/EvCreditBurst'
          - $ref: '#/components/messages/EvDepositAddrMismatch'
          # Ops (public — surfaced for transparency)
          - $ref: '#/components/messages/EvCircuitBreaker'
          - $ref: '#/components/messages/EvCircuitBreakerCleared'
          - $ref: '#/components/messages/EvCacheInvalidate'
          - $ref: '#/components/messages/EvStablecoinSnapshot'
          - $ref: '#/components/messages/EvDisputeCreated'
          - $ref: '#/components/messages/EvLpPoolNegative'
          - $ref: '#/components/messages/EvLpWithdrawalProcessed'
          - $ref: '#/components/messages/EvFundDepleted'
          # Admin (admin-only, filtered server-side)
          - $ref: '#/components/messages/EvAdminAlert'
          - $ref: '#/components/messages/EvAdminAction'
          - $ref: '#/components/messages/EvAutoscale'

# ─────────────────────────────────────────────────────────────────────────────
# Reusable messages + schemas
# ─────────────────────────────────────────────────────────────────────────────
components:
  # ── Client → Server commands ───────────────────────────────────────────────
  messages:
    CmdSubscribe:
      name: subscribe
      title: Subscribe to channels
      summary: Add channels to this connection's filter list
      payload:
        type: object
        required: [method, params]
        properties:
          method: { type: string, const: SUBSCRIBE }
          params:
            type: array
            items: { type: string, description: "Channel name" }
            example: [fast_tick, signals, orderbook]

    CmdUnsubscribe:
      name: unsubscribe
      title: Unsubscribe from channels
      payload:
        type: object
        required: [method, params]
        properties:
          method: { type: string, const: UNSUBSCRIBE }
          params:
            type: array
            items: { type: string }

    CmdAuth:
      name: auth
      title: Bind session (unlocks private channels)
      payload:
        type: object
        required: [method, token]
        properties:
          method: { type: string, const: AUTH }
          token:
            type: string
            description: |
              Session token (bearer) OR `sk_live_*`/`sk_test_*` API key.
              Machine partners should use the API key form.

    CmdLocale:
      name: locale
      title: Set i18n locale for outbound rendered text
      payload:
        type: object
        required: [method, locale]
        properties:
          method: { type: string, const: LOCALE }
          locale:
            type: string
            example: zh-CN
            description: "BCP-47 tag. Supported: en, zh-CN, zh-TW, id, vi, th, ja, ko, es, pt, ru, tr, ar."

    # ── Server → Client — Meta ──────────────────────────────────────────────
    EvSubscribed:
      name: subscribed
      title: Confirm channels added
      payload:
        type: object
        properties:
          type: { const: subscribed }
          channels:
            type: array
            items: { type: string }

    EvUnsubscribed:
      name: unsubscribed
      payload:
        type: object
        properties:
          type: { const: unsubscribed }
          channels:
            type: array
            items: { type: string }

    EvAuthenticated:
      name: authenticated
      title: AUTH accepted — private channels now flow
      payload:
        type: object
        properties:
          type: { const: authenticated }
          email: { type: string, format: email, description: "PII — only sent to the authenticating client" }

    EvLocaleSet:
      name: locale_set
      payload:
        type: object
        properties:
          type: { const: locale_set }
          locale: { type: string }

    EvForceLogout:
      name: force_logout
      title: Server terminates auth (session revoked / device conflict)
      payload:
        type: object
        properties:
          type: { const: force_logout }
          reason: { type: string, example: session_device_mismatch }

    # ── Server → Client — Market Data ───────────────────────────────────────
    EvFastTick:
      name: fast_tick
      title: High-frequency price tick (subsecond)
      payload:
        type: object
        properties:
          type: { const: fast_tick }
          items:
            type: array
            items:
              type: object
              properties:
                asset: { type: string, example: BTC }
                price: { $ref: '#/components/schemas/Money' }
                ts_ms: { type: integer, format: int64 }
          server_now: { type: integer, format: int64 }

    EvTicker:
      name: ticker
      title: Rolling 24h ticker snapshot (~1s)
      payload:
        type: object
        properties:
          type: { const: ticker }
          items:
            type: array
            items:
              type: object
              properties:
                asset: { type: string }
                price: { $ref: '#/components/schemas/Money' }
                change_24h_pct: { type: number }
                volume_24h: { $ref: '#/components/schemas/Money' }

    EvMarketsSnapshot:
      name: markets_snapshot
      title: Full market list refresh (paginated)
      payload:
        type: object
        properties:
          type: { const: markets_snapshot }
          markets:
            type: array
            items: { $ref: '#/components/schemas/MarketRef' }

    EvMarketsUpdated:
      name: markets_updated
      title: Incremental market delta (add/update/remove)
      payload:
        type: object
        properties:
          type: { const: markets_updated }
          added: { type: array, items: { $ref: '#/components/schemas/MarketRef' } }
          updated: { type: array, items: { $ref: '#/components/schemas/MarketRef' } }
          removed: { type: array, items: { type: string, description: market_id } }

    EvOrderbook:
      name: orderbook
      title: Orderbook incremental / snapshot
      payload:
        type: object
        properties:
          type: { const: orderbook }
          market_id: { type: string }
          outcome_id: { type: string }
          bids: { type: array, items: { type: array, items: { $ref: '#/components/schemas/Money' } } }
          asks: { type: array, items: { type: array, items: { $ref: '#/components/schemas/Money' } } }
          seq: { type: integer, description: monotonic sequence for gap detection }
          ts_ms: { type: integer, format: int64 }

    EvTradeExecuted:
      name: trade_executed
      title: Public trade tape entry
      payload:
        type: object
        properties:
          type: { const: trade_executed }
          trade_id: { type: string }
          market_id: { type: string }
          outcome_id: { type: string }
          side: { type: string, enum: [buy, sell] }
          price: { $ref: '#/components/schemas/Money' }
          size: { $ref: '#/components/schemas/Money' }
          ts_ms: { type: integer, format: int64 }

    EvWhaleTrade:
      name: whale_trade
      title: Large-notional trade highlight
      payload:
        type: object
        properties:
          type: { const: whale_trade }
          market_id: { type: string }
          notional_usd: { $ref: '#/components/schemas/Money' }
          direction: { type: string, enum: [buy, sell] }

    EvNormalMarket:
      name: normal_market
      title: Non-fast market state update
      payload: { type: object, properties: { type: { const: normal_market } } }

    EvCreatorMarketCreate:
      name: creator_market_create
      title: New creator market spawned
      payload:
        type: object
        properties:
          type: { const: creator_market_create }
          market: { $ref: '#/components/schemas/MarketRef' }

    # ── Fast Round Lifecycle ────────────────────────────────────────────────
    EvFastRoundCommit:
      name: fast_round_commit
      title: Fast round locked (pre-settlement commit)
      payload:
        type: object
        properties:
          type: { const: fast_round_commit }
          market_id: { type: string }
          round_id: { type: string }
          commit_price: { $ref: '#/components/schemas/Money' }
          committed_at_ms: { type: integer, format: int64 }

    EvFastRoundSettled:
      name: fast_round_settled
      payload:
        type: object
        properties:
          type: { const: fast_round_settled }
          market_id: { type: string }
          round_id: { type: string }
          winning_outcome_id: { type: string }
          settle_price: { $ref: '#/components/schemas/Money' }
          settlement_proof:
            type: object
            description: Deterministic proof (source, ts, sig) enabling replay verification

    EvFastSettlementStatement:
      name: fast_settlement_statement
      title: Per-user settlement result (private after AUTH)
      payload: { type: object, properties: { type: { const: fast_settlement_statement } } }

    EvFastSettlementToast:
      name: fast_settlement_toast
      title: Rendered toast text for UI (uses `LOCALE` for i18n)
      payload:
        type: object
        properties:
          type: { const: fast_settlement_toast }
          text: { type: string }

    # ── Signals & AI ────────────────────────────────────────────────────────
    EvSignal:
      name: signal
      payload: { type: object, properties: { type: { const: signal } } }

    EvSignalInsight:
      name: signal_insight
      payload: { type: object, properties: { type: { const: signal_insight } } }

    EvSignalsSnapshot:
      name: signals_snapshot
      payload: { type: object, properties: { type: { const: signals_snapshot } } }

    EvAiAnalysis:
      name: ai_analysis
      payload: { type: object, properties: { type: { const: ai_analysis } } }

    EvAiSummaryRefresh:
      name: ai_summary_refresh
      title: LLM-generated AI market summary refreshed (invalidate client cache)
      payload:
        type: object
        properties:
          type: { const: ai_summary_refresh }
          locale: { type: string }

    EvNews:
      name: news
      payload: { type: object, properties: { type: { const: news } } }

    # ── Account (private) ───────────────────────────────────────────────────
    EvAccount:
      name: account
      title: Account balance/state delta (AUTH required)
      payload:
        type: object
        properties:
          type: { const: account }
          balance_free: { $ref: '#/components/schemas/Money' }
          balance_reserved: { $ref: '#/components/schemas/Money' }

    EvDeposit:
      name: deposit
      payload: { type: object, properties: { type: { const: deposit } } }

    EvCustodyDeposit:
      name: custody_deposit
      payload: { type: object, properties: { type: { const: custody_deposit } } }

    EvCustodyDepositCredit:
      name: custody_deposit_credit
      payload: { type: object, properties: { type: { const: custody_deposit_credit } } }

    EvDepositCredit:
      name: deposit_credit
      payload: { type: object, properties: { type: { const: deposit_credit } } }

    EvWithdrawalStatusChanged:
      name: withdrawal_status_changed
      payload:
        type: object
        properties:
          type: { const: withdrawal_status_changed }
          withdrawal_id: { type: string }
          status: { type: string, enum: [pending, approved, broadcast, confirmed, failed, cancelled] }
          tx_hash: { type: string, nullable: true }

    EvQuickWithdrawal:
      name: quick_withdrawal
      payload: { type: object, properties: { type: { const: quick_withdrawal } } }

    EvLimitOrderFill:
      name: limit_order_fill
      payload:
        type: object
        properties:
          type: { const: limit_order_fill }
          order_id: { type: string }
          filled_size: { $ref: '#/components/schemas/Money' }
          fill_price: { $ref: '#/components/schemas/Money' }

    EvParlayBet:
      name: parlay_bet
      payload: { type: object, properties: { type: { const: parlay_bet } } }

    EvParlaySettled:
      name: parlay_settled
      payload: { type: object, properties: { type: { const: parlay_settled } } }

    EvReferralBonus:
      name: referral_bonus
      payload: { type: object, properties: { type: { const: referral_bonus } } }

    EvReferralEarningRealtime:
      name: referral_earning_realtime
      payload: { type: object, properties: { type: { const: referral_earning_realtime } } }

    EvAchievementUnlocked:
      name: achievement_unlocked
      payload: { type: object, properties: { type: { const: achievement_unlocked } } }

    # ── Security / Anti-abuse (private) ─────────────────────────────────────
    EvSecurityAlert:
      name: security_alert
      title: Broad security notification (unusual login, IP change, etc.)
      payload:
        type: object
        properties:
          type: { const: security_alert }
          severity: { type: string, enum: [info, warning, critical] }
          reason: { type: string }

    EvSessionDeviceMismatch:
      name: session_device_mismatch
      payload: { type: object, properties: { type: { const: session_device_mismatch } } }

    EvBonusAbuse:
      name: bonus_abuse
      payload: { type: object, properties: { type: { const: bonus_abuse } } }

    EvCreditAddrMismatch:
      name: credit_addr_mismatch
      payload: { type: object, properties: { type: { const: credit_addr_mismatch } } }

    EvCreditBurst:
      name: credit_burst
      payload: { type: object, properties: { type: { const: credit_burst } } }

    EvDepositAddrMismatch:
      name: deposit_addr_mismatch
      payload: { type: object, properties: { type: { const: deposit_addr_mismatch } } }

    # ── Ops (public transparency) ───────────────────────────────────────────
    EvCircuitBreaker:
      name: circuit_breaker
      title: Trading paused (market or asset scoped)
      payload:
        type: object
        properties:
          type: { const: circuit_breaker }
          scope: { type: string, enum: [asset, market, global] }
          scope_id: { type: string }
          reason: { type: string }

    EvCircuitBreakerCleared:
      name: circuit_breaker_cleared
      payload:
        type: object
        properties:
          type: { const: circuit_breaker_cleared }
          scope: { type: string }
          scope_id: { type: string }

    EvCacheInvalidate:
      name: cache_invalidate
      title: Server-side cache flushed — client should refetch REST
      payload:
        type: object
        properties:
          type: { const: cache_invalidate }
          keys: { type: array, items: { type: string } }

    EvStablecoinSnapshot:
      name: stablecoin_snapshot
      payload: { type: object, properties: { type: { const: stablecoin_snapshot } } }

    EvDisputeCreated:
      name: dispute_created
      payload:
        type: object
        properties:
          type: { const: dispute_created }
          market_id: { type: string }
          dispute_id: { type: string }

    EvLpPoolNegative:
      name: lp_pool_negative
      payload: { type: object, properties: { type: { const: lp_pool_negative } } }

    EvLpWithdrawalProcessed:
      name: lp_withdrawal_processed
      payload: { type: object, properties: { type: { const: lp_withdrawal_processed } } }

    EvFundDepleted:
      name: fund_depleted
      payload: { type: object, properties: { type: { const: fund_depleted } } }

    # ── Admin (server-filters to admin sessions only) ───────────────────────
    EvAdminAlert:
      name: admin_alert
      payload:
        type: object
        properties:
          type: { const: admin_alert }
          severity: { type: string }
          op: { type: string }
          err: { type: string }

    EvAdminAction:
      name: admin_action
      payload: { type: object, properties: { type: { const: admin_action } } }

    EvAutoscale:
      name: autoscale
      title: Cluster scaled up/down (worker count changed)
      payload:
        type: object
        properties:
          type: { const: autoscale }
          action: { type: string, enum: [scale_up, scale_down] }
          before: { type: integer }
          after: { type: integer }

  # ── Shared schemas ────────────────────────────────────────────────────────
  schemas:
    Money:
      type: string
      pattern: '^-?[0-9]+(\.[0-9]{1,18})?$'
      description: |
        Decimal string (never a JSON number) — preserves precision for USDT (6 dp),
        BTC (8 dp), ETH (18 dp).
      example: "1000.234567"

    MarketRef:
      type: object
      description: Compact market reference (full detail via REST /v1/markets/{id})
      properties:
        market_id: { type: string, example: m_a1b2c3d4e5f6 }
        title: { type: string }
        status: { type: string, enum: [scheduled, active, locked, settling, resolved, cancelled] }
        outcomes:
          type: array
          items:
            type: object
            properties:
              id: { type: string }
              price: { $ref: '#/components/schemas/Money' }
