openapi: 3.0.3
info:
  title: Embed Smart Money Signals API
  version: 1.0.0
  description: |
    Real-time smart money intelligence for crypto platforms. The API provides three capabilities:

    1. **Leaderboard** — ranked smart money wallets across Base, Ethereum, Solana, and Polymarket, scored by a composite trader metric blending PnL efficiency, win rate, consistency, and activity.
    2. **Signals** — real-time notifications when top-ranked wallets exhibit FOMO-worthy patterns (convergence buys, whale moves, sell signals).
    3. **Configuration** — per-customer controls for signal sensitivity, quality gates, token filters, and delivery preferences.

    ## Authentication
    All endpoints require a Bearer token in the `Authorization` header. Contact us to obtain your API key.

    ## Rate Limits
    No per-endpoint rate limits are enforced. Signal delivery is governed by your customer configuration (`daily_budget`, `per_type_limits`, `cooldown_hours`).

    ## Chains Supported
    - **Base** — Coinbase L2, growing DEX activity
    - **Ethereum** — most established DeFi, highest liquidity
    - **Solana** — highest memecoin activity
  contact:
    name: Embed Support
    url: https://alpha.getembed.ai/faq.html

servers:
  - url: https://api.mbd.xyz/v3/alpha
    description: Production

security:
  - BearerAuth: []

