openapi: 3.1.0
info:
  title: Falcon AI — Polymarket API
  version: "0.3.5"
  description: |
    Real-time and historical Polymarket data API.

    **Base URL (test):** `https://api-test.falconai.bot/api/v1/external/polymarket`
    **Base URL (production):** `https://api.falconai.bot/api/v1/external/polymarket`

    ## Authentication
    All endpoints require an API key in the `Authorization` header:
    ```
    Authorization: Bearer aif_your_api_key_here
    ```

    API keys are tiered — your key's tier determines which endpoints you can call:
    | Tier | Endpoints |
    |------|-----------|
    | 1 | `/health`, `/usage`, `/pricing/access`, `/events`, `/series`, `/markets`, `/markets/:id`, `/markets/:id/orderbook`, `/markets/:id/price/ticks`, `/fees/*` |
    | 2 | Tier 1 + `/trades`, `/traders/:wallet`, `/traders/:wallet/stats`, `/leaderboard`, `/copy-picks`, `/markets/:id/orderbook/history`, `/backtest` |
    | 3 | Tier 2 + `/positions/:wallet` |

    ## Response envelope
    All responses use the standard envelope:
    ```json
    {
      "success": true,
      "data": { ... },
      "pagination": { "limit": 50, "has_more": true, "next_cursor": "opaque" },
      "access_mode": "ztr_tier" | "credits",
      "error": null,
      "code": null
    }
    ```

    ## Pagination
    Use `cursor`-based pagination. Pass the `next_cursor` from the previous response:
    ```
    GET /markets?limit=50
    GET /markets?limit=50&cursor=<next_cursor from previous response>
    ```
    When `has_more` is `false`, you have reached the last page.

    ## Timestamps
    All timestamps are ISO 8601 UTC, e.g. `2026-03-31T12:00:00.000Z`.

    ## In-app trading (different API)
    **Browser / Telegram Mini App** Polymarket orders use a **Falcon JWT** on **`/api/polymarket/trading`** (not this `aif_` key). Open **`POLYMARKET-TRADING-INTERNAL-OPENAPI.yaml`** in the same `docs/` folder for that contract (`/orders`, `/wallet`, `/execute`, etc.) and for **`POST /api/auth/refresh`** (JWT rotation, including expired access tokens within grace).

servers:
  - url: https://api-test.falconai.bot/api/v1/external/polymarket
    description: Test server
  - url: https://api.falconai.bot/api/v1/external/polymarket
    description: Production server

security:
  - ApiKeyAuth: []

components:
  securitySchemes:
    ApiKeyAuth:
      type: http
      scheme: bearer
      bearerFormat: API Key (aif_...)
      description: >
        API key issued by Falcon AI. Pass as `Authorization: Bearer aif_...`.

  schemas:
    BacktestRule:
      type: object
      required: [field, op, value]
      properties:
        field:
          type: string
          enum: [mid_price, best_bid, best_ask, spread_bps, total_bid_depth, total_ask_depth]
        op:
          type: string
          enum: [gt, lt, gte, lte, eq]
        value:
          type: number

    # ── Standard envelope ────────────────────────────────────────────────────
    SuccessEnvelope:
      type: object
      required: [success, data]
      properties:
        success:
          type: boolean
          example: true
        data:
          description: The payload (object or array depending on endpoint)
        pagination:
          $ref: '#/components/schemas/Pagination'
        access_mode:
          type: string
          enum: [ztr_tier, credits, free]
          description: How this request was authorised/billed
        error:
          type: string
          nullable: true
          example: null
        code:
          type: string
          nullable: true
          example: null

    ErrorEnvelope:
      type: object
      required: [success, error, code]
      properties:
        success:
          type: boolean
          example: false
        data:
          nullable: true
          example: null
        error:
          type: string
          example: Market not found
        code:
          type: string
          enum:
            - BAD_REQUEST
            - UNAUTHORIZED
            - FORBIDDEN
            - NOT_FOUND
            - CONFLICT
            - PAYMENT_REQUIRED
            - INTERNAL_ERROR

    Pagination:
      type: object
      properties:
        limit:
          type: integer
          example: 50
        has_more:
          type: boolean
          example: true
        next_cursor:
          type: string
          nullable: true
          description: Opaque base64url token. Pass as `cursor` param on the next request.
          example: "eyJ0cyI6IjIwMjYtMDMtMzFUMTI6MDA6MDAuMDAwWiIsImlkIjoiMHhhYmMifQ"

    # ── Market ───────────────────────────────────────────────────────────────
    MarketSummary:
      type: object
      properties:
        market_id:
          type: string
          description: Polymarket conditionId (0x hex, lowercase)
          example: "0x1b673458d10556f596935189946770..."
        slug:
          type: string
          example: "will-trump-visit-china-by-march-31"
        title:
          type: string
          example: "Will Trump visit China by March 31?"
        category:
          type: string
          nullable: true
          example: "Trump · Presidential..."
        status:
          type: string
          enum: [active, resolved, closed]
          example: "active"
        end_time:
          type: string
          format: date-time
          nullable: true
          example: "2026-03-31T23:59:00.000Z"
        yes_price:
          type: number
          format: float
          nullable: true
          description: Latest YES outcome mid-price (0–1)
          example: 0.82
        no_price:
          type: number
          format: float
          nullable: true
          description: Latest NO outcome mid-price (0–1)
          example: 0.18
        volume_24h:
          type: number
          format: float
          nullable: true
          description: 24-hour trading volume in USDC
          example: 73635.50
        liquidity:
          type: number
          format: float
          nullable: true
          description: Total liquidity in USDC
          example: 250000.00
        snapshot_ts:
          type: string
          format: date-time
          nullable: true
          description: Timestamp of the latest snapshot

    MarketDetail:
      allOf:
        - $ref: '#/components/schemas/MarketSummary'
        - type: object
          properties:
            source_created_at:
              type: string
              format: date-time
              nullable: true
            source_updated_at:
              type: string
              format: date-time
              nullable: true
            updated_at:
              type: string
              format: date-time
            latest_snapshot:
              nullable: true
              type: object
              properties:
                yes_price:
                  type: number
                  nullable: true
                no_price:
                  type: number
                  nullable: true
                volume_24h:
                  type: number
                  nullable: true
                liquidity:
                  type: number
                  nullable: true
                spread_bps:
                  type: integer
                  nullable: true
                  description: Bid-ask spread in basis points
                ts:
                  type: string
                  format: date-time

    # ── Trade ────────────────────────────────────────────────────────────────
    Trade:
      type: object
      properties:
        trade_id:
          type: string
          description: Unique trade identifier (derived from tx hash)
          example: "0xabc123def...00"
        market_id:
          type: string
          example: "0x1b673458..."
        wallet:
          type: string
          description: Trader wallet address (lowercase)
          example: "0x1b673458d10556f596935189946770..."
        side:
          type: string
          enum: [buy, sell]
          example: "buy"
        outcome:
          type: string
          nullable: true
          description: Outcome name (e.g. "Yes", "No", "Up", "Down")
          example: "Yes"
        price:
          type: number
          format: float
          description: Fill price (0–1)
          example: 0.82
        size_usdc:
          type: number
          format: float
          description: Trade size in USDC
          example: 73635.50
        shares:
          type: number
          format: float
          description: Number of shares traded
          example: 89799.39
        executed_at:
          type: string
          format: date-time
          example: "2026-03-31T12:00:00.000Z"

    # ── Trader ───────────────────────────────────────────────────────────────
    TraderProfile:
      type: object
      properties:
        wallet:
          type: string
          example: "0x1b673458..."
        stats:
          type: object
          properties:
            trades_30d:
              type: integer
              example: 63
            avg_position_size_30d:
              type: number
              example: 27.54
            total_notional_30d:
              type: number
              example: 1735.02
        latest_trade_at:
          type: string
          format: date-time
          nullable: true

    # ── Position ─────────────────────────────────────────────────────────────
    WalletPosition:
      type: object
      properties:
        wallet:
          type: string
          example: "0x1b673458..."
        market_id:
          type: string
          example: "0xabc..."
        outcome:
          type: string
          example: "Yes"
        shares:
          type: number
          example: 1200.5
        avg_entry_price:
          type: number
          nullable: true
          example: 0.41
        mark_price:
          type: number
          nullable: true
          example: 0.47
        unrealized_pnl:
          type: number
          nullable: true
          example: 72.03
        as_of:
          type: string
          format: date-time

    # ── Fee ledger ────────────────────────────────────────────────────────────
    FeeQuoteResponse:
      type: object
      properties:
        quote_id:
          type: integer
          example: 42
        notional_usdc:
          type: number
          example: 1000.00
        fee_bps:
          type: integer
          example: 50
        fee_usdc:
          type: number
          example: 5.00
        payment_asset:
          type: string
          example: "credits"
        payment_amount:
          type: number
          example: 50.00
        all_options:
          type: object
          description: Payment options for all supported assets (credits, ztr, usdc, sol)

    SignalChannel:
      type: string
      description: Canonical signal feed channel (maps from Polymarket Gamma tags)
      enum:
        - weather
        - politics
        - sports
        - crypto
        - finance
        - economics
        - tech
        - culture
        - mentions
        - all

    Signal:
      type: object
      properties:
        signal_id:           { type: string }
        signal_type:         { type: string, enum: [whale_copy, price_signal, sentiment_signal] }
        category:            { $ref: '#/components/schemas/SignalChannel' }
        market_id:           { type: string }
        market_title:        { type: string, nullable: true }
        side:                { type: string, enum: [YES, NO] }
        entry_price:         { type: number, format: float, minimum: 0, maximum: 1 }
        true_prob:           { type: number, format: float }
        edge:                { type: number, format: float }
        confidence:          { type: number, format: float }
        suggested_size_pct:  { type: number, format: float }
        clients_sent:        { type: integer }
        expires_at:          { type: string, format: date-time }
        created_at:          { type: string, format: date-time }

  responses:
    BadRequest:
      description: Invalid request parameters
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            success: false
            error: "Invalid wallet format"
            code: "BAD_REQUEST"
    Unauthorized:
      description: Missing or invalid API key
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            success: false
            error: "Invalid or missing API key"
            code: "UNAUTHORIZED"
    Forbidden:
      description: API key tier insufficient for this endpoint
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
          example:
            success: false
            error: "This endpoint requires Tier 2 access"
            code: "FORBIDDEN"
    NotFound:
      description: Resource not found
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'
    PaymentRequired:
      description: Insufficient credits
      content:
        application/json:
          schema:
            $ref: '#/components/schemas/ErrorEnvelope'