paths:
  /leaderboard:
    get:
      operationId: getLeaderboard
      summary: Get ranked smart money wallets
      description: |
        Returns top wallets sorted by the chosen metric. The default `trader_score` ranking uses a composite formula:
        - PnL efficiency (40%) — net PnL / buy volume
        - Adjusted win rate (30%) — Bayesian-smoothed
        - Position consistency (20%) — closed position count
        - Activity (10%) — log-normalized trade count

        The `trader_score` field is always included in each wallet object regardless of sort selection.
      parameters:
        - name: rank_by
          in: query
          schema:
            type: string
            enum:
              - trader_score
              - realized_pnl_usd
              - true_win_rate
              - net_pnl_usd
              - total_buy_volume_usd
              - trade_count
              - tokens_traded
              - win_rate
              - avg_trade_size_usd
              - largest_trade_usd
            default: trader_score
          description: Sort metric for ranking wallets.
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 200
            default: 50
          description: Number of wallets to return.
        - name: chain
          in: query
          schema:
            type: string
            enum: [base, ethereum, solana, polymarket]
            default: base
          description: Blockchain to query. Each chain has its own wallet pool and filter defaults.
        - name: min_trades
          in: query
          schema:
            type: integer
            minimum: 0
            default: 1
          description: Minimum trade count filter. Increase to focus on experienced traders.
        - name: min_tokens
          in: query
          schema:
            type: integer
            minimum: 0
            default: 5
          description: Minimum unique tokens traded. Filters market-maker bots that trade a single pair.
        - name: min_closed
          in: query
          schema:
            type: integer
            minimum: 0
            default: 1
          description: Minimum closed positions. Wallets need closed trades for reliable PnL and win-rate data.
        - name: max_buy_vol
          in: query
          schema:
            type: number
            default: 1000000000
          description: Maximum buy volume (USD). Filters out aggregators and bots with unrealistic volume.
      responses:
        "200":
          description: Leaderboard results
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/LeaderboardResponse"
              example:
                wallets:
                  - rank: 1
                    wallet_address: "0x7a3f...9e2d"
                    chain: base
                    trader_score: 0.6821
                    total_buy_volume_usd: 284500.00
                    total_sell_volume_usd: 312800.00
                    net_pnl_usd: 142000.50
                    realized_pnl_usd: 98400.00
                    trade_count: 187
                    buy_count: 95
                    sell_count: 92
                    win_rate: 0.6800
                    true_win_rate: 0.7200
                    winning_positions: 18
                    closed_positions: 25
                    tokens_traded: 42
                    avg_trade_size_usd: 3250.00
                    largest_trade_usd: 28400.00
                    last_trade_time: "2026-03-23T14:22:00Z"
                    first_trade_time: "2026-01-15T09:10:00Z"
                total: 1842
                rank_by: trader_score
                chain: base
                chain_defaults:
                  min_trades: 1
                  min_tokens: 5
                  min_closed: 1
                  max_buy_vol: 1000000000
                updated_at: "2026-03-23T15:00:00Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /notifications:
    get:
      operationId: getNotifications
      summary: Get recent smart money signals
      description: |
        Returns recent signal notifications. Poll this endpoint every 10–30 seconds using the `since` parameter to receive new signals incrementally.

        Signal types:
        - **convergence_p0** — 5+ distinct qualifying top wallets bought the same token (after per-customer filters)
        - **convergence_p1** — same pattern with at least `convergence_min_wallets` (default 3) but fewer than 5 wallets; set min wallets to 2 for earliest P1
        - **whale_move** — large single buy on a DEX chain (not emitted for Polymarket in current pipeline)
        - **sell_signal** — multiple top wallets sold the same token (DEX only)
        - **pm_convergence_bet_p0** / **pm_convergence_bet_p1** — Polymarket buy-side outcome convergence (same CLOB); P0 = 5+ wallets, P1 = below P0 but above min wallets; includes `market` + `cta` when enriched
      parameters:
        - name: since
          in: query
          schema:
            type: string
            format: date-time
          description: ISO 8601 timestamp. Returns notifications newer than this. Defaults to 24 hours ago.
          example: "2026-03-23T10:00:00Z"
        - name: limit
          in: query
          schema:
            type: integer
            minimum: 1
            maximum: 100
            default: 50
          description: Maximum number of notifications to return.
        - name: wallet_id
          in: query
          schema:
            type: string
          description: |
            Wallet address for personalized enrichment. Each signal is checked
            against this wallet's positions (DEX token holdings and/or Polymarket
            outcome bets). Matching signals receive a `personal` block with
            position details and are boosted to the top of results.
          example: "0x7a3f9b2c1d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a"
      responses:
        "200":
          description: Signal notifications
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/NotificationsResponse"
              example:
                notifications:
                  - signal_id: "conv-base-TOSHI-20260323-0845"
                    signal_type: convergence
                    notification_type: convergence_p0
                    priority: P0
                    chain: base
                    headline: "5 top-ranked traders bought $TOSHI in last 6h"
                    token:
                      address: "0xac17..."
                      symbol: "$TOSHI"
                      current_price_usd: 0.000812
                      price_change_pct: 12.4
                      first_smart_entry_price: 0.000722
                    traders:
                      - wallet: "0x7a3f...9e2d"
                        leaderboard_rank: 4
                        trade_size_usd: 8420.50
                        total_pnl: 142000
                        win_rate: 0.72
                    summary:
                      trader_count: 5
                      total_usd_inflow: 34200.00
                      highest_rank: 4
                    why:
                      reasons:
                        - text: "3 of these traders have >70% win rate"
                        - text: "Token price up +12% since first smart entry"
                        - text: "Combined inflow $34.2K in 6 hours"
                    cta:
                      action: swap
                      token_address: "0xac17..."
                      chain: base
                    timestamp: "2026-03-23T08:45:00Z"
                    sent_at: "2026-03-23T08:45:02Z"
                count: 1
                since: "2026-03-23T08:00:00Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"
    delete:
      operationId: resetBudget
      summary: Reset daily notification budget
      description: |
        Deletes today's notification log entries, resetting the daily budget counter. Useful for testing and demo scenarios where you want to receive a fresh batch of signals.
      responses:
        "200":
          description: Budget reset confirmation
          content:
            application/json:
              schema:
                type: object
                properties:
                  deleted:
                    type: integer
                    description: Number of notification entries deleted.
                  reset_at:
                    type: string
                    format: date-time
                    description: Timestamp of the reset.
              example:
                deleted: 23
                reset_at: "2026-03-23T15:30:00Z"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

  /config:
    get:
      operationId: getConfig
      summary: Get customer configuration
      description: |
        Returns your full configuration object including signal sensitivity, quality gates, token filters, and delivery settings.
        Your account is identified automatically from the API key — no `customer_id` parameter needed.
      responses:
        "200":
          description: Customer configuration
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CustomerConfig"
              example:
                customer_id: my-app
                display_name: My Application
                chains: [base, ethereum]
                signal_types: [convergence_p0, convergence_p1, whale_move, sell_signal]
                daily_budget: 30
                per_type_limits:
                  convergence_p0: 10
                  convergence_p1: 10
                  whale_move: 5
                  sell_signal: 5
                min_trades: 10
                min_win_rate: 0
                min_closed_positions: 1
                min_tokens_traded: 5
                max_buy_volume_usd: 1000000000
                convergence_min_wallets: 3
                convergence_window_hours: 6
                whale_min_trade_usd: 50000
                whale_max_rank: 50
                min_trader_score: 0
                min_trigger_win_rate: 0
                min_trigger_closed: 0
                min_token_liquidity_usd: 0
                min_token_holders: 0
                priority_filter: all
                cooldown_hours: 2
                webhook_url: ""
                created_at: "2026-03-15T00:00:00Z"
                updated_at: "2026-03-23T12:00:00Z"
        "400":
          description: Missing customer_id parameter
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "customer_id is required"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "404":
          description: Customer not found
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              example:
                error: "No config found for unknown-customer"
        "500":
          $ref: "#/components/responses/InternalError"
    put:
      operationId: updateConfig
      summary: Update customer configuration
      description: |
        Partial update — only send the fields you want to change. Your account is identified automatically from the API key. Changes take effect within 5 minutes (server-side config cache TTL).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/ConfigUpdate"
            examples:
              set_budget_and_chains:
                summary: Set daily budget and active chains
                value:
                  daily_budget: 30
                  chains: [base, ethereum]
              tighten_quality:
                summary: Tighten quality gates
                value:
                  min_trader_score: 0.2
                  min_trigger_win_rate: 0.5
                  min_trigger_closed: 3
              configure_webhook:
                summary: Enable webhook delivery
                value:
                  webhook_url: "https://hooks.slack.com/services/T00/B00/xxx"
      responses:
        "200":
          description: Updated configuration (full object returned)
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/CustomerConfig"
        "400":
          description: Invalid request
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"
              examples:
                no_fields:
                  value:
                    error: "No updatable fields provided"
        "401":
          $ref: "#/components/responses/Unauthorized"
        "500":
          $ref: "#/components/responses/InternalError"