# ── Endpoints ──────────────────────────────────────────────────────────────────

paths:

  /health:
    get:
      summary: API health check
      description: Returns service status. No tier requirement — any valid API key.
      operationId: getHealth
      tags: [System]
      responses:
        "200":
          description: Service is healthy
          content:
            application/json:
              example:
                success: true
                data:
                  service: "external-polymarket-api"
                  status: "ok"
                  version: "0.3.0"

  /usage:
    get:
      summary: API and webhook usage (Tier 1)
      description: Returns monthly request quota consumption and webhook delivery counts for the authenticated API key.
      operationId: getUsage
      tags: [System]
      responses:
        "200":
          description: Usage snapshot
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'

  /leaderboard:
    get:
      summary: Trader leaderboard (Tier 2)
      description: Risk-adjusted trader rankings from the polymarket_trader_pnl materialized view.
      operationId: getLeaderboard
      tags: [Traders]
      parameters:
        - name: sort_by
          in: query
          schema:
            type: string
            enum: [total_realized_pnl, win_rate_pct, total_trades, consistency_score, profit_factor]
            default: total_realized_pnl
        - name: risk
          in: query
          schema:
            type: boolean
            default: false
          description: When true, include profit_factor and extended risk metrics.
        - name: min_trades
          in: query
          schema:
            type: integer
            default: 5
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
      responses:
        "200":
          description: Leaderboard rows
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'

  /copy-picks:
    get:
      summary: Topic copy picks (Tier 2)
      description: Curated wallets with recent realized PnL in a topic category.
      operationId: getCopyPicks
      tags: [Traders]
      parameters:
        - name: topic
          in: query
          schema:
            type: string
            default: politics
        - name: sort_by
          in: query
          schema:
            type: string
            enum: [recent_pnl, consistency]
            default: recent_pnl
        - name: limit
          in: query
          schema:
            type: integer
            default: 10
            maximum: 25
      responses:
        "200":
          description: Copy pick list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'

  /pricing/access:
    get:
      summary: Current tier and credit costs
      description: Returns the caller's current ZTR tier, balance, and credit costs per tier.
      operationId: getPricingAccess
      tags: [System]
      responses:
        "200":
          description: Tier and pricing info
          content:
            application/json:
              example:
                success: true
                data:
                  current_tier: 2
                  ztr_balance: 1500.00
                  analytics_credit_costs:
                    tier1: 3
                    tier2: 8
                    tier3: 15
                  bot_fee:
                    fee_bps: 50
                    fee_percent: 0.5
                    payable_in: ["credits", "ztr", "usdc", "sol"]

  # ── Discovery (series → events → markets) ─────────────────────────────────

  /events:
    get:
      summary: List events (Tier 1)
      description: Browse indexed parent events grouped from Gamma during ingestion.
      tags: [Discovery]
      parameters:
        - name: status
          in: query
          schema: { type: string }
        - name: category
          in: query
          schema: { type: string }
        - name: series_slug
          in: query
          schema: { type: string }
        - name: search
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 50, minimum: 1, maximum: 200 }
        - name: cursor
          in: query
          schema: { type: string }
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Paginated events list

  /events/{slugOrId}:
    get:
      summary: Event detail with markets (Tier 1)
      tags: [Discovery]
      parameters:
        - name: slugOrId
          in: path
          required: true
          schema: { type: string }
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Event with nested markets
        "404":
          $ref: '#/components/responses/NotFound'

  /series:
    get:
      summary: List series (Tier 1)
      tags: [Discovery]
      parameters:
        - name: status
          in: query
          schema: { type: string }
        - name: category
          in: query
          schema: { type: string }
        - name: search
          in: query
          schema: { type: string }
        - name: limit
          in: query
          schema: { type: integer, default: 50, minimum: 1, maximum: 200 }
        - name: cursor
          in: query
          schema: { type: string }
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Paginated series list

  /series/{slugOrId}:
    get:
      summary: Series detail with events (Tier 1)
      tags: [Discovery]
      parameters:
        - name: slugOrId
          in: path
          required: true
          schema: { type: string }
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Series with child events
        "404":
          $ref: '#/components/responses/NotFound'

  # ── Markets ────────────────────────────────────────────────────────────────

  /markets:
    get:
      summary: List markets (Tier 1)
      description: |
        Returns a paginated list of Polymarket markets with their latest prices and volume.
        Use the `next_cursor` field to fetch the next page.
      operationId: listMarkets
      tags: [Markets]
      parameters:
        - name: status
          in: query
          schema:
            type: string
            enum: [active, resolved, closed]
          description: Filter by market status
          example: active
        - name: category
          in: query
          schema:
            type: string
          description: Filter by category (case-insensitive partial match)
          example: "Sports"
        - name: event_slug
          in: query
          schema:
            type: string
          description: Filter markets under a parent event slug
        - name: search
          in: query
          schema:
            type: string
          description: Search market title or slug
          example: "Trump"
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 200
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor from previous response's `pagination.next_cursor`
      responses:
        "200":
          description: List of markets
          headers:
            X-Cache:
              schema:
                type: string
                enum: [HIT, MISS]
              description: Whether this response was served from Redis cache
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/MarketSummary'
              example:
                success: true
                data:
                  - market_id: "0x1b673458d10556f596935189946770..."
                    title: "Will Trump visit China by March 31?"
                    category: "Trump · Presidential..."
                    status: "active"
                    yes_price: 0.82
                    no_price: 0.18
                    volume_24h: 73635.50
                    end_time: "2026-03-31T23:59:00.000Z"
                pagination:
                  limit: 50
                  has_more: true
                  next_cursor: "eyJ0cyI6IjIwMjYt..."
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'

  /markets/{marketId}:
    get:
      summary: Get market detail (Tier 1)
      description: Returns full market metadata plus the latest price/volume snapshot.
      operationId: getMarket
      tags: [Markets]
      parameters:
        - name: marketId
          in: path
          required: true
          schema:
            type: string
          description: Polymarket conditionId (0x hex)
          example: "0x1b673458d10556f596935189946770..."
      responses:
        "200":
          description: Market detail
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/MarketDetail'
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "404":
          $ref: '#/components/responses/NotFound'

  /markets/{marketId}/orderbook:
    get:
      summary: Live L2 order book (Tier 1)
      description: Fetches current YES/NO bid-ask depth from the Polymarket CLOB for a market.
      operationId: getMarketOrderbook
      tags: [Markets]
      parameters:
        - name: marketId
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Live order book snapshot
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'
        "404":
          $ref: '#/components/responses/NotFound'

  /markets/{marketId}/price/ticks:
    get:
      summary: CLOB WebSocket price ticks (Tier 1)
      description: |
        Sub-minute price ticks from the CLOB market WebSocket channel.
        Includes best bid/ask, mid, and last trade price (~5s write interval).
      tags: [Markets]
      parameters:
        - name: marketId
          in: path
          required: true
          schema: { type: string }
        - name: outcome
          in: query
          schema: { type: string, enum: [YES, NO], default: YES }
        - name: hours
          in: query
          schema: { type: integer, default: 1, minimum: 1, maximum: 72 }
        - name: limit
          in: query
          schema: { type: integer, default: 500, minimum: 1, maximum: 2000 }
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Tick rows ordered by timestamp ascending
        "404":
          $ref: '#/components/responses/NotFound'

  # ── Trades ─────────────────────────────────────────────────────────────────

  /trades:
    get:
      summary: List trades (Tier 2)
      description: |
        Returns a paginated feed of trade events. Can be filtered by market, wallet, side, or date range.
        Most recent trades are returned first.
      operationId: listTrades
      tags: [Trades]
      parameters:
        - name: market_id
          in: query
          schema:
            type: string
          description: Filter by market conditionId
        - name: wallet
          in: query
          schema:
            type: string
          description: Filter by trader wallet address (must be valid 0x Ethereum address)
          example: "0x1b673458d10556f596935189946770..."
        - name: side
          in: query
          schema:
            type: string
            enum: [buy, sell]
        - name: start_time
          in: query
          schema:
            type: string
            format: date-time
          description: Only return trades at or after this time (ISO 8601)
          example: "2026-03-01T00:00:00Z"
        - name: end_time
          in: query
          schema:
            type: string
            format: date-time
          description: Only return trades at or before this time (ISO 8601)
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            minimum: 1
            maximum: 200
        - name: cursor
          in: query
          schema:
            type: string
          description: Pagination cursor
      responses:
        "200":
          description: List of trade events
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/Trade'
              example:
                success: true
                data:
                  - trade_id: "0xabc123..."
                    market_id: "0x1b673458..."
                    wallet: "0x1b673458..."
                    side: "buy"
                    outcome: "Yes"
                    price: 0.82
                    size_usdc: 73635.50
                    shares: 89799.39
                    executed_at: "2026-03-31T12:00:00.000Z"
                pagination:
                  limit: 50
                  has_more: true
                  next_cursor: "eyJ0cyI6IjIwMjYt..."
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'

  # ── Traders ────────────────────────────────────────────────────────────────

  /traders/{wallet}:
    get:
      summary: Trader profile (Tier 2)
      description: Returns 30-day trading statistics for a given wallet.
      operationId: getTrader
      tags: [Traders]
      parameters:
        - name: wallet
          in: path
          required: true
          schema:
            type: string
          description: Ethereum wallet address (0x format)
          example: "0x1b673458d10556f596935189946770..."
      responses:
        "200":
          description: Trader statistics
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/TraderProfile'
              example:
                success: true
                data:
                  wallet: "0x1b673458..."
                  stats:
                    trades_30d: 63
                    avg_position_size_30d: 27.54
                    total_notional_30d: 1735.02
                  latest_trade_at: "2026-03-31T13:15:37.000Z"
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'

  /traders/{wallet}/stats:
    get:
      summary: Full trader analytics (Tier 2)
      description: Lifetime PnL, win rate, category breakdown, and recent trades for a wallet.
      operationId: getTraderStats
      tags: [Traders]
      parameters:
        - name: wallet
          in: path
          required: true
          schema:
            type: string
      responses:
        "200":
          description: Extended trader profile
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'
        "404":
          $ref: '#/components/responses/NotFound'

  # ── Positions ──────────────────────────────────────────────────────────────

  /positions/{wallet}:
    get:
      summary: Wallet positions (Tier 3)
      description: |
        Returns the latest open positions for a wallet.
        **Note:** Position data requires the positions snapshot job (B4) to be running.
        Returns an empty array if the job has not run yet.
      operationId: getPositions
      tags: [Traders]
      parameters:
        - name: wallet
          in: path
          required: true
          schema:
            type: string
          example: "0x1b673458d10556f596935189946770..."
        - name: limit
          in: query
          schema:
            type: integer
            default: 50
            maximum: 200
        - name: offset
          in: query
          schema:
            type: integer
            default: 0
      responses:
        "200":
          description: Open wallet positions
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: '#/components/schemas/WalletPosition'
        "400":
          $ref: '#/components/responses/BadRequest'
        "401":
          $ref: '#/components/responses/Unauthorized'
        "403":
          $ref: '#/components/responses/Forbidden'

  # ── Fees ───────────────────────────────────────────────────────────────────

  /fees/quote:
    post:
      summary: Create fee quote (Tier 1)
      description: |
        Calculate and lock in a fee quote for a trade of a given notional size.
        Returns a `quote_id` that can be used to charge credits or confirm on-chain payment.
      operationId: createFeeQuote
      tags: [Fees]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filled_notional_usdc]
              properties:
                filled_notional_usdc:
                  type: number
                  description: Trade notional size in USDC
                  example: 1000.00
                payment_asset:
                  type: string
                  enum: [credits, ztr, usdc, sol]
                  default: credits
                  example: "credits"
      responses:
        "201":
          description: Fee quote created
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        $ref: '#/components/schemas/FeeQuoteResponse'
        "400":
          $ref: '#/components/responses/BadRequest'

  /fees/{id}:
    get:
      summary: Get fee ledger entry (Tier 1)
      operationId: getFee
      tags: [Fees]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
      responses:
        "200":
          description: Fee ledger entry
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'
        "404":
          $ref: '#/components/responses/NotFound'

  /fees/{id}/charge-credits:
    post:
      summary: Charge credits for fee (Tier 1)
      description: Deducts credits from the caller's balance to pay the quoted fee.
      operationId: chargeFeeCredits
      tags: [Fees]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
          example: 42
      responses:
        "200":
          description: Credits charged successfully
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'
        "402":
          $ref: '#/components/responses/PaymentRequired'
        "404":
          $ref: '#/components/responses/NotFound'
        "409":
          description: Fee already charged or in invalid state

  /fees/{id}/confirm-onchain:
    post:
      summary: Confirm on-chain fee payment (Tier 1)
      description: Submit a Polygon transaction signature to confirm an on-chain USDC/SOL fee payment.
      operationId: confirmFeeOnchain
      tags: [Fees]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [signature]
              properties:
                signature:
                  type: string
                  description: Transaction hash / signature from the on-chain payment
                  example: "0xdeadbeef..."
      responses:
        "200":
          description: On-chain payment confirmed
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'

  /fees/{id}/finalize-fill:
    post:
      summary: Finalize fee after fill (Tier 1)
      description: Mark a fee ledger entry as finalized once the actual fill notional is known.
      operationId: finalizeFee
      tags: [Fees]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [filled_notional_usdc]
              properties:
                filled_notional_usdc:
                  type: number
                  example: 950.00
                payment_asset:
                  type: string
                  example: "credits"
      responses:
        "200":
          description: Fee finalized
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'

  /fees/{id}/settlements:
    get:
      summary: List fee settlements (Tier 1)
      description: Returns all confirmed settlement records for a fee ledger entry.
      operationId: listFeeSettlements
      tags: [Fees]
      parameters:
        - name: id
          in: path
          required: true
          schema:
            type: integer
      responses:
        "200":
          description: Settlement records
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'
        "404":
          $ref: '#/components/responses/NotFound'

  /backtest:
    post:
      summary: Run a strategy backtest
      description: |
        Runs a simple rule-based strategy over stored order book / market snapshots
        and returns an equity curve, trade list, and summary statistics.

        **Entry rules** — ALL must match at the same snapshot for the engine to enter.
        **Exit rules**  — ANY that matches triggers an exit.

        **Available fields:**
        | Field | Description |
        |-------|-------------|
        | `mid_price` | Mid-price of the token (0–1, e.g. 0.35 = 35¢) |
        | `best_bid` | Best bid price |
        | `best_ask` | Best ask price |
        | `spread_bps` | Bid-ask spread in basis points |
        | `total_bid_depth` | Sum of top-20 bid sizes |
        | `total_ask_depth` | Sum of top-20 ask sizes |
      tags: [Markets]
      security:
        - ApiKeyAuth: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [market_id, entry_rules, exit_rules]
              properties:
                market_id:
                  type: string
                  description: Market condition ID
                outcome:
                  type: string
                  enum: [YES, NO]
                  default: YES
                from:
                  type: string
                  format: date-time
                  description: Window start (default 7 days ago)
                to:
                  type: string
                  format: date-time
                  description: Window end (default now)
                entry_rules:
                  type: array
                  minItems: 1
                  items:
                    $ref: '#/components/schemas/BacktestRule'
                exit_rules:
                  type: array
                  minItems: 1
                  items:
                    $ref: '#/components/schemas/BacktestRule'
                position_size_usdc:
                  type: number
                  default: 100
                  description: Notional USDC per trade
                max_trades:
                  type: integer
                  default: 200
            example:
              market_id: "0x1234..."
              outcome: "YES"
              from: "2026-03-01T00:00:00Z"
              to: "2026-03-31T23:59:59Z"
              entry_rules:
                - { field: "mid_price", op: "lt", value: 0.3 }
              exit_rules:
                - { field: "mid_price", op: "gte", value: 0.6 }
              position_size_usdc: 100
      responses:
        "200":
          description: Backtest results
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          summary:
                            type: object
                            properties:
                              total_trades:   { type: integer }
                              wins:           { type: integer }
                              losses:         { type: integer }
                              total_pnl:      { type: number }
                              win_rate_pct:   { type: number }
                              max_drawdown:   { type: number }
                              sharpe_approx:  { type: number, nullable: true }
                          equity_curve:
                            type: array
                            items:
                              type: object
                              properties:
                                ts:     { type: string, format: date-time }
                                equity: { type: number }
                          trades:
                            type: array
                            items:
                              type: object
                              properties:
                                entry_ts:    { type: string, format: date-time }
                                exit_ts:     { type: string, format: date-time }
                                entry_price: { type: number }
                                exit_price:  { type: number }
                                pnl:         { type: number }
        "400":
          $ref: '#/components/responses/BadRequest'
        "404":
          $ref: '#/components/responses/NotFound'

  /markets/{marketId}/orderbook/history:
    get:
      summary: Order book spread history
      description: |
        Returns stored order book snapshots for a given market and outcome.
        Snapshots are captured on each markets ingestion cycle (~15 min).

        **Tiered lookback:** Tier 2 keys may request up to 7 days (168 h); Tier 3 up to 30 days (720 h).
        **Tiered storage:** L2 bid/ask levels (`include_levels=true`) are returned only for the most recent ~72 h;
        older rows retain spread/mid/depth summary at hourly resolution after the first 3 days.
      tags: [Markets]
      parameters:
        - name: marketId
          in: path
          required: true
          schema:
            type: string
        - name: outcome
          in: query
          schema:
            type: string
            enum: [YES, NO]
            default: YES
        - name: hours
          in: query
          description: Lookback window in hours (1–168 for tier 2, up to 720 for tier 3; default 24)
          schema:
            type: integer
            minimum: 1
            maximum: 720
            default: 24
        - name: limit
          in: query
          description: Max rows returned (1–2000, default 288)
          schema:
            type: integer
            minimum: 1
            maximum: 2000
            default: 288
        - name: include_levels
          in: query
          description: When true, include stored L2 bids/asks where retained (recent ~72 h only)
          schema:
            type: boolean
            default: false
      security:
        - ApiKeyAuth: []
      responses:
        "200":
          description: Spread history rows ordered by timestamp ascending
          content:
            application/json:
              schema:
                allOf:
                  - $ref: '#/components/schemas/SuccessEnvelope'
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          type: object
                          properties:
                            ts:              { type: string, format: date-time }
                            best_bid:        { type: number }
                            best_ask:        { type: number }
                            spread:          { type: number }
                            spread_bps:      { type: integer }
                            mid_price:       { type: number }
                            total_bid_depth: { type: number }
                            total_ask_depth: { type: number }
                            bids:
                              type: array
                              description: Present when include_levels=true and L2 retained for this row
                              items:
                                type: object
                                properties:
                                  price: { type: number }
                                  size:  { type: number }
                            asks:
                              type: array
                              description: Present when include_levels=true and L2 retained for this row
                              items:
                                type: object
                                properties:
                                  price: { type: number }
                                  size:  { type: number }
                      meta:
                        type: object
                        properties:
                          market_id:      { type: string }
                          outcome:        { type: string }
                          hours:          { type: integer }
                          max_hours:      { type: integer }
                          levels_max_hours: { type: integer }
                          count:          { type: integer }
                          retention_note: { type: string }
                          levels_note:    { type: string }
        "404":
          $ref: '#/components/responses/NotFound'

  /signals/recent:
    get:
      summary: Recent broadcast signals
      description: |
        Returns the last N trading signals broadcast via WebSocket to connected Falcon AI Agent clients.
        Each signal has been pre-filtered by the server-side Kelly engine (min edge 5%, min confidence 55%).
      tags: [Signals]
      security:
        - ApiKeyAuth: []
      parameters:
        - name: limit
          in: query
          schema: { type: integer, minimum: 1, maximum: 200, default: 50 }
        - name: category
          in: query
          schema:
            $ref: '#/components/schemas/SignalChannel'
      responses:
        "200":
          description: List of recent signals
          content:
            application/json:
              schema:
                type: object
                properties:
                  signals:
                    type: array
                    items:
                      $ref: '#/components/schemas/Signal'
                  count: { type: integer }
        "401":
          $ref: '#/components/responses/Unauthorized'

  /signals/stats:
    get:
      summary: Signal feed + community performance stats
      description: |
        Returns aggregate stats for the signal broadcaster and opt-in community dry-run performance reports.
        Does not require authentication.
      tags: [Signals]
      responses:
        "200":
          description: Stats object
          content:
            application/json:
              schema:
                type: object
                properties:
                  live_connections:
                    type: integer
                    description: Number of Falcon AI Agent clients currently connected
                  broadcast:
                    type: object
                    properties:
                      total_signals_30d: { type: string }
                      signals_7d:        { type: string }
                      avg_confidence:    { type: string }
                      avg_edge:          { type: string }
                      total_deliveries:  { type: string }
                  community:
                    type: object
                    properties:
                      unique_clients:  { type: string }
                      avg_roi_pct:     { type: string }
                      avg_win_rate:    { type: string }
                      total_trades:    { type: string }
                      last_report_at:  { type: string, format: date-time, nullable: true }

  /webhooks:
    post:
      summary: Create webhook subscription
      description: Register an HTTPS endpoint to receive signed Falcon Polymarket events. Base path is mounted at `/api/v1/external/polymarket/webhooks`.
      tags: [Webhooks]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [url, events]
              properties:
                url: { type: string, format: uri }
                events:
                  type: array
                  items:
                    type: string
                    enum: [trade.executed, market.resolved, market.price_signal, market.volume_spike, trader.large_trade, copy.executed, risk.blocked]
                label: { type: string }
                market_filter:
                  type: array
                  items: { type: string }
                min_trade_size: { type: number, default: 0 }
      responses:
        "201":
          description: Webhook created (secret returned once)
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'
    get:
      summary: List webhook subscriptions
      tags: [Webhooks]
      responses:
        "200":
          description: Webhook list
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'

  /webhooks/events:
    get:
      summary: Supported webhook event types
      tags: [Webhooks]
      security: []
      responses:
        "200":
          description: Event catalog
          content:
            application/json:
              schema:
                $ref: '#/components/schemas/SuccessEnvelope'