components:
  securitySchemes:
    BearerAuth:
      type: http
      scheme: bearer
      description: "API key prefixed with `mbd-`. Pass as `Authorization: Bearer mbd-your-api-key-here`."

  responses:
    Unauthorized:
      description: Missing or invalid Bearer token
      content:
        application/json:
          schema:
            type: object
            properties:
              message:
                type: string
          example:
            message: Unauthorized
    InternalError:
      description: Server error
      content:
        application/json:
          schema:
            $ref: "#/components/schemas/Error"
          example:
            error: "Internal server error"

  schemas:
    Error:
      type: object
      properties:
        error:
          type: string
      required: [error]

    Wallet:
      type: object
      description: A ranked smart money wallet.
      properties:
        rank:
          type: integer
          description: Position in the leaderboard (1-based).
        wallet_address:
          type: string
          description: On-chain wallet address.
        chain:
          type: string
          enum: [base, ethereum, solana, polymarket]
          description: Blockchain the wallet was indexed on.
        trader_score:
          type: number
          format: float
          description: "Composite score (0–1) blending PnL efficiency (40%), win rate (30%), consistency (20%), activity (10%)."
        total_buy_volume_usd:
          type: number
          format: float
          description: Total USD buy volume across all trades.
        total_sell_volume_usd:
          type: number
          format: float
          description: Total USD sell volume across all trades.
        net_pnl_usd:
          type: number
          format: float
          description: Net profit/loss in USD (all-time, from data provider).
        realized_pnl_usd:
          type: number
          format: float
          description: Realized PnL from closed positions only (computed from per-token position rollups).
        trade_count:
          type: integer
          description: Total number of trades.
        buy_count:
          type: integer
          description: Number of buy trades.
        sell_count:
          type: integer
          description: Number of sell trades.
        win_rate:
          type: number
          format: float
          description: "Win rate from data provider (0–1)."
        true_win_rate:
          type: number
          format: float
          description: "Win rate computed from closed positions (0–1). More reliable than `win_rate`."
        winning_positions:
          type: integer
          description: Number of closed positions with positive PnL.
        closed_positions:
          type: integer
          description: Total number of closed positions.
        tokens_traded:
          type: integer
          description: Number of unique tokens traded.
        avg_trade_size_usd:
          type: number
          format: float
          description: Average trade size in USD.
        largest_trade_usd:
          type: number
          format: float
          description: Largest single trade in USD.
        last_trade_time:
          type: string
          format: date-time
          description: Timestamp of the wallet's most recent trade.
        first_trade_time:
          type: string
          format: date-time
          description: Timestamp of the wallet's first indexed trade.

    LeaderboardResponse:
      type: object
      properties:
        wallets:
          type: array
          items:
            $ref: "#/components/schemas/Wallet"
          description: Ranked list of wallets.
        total:
          type: integer
          description: Total number of wallets matching the filters (before limit).
        rank_by:
          type: string
          description: The metric used for ranking.
        chain:
          type: string
          description: The chain queried.
        chain_defaults:
          type: object
          description: Default filter values for the queried chain.
          properties:
            min_trades:
              type: integer
            min_tokens:
              type: integer
            min_closed:
              type: integer
            max_buy_vol:
              type: number
        updated_at:
          type: string
          format: date-time
          description: Timestamp of this response.

    Token:
      type: object
      description: Token involved in the signal.
      properties:
        address:
          type: string
          description: On-chain token contract address (DEX) or Polymarket CLOB token id as decimal string.
        symbol:
          type: string
          description: Token ticker symbol.
        current_price_usd:
          type: number
          format: float
          description: Current token price in USD at signal time.
        price_change_pct:
          type: number
          format: float
          description: Price change percentage since first smart money entry.
        first_smart_entry_price:
          type: number
          format: float
          description: Token price when the first ranked wallet bought in.

    Market:
      type: object
      description: Polymarket market metadata (present when chain is polymarket). Sourced from DynamoDB (polymarket-items / mappings), not Postgres.
      properties:
        item_id:
          type: string
          description: Gamma market id.
        question:
          type: string
        outcome_label:
          type: string
        slug:
          type: string
        end_date:
          type: string
        tags:
          type: array
          items:
            type: string
        volume_24hr:
          type: number
          format: float
        closed:
          type: boolean
        enrichment_source:
          type: string
          description: polymarket-items or polymarket-market-mappings fallback.

    UserTrade:
      type: object
      description: Polymarket signal — one wallet's stats (trader) and fill (trade).
      properties:
        trader:
          type: object
          description: Leaderboard and PnL fields for the wallet (no nested trade).
        trade:
          type: object
          description: Outcome trade for this signal (size_usd, price_usd, transaction_hash, traded_at).

    Trader:
      type: object
      description: A trader involved in the signal.
      properties:
        wallet:
          type: string
          description: Truncated wallet address.
        leaderboard_rank:
          type: integer
          description: Trader's current leaderboard rank.
        trade_size_usd:
          type: number
          format: float
          description: Size of the trade that triggered the signal.
        total_pnl:
          type: number
          format: float
          description: Trader's all-time PnL.
        win_rate:
          type: number
          format: float
          description: "Trader's win rate (0–1)."

    Signal:
      type: object
      description: A smart money signal notification.
      properties:
        signal_id:
          type: string
          description: Unique signal identifier. Use this to deduplicate.
          example: "conv-base-TOSHI-20260323-0845"
        signal_type:
          type: string
          enum: [convergence, whale_move, sell_signal, pm_convergence]
          description: High-level signal category.
        notification_type:
          type: string
          enum: [convergence_p0, convergence_p1, whale_move, sell_signal, pm_convergence_bet_p0, pm_convergence_bet_p1]
          description: Specific signal type with priority tier.
        priority:
          type: string
          enum: [P0, P1, P2]
          description: "Signal priority. P0 = highest conviction."
        chain:
          type: string
          enum: [base, ethereum, solana, polymarket]
        headline:
          type: string
          description: Human-readable summary for display.
          example: "5 top-ranked traders bought $TOSHI in last 6h"
        token:
          $ref: "#/components/schemas/Token"
        market:
          $ref: "#/components/schemas/Market"
        traders:
          type: array
          items:
            $ref: "#/components/schemas/Trader"
          description: DEX signals — merged participant rows including nested trade. For Polymarket, this may be empty; use user_trades instead.
        user_trades:
          type: array
          items:
            $ref: "#/components/schemas/UserTrade"
          description: Polymarket only — canonical list of { trader, trade } per wallet. Empty for DEX signals.
        summary:
          type: object
          description: Aggregate stats about the signal.
          properties:
            trader_count:
              type: integer
              description: Number of wallets involved.
            total_usd_inflow:
              type: number
              format: float
              description: Combined USD value of all trades in the signal.
            highest_rank:
              type: integer
              description: Best leaderboard rank among the traders.
        why:
          type: object
          description: Explainability reasons for the signal.
          properties:
            reasons:
              type: array
              items:
                type: object
                properties:
                  text:
                    type: string
                    description: Human-readable reason.
        cta:
          type: object
          description: Call-to-action data for deep-linking to a swap UI or Polymarket.
          properties:
            action:
              type: string
              enum: [swap, polymarket]
            token_address:
              type: string
              description: Token contract address for the swap (DEX) or CLOB id (Polymarket).
            chain:
              type: string
              description: Chain for the swap.
            clob_token_id:
              type: string
              description: Polymarket CLOB token id (decimal string) when action is polymarket.
            item_id:
              type: string
            slug:
              type: string
        timestamp:
          type: string
          format: date-time
          description: When the signal was generated.
        sent_at:
          type: string
          format: date-time
          description: When the signal was delivered.

    NotificationsResponse:
      type: object
      properties:
        notifications:
          type: array
          items:
            $ref: "#/components/schemas/Signal"
        count:
          type: integer
          description: Number of notifications returned.
        since:
          type: string
          format: date-time
          description: The `since` timestamp used for the query.

    CustomerConfig:
      type: object
      description: Per-customer configuration controlling signal generation and delivery.
      properties:
        customer_id:
          type: string
          description: Unique customer identifier.
        display_name:
          type: string
          description: Human-readable customer name.
        chains:
          type: array
          items:
            type: string
            enum: [base, ethereum, solana, polymarket]
          description: Active chains that generate signals for this customer.
        signal_types:
          type: array
          items:
            type: string
            enum: [convergence_p0, convergence_p1, whale_move, sell_signal, pm_convergence_bet_p0, pm_convergence_bet_p1]
          description: Which signal types are enabled.
        daily_budget:
          type: integer
          description: Maximum total notifications per day across all signal types.
        per_type_limits:
          type: object
          description: Per-signal-type daily caps.
          additionalProperties:
            type: integer
          example:
            convergence_p0: 10
            convergence_p1: 10
            whale_move: 5
            sell_signal: 5
            pm_convergence_bet_p0: 3
            pm_convergence_bet_p1: 5
        min_trades:
          type: integer
          description: Minimum trade count for ranked wallets.
        min_win_rate:
          type: number
          format: float
          description: "Minimum win rate (0–1) for ranked wallets."
        min_closed_positions:
          type: integer
          description: Minimum closed positions for ranked wallets.
        min_tokens_traded:
          type: integer
          description: Minimum unique tokens traded.
        max_buy_volume_usd:
          type: number
          description: Maximum buy volume (USD) for ranked wallets.
        convergence_min_wallets:
          type: integer
          description: Minimum wallets buying the same token to trigger a convergence signal.
        convergence_window_hours:
          type: number
          description: Time window (hours) for detecting wallet convergence on a token.
        whale_min_trade_usd:
          type: number
          description: Minimum USD trade size to trigger a whale move signal.
        whale_max_rank:
          type: integer
          description: Only wallets ranked this high or better trigger whale alerts.
        min_trader_score:
          type: number
          format: float
          description: "Minimum composite trader score (0–1) for a wallet to generate any signal."
        min_trigger_win_rate:
          type: number
          format: float
          description: "Minimum win rate (0–1) for the triggering wallet."
        min_trigger_closed:
          type: integer
          description: Minimum closed positions for the triggering wallet.
        min_token_liquidity_usd:
          type: number
          description: Minimum 24h volume (USD) for the token in a signal.
        min_token_holders:
          type: integer
          description: Minimum holder count for the token in a signal.
        priority_filter:
          type: string
          description: "Only deliver signals at or above this priority. Values: `all`, `P0`, `P0,P1`, `P1,P2`."
        cooldown_hours:
          type: number
          description: Minimum hours between duplicate signals for the same token.
        webhook_url:
          type: string
          format: uri
          description: Webhook endpoint for real-time signal delivery via POST.
        created_at:
          type: string
          format: date-time
        updated_at:
          type: string
          format: date-time

    ConfigUpdate:
      type: object
      description: Partial config update. Only include fields you want to change. Your account is identified by the API key.
      properties:
        display_name:
          type: string
        chains:
          type: array
          items:
            type: string
            enum: [base, ethereum, solana]
        signal_types:
          type: array
          items:
            type: string
            enum: [convergence_p0, convergence_p1, whale_move, sell_signal]
        daily_budget:
          type: integer
          minimum: 5
          maximum: 200
        per_type_limits:
          type: object
          additionalProperties:
            type: integer
        min_trades:
          type: integer
        min_win_rate:
          type: number
          minimum: 0
          maximum: 1
        min_closed_positions:
          type: integer
        min_tokens_traded:
          type: integer
        max_buy_volume_usd:
          type: number
        convergence_min_wallets:
          type: integer
          minimum: 2
        convergence_window_hours:
          type: number
          minimum: 1
        whale_min_trade_usd:
          type: number
          minimum: 0
        whale_max_rank:
          type: integer
          minimum: 1
        min_trader_score:
          type: number
          minimum: 0
          maximum: 1
        min_trigger_win_rate:
          type: number
          minimum: 0
          maximum: 1
        min_trigger_closed:
          type: integer
          minimum: 0
        min_token_liquidity_usd:
          type: number
          minimum: 0
        min_token_holders:
          type: integer
          minimum: 0
        priority_filter:
          type: string
          enum: [all, P0, "P0,P1", "P1,P2"]
        cooldown_hours:
          type: number
          minimum: 0
        webhook_url:
          type: string
          format: uri
