API reference

The v1 API, kept for existing integrations. New work should use v2.

v1 is frozen

v1 is deprecated and stops answering on April 16, 2027. Every v1 response carries Deprecation and Sunset headers. Move to v2 before then.

Migrating from v1 maps every v1 route to its v2 successor and lists what changed. Base URL: https://polywhaler.com/api/v1.

Trades#

Polymarket trades from $2,000 and Kalshi trades from $5,000 ($10,000 for sports and 15-minute or hourly markets), with insider scores on Polymarket buys and full market context.

GET /trades#

GET/api/v1/trades

List whale trades

Paginated list of whale trades. Cursor-based pagination using the last item's timestamp. Covers both venues: Polymarket (default) and Kalshi (venue=kalshi or venue=all). Kalshi rows carry venue: "kalshi", the Kalshi trade id as transactionHash, and the trader's Kalshi identity as proxyWallet; they have no insider scores. Polymarket SELL trades are not scored either (insiderScore is null). There is no size floor beyond what is stored: Polymarket trades from $2,000; Kalshi trades from $5,000, or $10,000 for sports and for 15-minute and hourly markets. wallet, min_insider_score, and the trader-quality filters are Polymarket-only: with venue=all they silently narrow results to Polymarket, and with venue=kalshi they 400.

  • limitinteger · query

    Default 100, max 500.

  • cursorstring · query

    Unix-seconds timestamp from a prior nextCursor.

  • frominteger | ISO 8601 · query

    Inclusive lower bound on timestamp.

  • tointeger | ISO 8601 · query

    Inclusive upper bound on timestamp.

  • venuestring · query

    polymarket (default), kalshi, or all (merged, newest first).

  • walletstring · query

    Filter by proxy wallet address (case-insensitive). Polymarket only.

  • categorystring · query

    Market category, comma-separated for multiple (e.g. crypto,politics).

  • sportsstring · query

    include (default), exclude (hide sports/esports, matching the dashboard), or only.

  • sidestring · query

    BUY or SELL.

  • min_amountnumber · query

    Minimum trade USD value. No default; Kalshi rows never go below their $5,000 floor.

  • min_insider_scorenumber · query

    Minimum insider score (0-100). Polymarket only.

  • max_probabilitynumber · query

    Only trades entered strictly below this price (0-1 exclusive) — the longshot filter.

  • min_smart_moneynumber · query

    Minimum smoothed smart-money score (0-100) of the trading wallet. Polymarket only.

  • min_win_ratenumber · query

    Minimum smoothed win rate (0-100) of the trading wallet. Polymarket only.

  • max_rankinteger · query

    Only wallets at or above this leaderboard rank (e.g. 100 = Top 100). Polymarket only.

  • includestring · query

    Comma-separated. trader_quality attaches the wallet's smart-money score, win rate, rank, and tier to each Polymarket trade (attached automatically when a quality filter is set).

  • event_slugstring · query

    Polymarket event slug (Kalshi: matches the ticker).

  • market_slugstring · query

    Polymarket market slug (Kalshi: matches the ticker).

Response
{
  "data": [
    {
      "venue": "polymarket",
      "transactionHash": "0xabc...",
      "side": "BUY",
      "size": 25000,
      "price": 0.42,
      "amount": 10500,
      "timestamp": 1736208000,
      "eventSlug": "us-recession-2026",
      "marketSlug": "us-recession-2026",
      "title": "Will the US enter a recession in 2026?",
      "outcome": "Yes",
      "proxyWallet": "0xdef...",
      "trader": { "name": null, "pseudonym": "Sample-Heron", "image": null },
      "category": "economy",
      "insiderScore": 78,
      "insiderBreakdown": {
        "conviction": 30, "priceImpact": 0, "tradeSize": 0,
        "marketDominance": 16, "timing": 6, "walletAge": 10, "context": 16
      },
      "traderQuality": {
        "smartMoneyScore": 81.4, "winRate": 62.0, "rank": 42,
        "rankTier": "top100", "isProvisional": false
      }
    }
  ],
  "pagination": { "limit": 100, "count": 100, "nextCursor": "1736207999" },
  "venue": "polymarket"
}

GET /trades/{hash}#

GET/api/v1/trades/{hash}

Get a single trade

Returns one Polymarket trade with the full insider-score breakdown (total plus a factors array, each factor with its points out of maxPoints; null for unscored trades), any LLM context score, and, once the market has resolved, a resolution object with win/loss, P&L, ROI, and the vindication flag. resolution is null while the market is open.

  • hashstring · pathRequired

    Polygon transaction hash.

Response
{
  "data": {
    "venue": "polymarket",
    "transactionHash": "0xabc...",
    "side": "BUY",
    "amount": 10500,
    "insiderScore": 78,
    "insiderScoreBreakdown": {
      "total": 78,
      "factors": [
        {
          "name": "Low Probability, High Stakes",
          "description": "Large bets on unlikely outcomes may indicate insider knowledge",
          "points": 30,
          "maxPoints": 35,
          "met": true,
          "details": "..."
        },
        {
          "name": "High-Risk Market Context",
          "description": "AI-evaluated insider risk based on market type and event context",
          "points": 16,
          "maxPoints": 20,
          "met": true,
          "details": "..."
        }
      ]
    },
    "insiderContextScore": 16,
    "resolution": {
      "status": "win",
      "winningOutcome": "Yes",
      "entryPrice": 0.42,
      "exitPrice": 1.0,
      "profitLoss": 14500,
      "roiPercent": 138.1,
      "resolvedAt": "2026-06-30T12:00:00Z",
      "holdSeconds": 1209600,
      "isVindicated": false
    }
  }
}

Outcomes#

Resolved trade results — win/loss, P&L, ROI, vindication. The backtesting surface.

GET /outcomes#

GET/api/v1/outcomes

List resolved trade outcomes

Polymarket whale trades whose markets have resolved, with entry/exit price, dollar P&L, ROI, hold duration, and the vindication flag (a suspicious low-probability trade that won). Each row embeds the originating trade's market context. Cursor-paginated on trade timestamp, newest first.

  • limitinteger · query

    Default 100, max 500.

  • cursorstring · query

    Unix-seconds trade timestamp from a prior nextCursor.

  • frominteger | ISO 8601 · query

    Inclusive lower bound on trade timestamp.

  • tointeger | ISO 8601 · query

    Inclusive upper bound on trade timestamp.

  • walletstring · query

    Filter by wallet address (case-insensitive).

  • event_slugstring · query

    Polymarket event slug.

  • statusstring · query

    win, loss, pending, cancelled, or invalid.

  • vindicatedboolean · query

    true = only vindicated trades (the Calls feed), false = exclude them.

  • min_roinumber · query

    Minimum ROI percent.

Response
{
  "data": [
    {
      "transactionHash": "0xabc...",
      "wallet": "0xdef...",
      "eventSlug": "us-recession-2026",
      "status": "win",
      "winningOutcome": "Yes",
      "entryPrice": 0.42,
      "exitPrice": 1.0,
      "profitLoss": 14500,
      "roiPercent": 138.1,
      "side": "BUY",
      "positionSize": 25000,
      "entryValue": 10500,
      "tradeTimestamp": 1736208000,
      "resolvedAt": "2026-06-30T12:00:00Z",
      "holdSeconds": 1209600,
      "isVindicated": false,
      "trade": {
        "title": "Will the US enter a recession in 2026?",
        "outcome": "Yes",
        "category": "economy",
        "marketSlug": "us-recession-2026",
        "insiderScore": 78,
        "trader": { "name": null, "pseudonym": "Sample-Heron", "image": null }
      }
    }
  ],
  "pagination": { "limit": 100, "count": 100, "nextCursor": "1736207999" }
}

Wallets#

Whale profiles, trade history, and coordinated-trading cluster detection.

GET /wallets/{addr}#

GET/api/v1/wallets/{addr}

Get a wallet profile

Aggregated stats, global leaderboard rank, category breakdown, trading patterns, and risk profile. smartMoneyScore is the smoothed value shown on the dashboard; smartMoneyScoreRaw is the un-smoothed daily value. The rank is computed against smoothed_smart_money_score with a realized-P&L tiebreaker, matching the leaderboard UI. winRate is a percentage (0-100) of decisive closed positions won; recentProfitLoss is realized P&L on positions closed in the last 90 days (the board's 90d P&L). Pass include=… to opt into extra data.

  • addrstring · pathRequired

    Wallet address (case-insensitive).

  • includestring · query

    Comma-separated. portfolio adds live Polymarket portfolio summary (~300ms). walletAge adds on-chain wallet age via Polygonscan (~200ms; cached). snapshots adds rankHistory (last 180 days) and scoreHistory (last 30 days) from the daily snapshot table.

Response
{
  "data": {
    "wallet": "0xdef...",
    "traderName": null,
    "traderImage": null,
    "isConfirmedInsider": false,
    "followerCount": 12,
    "leaderboardRank": 42,
    "stats": {
      "totalTrades": 287,
      "totalVolume": 1240000,
      "avgTradeSize": 4320,
      "medianTradeSize": 1500,
      "largestTradeValue": 88000,
      "winCount": 78, "lossCount": 47, "pendingCount": 12,
      "winRate": 62.4,
      "avgRoiPercent": 18.4,
      "totalProfitLoss": 184000,
      "recentProfitLoss": 23500,
      "smartMoneyScore": 81.4,
      "smartMoneyScoreRaw": 83.1,
      "isProvisional": false,
      "avgInsiderScore": 54,
      "firstTradeTimestamp": 1704067200,
      "lastTradeTimestamp": 1736208000
    },
    "categoryBreakdown": { "politics": { "trades": 142, "volume": 612000, "winRate": 0.71 } },
    "tradingPatterns": {
      "preferredProbabilityRange": "40-60% (moderate)",
      "tradingFrequency": "high (2-5 trades/day)",
      "avgHoldDurationDays": "12.4"
    },
    "riskProfile": "moderate",
    "isActive": true,
    "lastUpdatedAt": "2026-05-21T13:42:00Z",
    "walletAge": {
      "ageDays": 412,
      "ageHours": 9897,
      "joinDate": "2025-03-29T07:14:00Z",
      "source": "polygonscan"
    },
    "portfolio": null
  }
}

GET /wallets/{addr}/positions#

GET/api/v1/wallets/{addr}/positions

Get live Polymarket positions

Live current + closed positions for a wallet, fetched directly from Polymarket. Includes aggregated portfolio summary (value, unrealized + realized P&L, win/loss counts). On closed positions, totalBought is shares (as Polymarket reports it) and costUsd is what the position cost in dollars; avgRoiPercent is realized P&L over the summed costUsd.

  • addrstring · pathRequired

    Wallet address (case-insensitive).

  • statusstring · query

    current, closed, or all (default).

  • limitinteger · query

    Override Polymarket page size. Default 1000 current / 2000 closed.

Response
{
  "data": {
    "wallet": "0xdef...",
    "summary": {
      "portfolioValue": 142000,
      "unrealizedPnl": 18400,
      "realizedPnl": 165600,
      "totalPnl": 184000,
      "activeCount": 14, "redeemableCount": 2, "mergeableCount": 0,
      "closedCount": 125, "winCount": 78, "lossCount": 47,
      "winRate": 0.624, "avgRoiPercent": 18.4
    },
    "current": [
      {
        "title": "Will the US enter a recession in 2026?",
        "outcome": "Yes",
        "size": 12500, "avgPrice": 0.41, "curPrice": 0.49,
        "currentValue": 6125, "cashPnl": 1000,
        "redeemable": false, "mergeable": false,
        "eventSlug": "us-recession-2026", "marketSlug": "us-recession-2026"
      }
    ],
    "closed": [
      {
        "title": "...", "outcome": "Yes",
        "avgPrice": 0.32, "realizedPnl": 8400, "totalBought": 16000, "costUsd": 5120,
        "eventSlug": "...", "timestamp": 1735660800
      }
    ]
  }
}

GET /wallets/{addr}/trades#

GET/api/v1/wallets/{addr}/trades

List a wallet's trades

Paginated trade history for one wallet.

  • addrstring · pathRequired

    Wallet address (case-insensitive).

  • limitinteger · query

    Default 100, max 500.

  • cursorstring · query

    Unix-seconds timestamp from a prior nextCursor.

  • frominteger | ISO 8601 · query

    Inclusive lower bound on timestamp.

  • tointeger | ISO 8601 · query

    Inclusive upper bound on timestamp.

  • sidestring · query

    BUY or SELL.

Response
{
  "data": [ /* same shape as GET /trades */ ],
  "pagination": { "limit": 100, "count": 100, "nextCursor": "1736207999" }
}

GET /wallets/{addr}/cluster#

GET/api/v1/wallets/{addr}/cluster

Get coordinated clusters

Detected coordinated trading groups (clusters) that include this wallet.

  • addrstring · pathRequired

    Wallet address (case-insensitive).

Response
{
  "data": [
    {
      "clusterId": "6f1c2a9e-4b7d-4e2a-9c3f-1d8e5b0a7c42",
      "members": ["0xdef...", "0x...", "0x..."],
      "size": 4,
      "confidenceScore": 0.86,
      "outcomeAlignmentRate": 0.92,
      "totalCoordinatedTrades": 41,
      "isActive": true
    }
  ]
}

Leaderboard#

Smart-money rankings across all tracked wallets.

GET /leaderboard#

GET/api/v1/leaderboard

List top traders

Offset-paginated, ranked by smoothed smart-money score (matches the dashboard). smartMoneyScore is the smoothed display value and smartMoneyScoreRaw the un-smoothed daily value. winRate (a percentage, 0-100), totalProfitLoss (lifetime realized) and recentProfitLoss (realized over the last 90 days) are raw, the same figures the dashboard and /wallets/{addr} show. isProvisional=true flags wallets with fewer than 10 resolved trades, where the score is still stabilizing. Sports whales are excluded by default so ranks line up with the dashboard board and the leaderboardRank from /wallets/{addr}; pass exclude_sports=false to rank across every tracked wallet.

  • limitinteger · query

    Default 50, max 200.

  • offsetinteger · query

    Default 0.

  • sort_bystring · query

    smart_money_score (default; sorts on smoothed), win_rate, total_profit_loss, recent_profit_loss, avg_insider_score, total_trades.

  • sort_dirstring · query

    asc or desc (default).

  • exclude_sportsboolean · query

    Default true (matches the dashboard). false includes sports whales; the response's sportsExcluded field reports what was applied.

  • includestring · query

    Comma-separated. snapshots adds prevRank (yesterday's rank) and scoreHistory (last 30 days) per entry.

Response
{
  "data": [
    {
      "wallet": "0xdef...",
      "rank": 1,
      "traderName": "sample-whale",
      "traderImage": "https://...",
      "winRate": 71.2,
      "totalProfitLoss": 612000,
      "recentProfitLoss": 88400,
      "smartMoneyScore": 94.2,
      "smartMoneyScoreRaw": 95.7,
      "isProvisional": false,
      "avgInsiderScore": 38,
      "totalTrades": 412,
      "isConfirmedInsider": false,
      "followerCount": 142,
      "lastUpdatedAt": "2026-05-21T13:42:00Z"
    }
  ],
  "pagination": { "limit": 50, "offset": 0, "totalCount": 1843 },
  "sortBy": "smart_money_score",
  "sortDir": "desc",
  "sportsExcluded": true
}

Positions#

Aggregated positions rolled up across multiple trades.

GET /positions/aggregated#

GET/api/v1/positions/aggregated

List aggregated positions

Wallets that have built up sizable positions across many small trades. Cursor-paginated.

  • limitinteger · query

    Default 100, max 500.

  • cursorstring · query

    Unix-seconds timestamp from a prior nextCursor.

  • frominteger | ISO 8601 · query

    Lower bound on most-recent trade timestamp.

  • tointeger | ISO 8601 · query

    Upper bound on most-recent trade timestamp.

  • walletstring · query

    Filter by proxy wallet.

  • categorystring · query

    Market category.

  • sidestring · query

    BUY or SELL.

  • min_insider_scorenumber · query

    Minimum aggregated insider score.

  • min_valuenumber · query

    Minimum total USD value.

Response
{
  "data": [
    {
      "positionKey": "0xdef:us-recession-2026:Yes",
      "proxyWallet": "0xdef...",
      "marketSlug": "us-recession-2026",
      "side": "BUY",
      "totalValue": 145000,
      "tradeCount": 14,
      "avgPrice": 0.41,
      "aggregatedInsiderScore": 72
    }
  ],
  "pagination": { "limit": 100, "count": 100, "nextCursor": "1736207999" }
}

Signals & Analytics#

Aggregated market intelligence: insider-risk events, capital flow, whale volume, and consensus.

GET /suspicious-events#

GET/api/v1/suspicious-events

List insider-risk events

Events ranked by aggregated insider risk: high-insider-score BUY volume rolled up per event, with the concentrated outcomes and the top whales behind them. Same computation as the site's Suspicious Events feed. Sports are excluded unless requested via category.

  • timestring · query

    1h, 6h, 24h, 7d (default), or 30d.

  • categorystring · query

    One category, sports, or markets (everything except crypto). Default: all non-sports.

  • limitinteger · query

    Default 50, max 100.

Response
{
  "data": [
    {
      "eventSlug": "us-strikes-iran-2026",
      "title": "US military strike on Iran in 2026?",
      "category": "geopolitics",
      "aggregateInsiderScore": 74,
      "highRiskTradeCount": 6,
      "mediumRiskTradeCount": 11,
      "totalSuspiciousVolume": 412000,
      "suspiciousOutcomes": [
        { "outcome": "Yes", "insiderScore": 88, "tradeCount": 9, "totalVolume": 310000, "dominantSide": "BUY" }
      ],
      "topWhales": [
        { "wallet": "0xdef...", "name": null, "totalVolume": 120000, "insiderScore": 88 }
      ]
    }
  ],
  "params": { "time": "7d", "category": "all", "limit": 50 }
}

GET /smart-money-flow#

GET/api/v1/smart-money-flow

Get net whale capital flow

Net whale capital in/out by category over the window (buys positive, sells negative), split into all-whale flow vs smart-money-only flow (wallets with smoothed smart-money score >= 60), plus the markets smart money is accumulating. Sports excluded.

  • rangestring · query

    24h, 7d (default), or 30d.

Response
{
  "data": {
    "range": "7d",
    "totals": { "netFlow": 1840000, "smartNetFlow": 920000, "tradeCount": 3120 },
    "categories": [
      { "category": "politics", "netFlow": 610000, "smartNetFlow": 380000, "buyVolume": 1400000, "sellVolume": 790000, "tradeCount": 940 }
    ],
    "topAccumulating": [
      { "title": "Will the US enter a recession in 2026?", "eventSlug": "us-recession-2026", "smartNetFlow": 240000, "tradeCount": 41 }
    ]
  }
}

GET /markets#

GET/api/v1/markets

List markets by whale volume

Per-event whale-activity roll-up: total whale volume, buy/sell split, net flow, unique wallets, and volume-weighted insider score. Sorted by whale volume — where whale money is concentrating right now.

  • timestring · query

    1h, 6h, 24h (default), 7d, or 30d.

  • categorystring · query

    Market category, comma-separated for multiple.

  • sportsstring · query

    include (default), exclude, or only.

  • min_volumenumber · query

    Minimum whale volume (USD) to include an event.

  • limitinteger · query

    Default 25, max 100.

Response
{
  "data": [
    {
      "eventSlug": "us-recession-2026",
      "title": "Will the US enter a recession in 2026?",
      "category": "economy",
      "whaleVolume": 1240000,
      "tradeCount": 84,
      "buyVolume": 910000,
      "sellVolume": 330000,
      "netFlow": 580000,
      "uniqueWallets": 37,
      "maxInsiderScore": 82,
      "volumeWeightedInsiderScore": 44
    }
  ],
  "params": { "time": "24h", "sports": "include", "category": "all", "limit": 25 }
}

GET /consensus#

GET/api/v1/consensus

Get watchlist consensus positions

Positions where N or more whales on YOUR watchlist bought (or sold) the same outcome in the same market. Driven by the authenticated account's watchlist — manage it via /watchlist or the dashboard.

  • timestring · query

    Lookback window, e.g. 24h, 7d (default), 30d.

  • mininteger · query

    Minimum whales converging on the position. Default 3, minimum 2.

  • sidestring · query

    BUY (default) or SELL.

Response
{
  "data": [
    {
      "positionKey": "us-recession-2026:Yes:BUY",
      "eventSlug": "us-recession-2026",
      "marketSlug": "us-recession-2026",
      "title": "Will the US enter a recession in 2026?",
      "outcome": "Yes",
      "side": "BUY",
      "category": "economy",
      "whaleCount": 4,
      "totalValue": 182000,
      "lastTradeTimestamp": 1736208000,
      "whales": [
        { "wallet": "0xdef...", "name": "sample-whale", "value": 88000, "timestamp": 1736208000 }
      ]
    }
  ],
  "params": { "time": "7d", "min": 3, "side": "BUY", "watchlistSize": 12 }
}

Clusters#

Coordinated-trading (sybil) cluster detection across all tracked wallets.

GET /clusters#

GET/api/v1/clusters

List detected clusters

Every detected coordinated-trading cluster, highest confidence first. Complements /wallets/{addr}/cluster (single-wallet lookup). Offset-paginated.

  • limitinteger · query

    Default 50, max 200.

  • offsetinteger · query

    Default 0.

  • activestring · query

    true (default), false, or all.

  • min_confidencenumber · query

    Minimum confidence score (0-1).

  • min_sizeinteger · query

    Minimum member count.

  • methodstring · query

    Filter by detection method: timing_correlation, outcome_correlation, funding_source, multi_factor, or sybil_farm.

  • flaggedboolean · query

    Only clusters flagged (or not flagged) for review.

Response
{
  "data": [
    {
      "clusterId": "6f1c2a9e-4b7d-4e2a-9c3f-1d8e5b0a7c42",
      "members": ["0xdef...", "0x...", "0x..."],
      "size": 4,
      "detectionMethod": "timing_correlation",
      "confidenceScore": 0.86,
      "outcomeAlignmentRate": 0.92,
      "totalCoordinatedTrades": 41,
      "isActive": true,
      "flaggedForReview": false
    }
  ],
  "pagination": { "limit": 50, "offset": 0, "totalCount": 112 }
}

Watchlist#

Manage the wallets you follow — the same list that powers /consensus and the trade.followed_wallet webhook.

GET /watchlist#

GET/api/v1/watchlist

List followed wallets

All wallets on your watchlist, newest first.

Response
{
  "data": [
    {
      "wallet": "0xdef...",
      "name": "sample-whale",
      "image": "https://...",
      "createdAt": "2026-05-01T09:00:00Z"
    }
  ]
}

POST /watchlist#

POST/api/v1/watchlist

Follow a wallet

Adds a wallet to your watchlist (idempotent upsert). Maximum 1,000 entries per account.

Request body
{
  "wallet": "0xdef...",
  "name": "sample-whale"
}
Response
{
  "data": {
    "wallet": "0xdef...",
    "name": "sample-whale",
    "image": null,
    "createdAt": "2026-07-18T09:00:00Z"
  }
}

DELETE /watchlist#

DELETE/api/v1/watchlist

Unfollow a wallet

Removes a wallet from your watchlist. Pass the wallet as a query parameter: ?wallet=0x….

  • walletstring · queryRequired

    Wallet address to unfollow.

Response
{ "data": { "deleted": true, "wallet": "0xdef..." } }

Oracle#

The Prophet — daily newspaper-style market predictions.

GET /oracle#

GET/api/v1/oracle

List Oracle editions

Daily editions of The Prophet, newest first.

  • limitinteger · query

    Default 30, max 90.

  • beforeISO 8601 · query

    Editions strictly before this date.

  • afterISO 8601 · query

    Editions strictly after this date.

Response
{
  "data": [
    {
      "id": "oracle_2026-05-09",
      "editionDate": "2026-05-09",
      "volumeNumber": 142,
      "generatedAt": "2026-05-09T11:00:00Z",
      "articles": [ /* full article objects */ ]
    }
  ],
  "pagination": { "limit": 30, "count": 30 }
}

Black Swan#

Geopolitical and macro risk-anomaly radar.

GET /black-swan#

GET/api/v1/black-swan

Get latest radar snapshot

The most recent cached Black Swan radar snapshot. Returns 503 if no snapshot has been generated yet.

Response
{
  "data": {
    "buckets": [ /* risk buckets */ ],
    "topRisks": [ /* highest-risk markets */ ]
  },
  "updatedAt": "2026-05-09T13:42:00Z"
}

Bitcoin Forecast#

Prediction-market-implied Bitcoin price distributions: hourly model runs over every open Polymarket + Kalshi BTC price market, with per-market weights and a rolling year of run history for backtesting. Every payload carries modelVersion so model changes are detectable in your data; the full specification and version history are in the Developer portal for Quant subscribers (polywhaler.com/developer/methodology#bitcoin-forecast). btc-forecast/1.1.0 changed the fit (markets on a common time scale, near-certain prices left out), spreadPct, the platform prior and the insight types, so segment backtests by modelVersion.

GET /btc-forecast#

GET/api/v1/btc-forecast

Get the latest forecast

The most recent model run: spot price, overall confidence, the full 1w/1m/3m/6m/1y horizon ladder (median, mean, 50/80/95% intervals, Bear/Base/Bull scenarios, implied vol, key-level probabilities), plus computed insights and consistency flags. Horizons with synthetic: true have no markets expiring nearby and are interpolated from the fitted maturities around them; their confidence is discounted accordingly. Density grids and the daily fan path are opt-in via include.

  • horizonsstring · query

    Comma-separated subset of 1w,1m,3m,6m,1y. Default: all.

  • includestring · query

    Comma-separated extras: density (120-point probability density per horizon), fan (daily median + quantile bands out to 1y).

Response
{
  "data": {
    "runAt": "2026-07-19T15:05:00Z",
    "spot": 64491,
    "spotSource": "coingecko",
    "modelVersion": "btc-forecast/1.1.0",
    "marketCount": 508,
    "confidence": 81,
    "horizons": [
      {
        "key": "1m",
        "targetDate": "2026-08-18T15:05:00Z",
        "tYears": 0.082,
        "median": 62570,
        "mean": 63640,
        "ci50": [56900, 68800],
        "ci80": [46569, 84070],
        "ci95": [40100, 97600],
        "scenarios": { "bear": 46569, "base": 62570, "bull": 84070 },
        "sigmaAnnual": 0.8,
        "sigmaBorrowed": true,
        "synthetic": true,
        "confidence": 68,
        "nPoints": 0,
        "nClusters": 0,
        "rmse": 0,
        "ess": 0,
        "keyLevels": [
          { "level": 60000, "pAbove": 0.58 },
          { "level": 70000, "pAbove": 0.28 }
        ]
      }
    ],
    "insights": [
      { "type": "divergence", "text": "Polymarket and Kalshi disagree near $61,500: 83% vs 90% for the same level and horizon." }
    ],
    "flags": []
  }
}

GET /btc-forecast/history#

GET/api/v1/btc-forecast/history

List past forecast runs

Hourly model runs, newest first, with compact per-horizon summaries (no density grids or key levels). Runs are kept for 365 days. Built for backtesting: join each run's intervals against realized prices to score calibration. Use nextCursor as before for the next (older) page.

  • limitinteger · query

    Default 24, max 168 (one week of hourly runs).

  • beforestring · query

    ISO 8601: only runs strictly before this time.

  • afterstring · query

    ISO 8601: only runs strictly after this time.

  • horizonsstring · query

    Comma-separated subset of 1w,1m,3m,6m,1y. Default: all.

Response
{
  "data": [
    {
      "runAt": "2026-07-19T15:05:00Z",
      "spot": 64491,
      "spotSource": "coingecko",
      "modelVersion": "btc-forecast/1.1.0",
      "marketCount": 508,
      "confidence": 81,
      "horizons": [
        {
          "key": "1w",
          "targetDate": "2026-07-23T09:41:00Z",
          "median": 64329,
          "ci80": [58929, 70224],
          "sigmaAnnual": 0.63,
          "synthetic": false,
          "confidence": 81
        }
      ]
    }
  ],
  "pagination": { "limit": 24, "count": 24, "nextCursor": "2026-07-18T16:05:00Z" }
}

GET /btc-forecast/markets#

GET/api/v1/btc-forecast/markets

List supporting markets

Every market feeding the latest run, ordered by model weight descending, with the full weight-factor breakdown (liquidity, volume, spread, freshness, open interest, kind penalty, platform prior, cluster haircut, consistency penalty). spreadPct is the bid-ask spread relative to the price of the cheaper side of the book, and a tighter spread earns a higher spreadScore. derived: true marks touch markets converted to an ending-price estimate; flagged: true marks markets penalized by consistency checks. Markets priced as near-certain either way are left out of the fit and do not appear. Directional up-or-down dailies appear with weight 0 (insights side-channel only).

  • platformstring · query

    polymarket or kalshi. Default: both.

  • horizonstring · query

    One of 1w,1m,3m,6m,1y. Default: all.

Response
{
  "data": {
    "runAt": "2026-07-19T15:05:00Z",
    "spot": 64491,
    "modelVersion": "btc-forecast/1.1.0",
    "markets": [
      {
        "platform": "kalshi",
        "marketId": "KXBTCD-26JUL1917-T64249.99",
        "eventId": "KXBTCD-26JUL1917",
        "title": "Bitcoin price today at 5pm EDT? (64,250 or above)",
        "url": "https://kalshi.com/markets/kxbtcd",
        "kind": "terminal_above",
        "strikeLow": 64249.99,
        "strikeHigh": null,
        "expiry": "2026-07-19T21:05:00Z",
        "prob": 0.62,
        "probDebiased": 0.632,
        "bid": 0.61,
        "ask": 0.63,
        "spreadPct": 0.053,
        "liquidity": null,
        "volume24h": 114329.95,
        "volumeTotal": 214100,
        "openInterest": 88400,
        "weight": 0.45,
        "weightFactors": {
          "liquidity": 0.4, "volume": 0.94, "spreadScore": 0.47,
          "freshness": 1, "oiScore": 0.9, "kindPenalty": 1,
          "platformPrior": 1, "clusterHaircut": 0.71, "consistencyPenalty": 1
        },
        "contribution": 0.012,
        "horizon": "1w",
        "derived": false,
        "flagged": false
      }
    ]
  },
  "pagination": { "count": 348 }
}

Exports#

Bulk CSV / JSON / Parquet exports of trade + outcome data, processed asynchronously.

GET /exports#

GET/api/v1/exports

List your export jobs

Most recent first.

  • limitinteger · query

    Default 20, max 100.

Response
{
  "data": [
    {
      "id": "0b6e3f4a-9d2c-4c8e-b1a7-5e9f2d3c6a18",
      "status": "completed",
      "format": "csv",
      "dataset": "trades",
      "rowCount": 24500,
      "fileUrl": "https://abc123.public.blob.vercel-storage.com/exports/you_example.com/0b6e3f4a-9d2c-4c8e-b1a7-5e9f2d3c6a18.csv",
      "fileSizeBytes": 4318211,
      "requestedAt": "2026-05-09T12:00:00Z",
      "completedAt": "2026-05-09T12:01:42Z",
      "expiresAt": "2026-05-16T12:01:42Z"
    }
  ]
}

POST /exports#

POST/api/v1/exports

Queue a new export

Returns 202 with a job ID. Limit 3 concurrent queued/processing jobs per user. format is csv (default), json, or parquet. dataset selects what to export: trades (default; filters: from, to, wallet, category, side, min_amount, min_insider_score, event_slug, market_slug) or outcomes (resolved trade results; filters: from, to, wallet, event_slug, status, vindicated). Exports cover Polymarket data, up to 100,000 rows per job. Subscribe to the export.completed webhook to get the download link pushed instead of polling.

Request body
{
  "format": "csv",
  "dataset": "trades",
  "filters": {
    "from": "2026-01-01",
    "to": "2026-05-09",
    "category": "politics",
    "min_amount": 25000
  }
}
Response
{
  "data": {
    "id": "0b6e3f4a-9d2c-4c8e-b1a7-5e9f2d3c6a18",
    "status": "queued",
    "format": "csv",
    "dataset": "trades",
    "filters": { "category": "politics", "min_amount": 25000 },
    "requestedAt": "2026-05-09T13:00:00Z"
  }
}

GET /exports/{id}#

GET/api/v1/exports/{id}

Poll export job status

Polling-friendly. Once status is completed, fileUrl is a download link and expiresAt its expiry time, 7 days after the job finished. Download the file before then.

  • idstring · pathRequired

    Export job ID returned from POST.

Response
{
  "data": {
    "id": "0b6e3f4a-9d2c-4c8e-b1a7-5e9f2d3c6a18",
    "status": "processing",
    "format": "csv",
    "startedAt": "2026-05-09T13:00:01Z"
  }
}

Webhooks#

Manage outbound webhook subscriptions programmatically.

GET /webhooks#

GET/api/v1/webhooks

List webhook subscriptions

All webhook subscriptions for your account.

Response
{
  "data": [
    {
      "id": "3c9a7e21-5f4b-4d8a-a6e2-7b1d0c9f4e53",
      "url": "https://example.com/polywhaler",
      "events": ["trade.high_insider", "cluster.detected"],
      "active": true,
      "description": "Production alert relay",
      "createdAt": "2026-05-01T09:00:00Z"
    }
  ]
}

POST /webhooks#

POST/api/v1/webhooks

Create a webhook subscription

Returns the signing secret in the response — store it immediately, it's never returned again. Optional filters narrow trade.* deliveries server-side: min_amount (USD floor), categories (allowlist), min_insider_score (0-100). Non-trade events ignore filters. Update or clear them later via PATCH.

Request body
{
  "url": "https://example.com/polywhaler",
  "events": ["trade.high_insider", "cluster.detected"],
  "description": "Production alert relay",
  "filters": { "min_amount": 50000, "categories": ["politics", "geopolitics"] }
}
Response
{
  "data": {
    "id": "3c9a7e21-5f4b-4d8a-a6e2-7b1d0c9f4e53",
    "url": "https://example.com/polywhaler",
    "events": ["trade.high_insider", "cluster.detected"],
    "active": true,
    "filters": { "min_amount": 50000, "categories": ["politics", "geopolitics"] }
  },
  "secret": "pwsk_4a2b...",
  "secretWarning": "This secret will not be shown again. Save it now."
}

GET /webhooks/{id}#

GET/api/v1/webhooks/{id}

Get a subscription + delivery log

Returns the subscription with its 20 most recent delivery attempts.

  • idstring · pathRequired

    Webhook subscription ID.

Response
{
  "data": {
    "id": "3c9a7e21-5f4b-4d8a-a6e2-7b1d0c9f4e53",
    "url": "https://example.com/polywhaler",
    "events": ["trade.high_insider"],
    "active": true,
    "recentDeliveries": [
      { "id": "d4e8b2c1-7a3f-4e9b-8c5d-2f6a1b0e9d37", "event": "trade.high_insider", "statusCode": 200, "attempt": 1, "deliveredAt": "..." }
    ]
  }
}

PATCH /webhooks/{id}#

PATCH/api/v1/webhooks/{id}

Update a webhook subscription

All fields optional; provide at least one. filters: null (or {}) clears delivery filters.

  • idstring · pathRequired

    Webhook subscription ID.

Request body
{
  "events": ["trade.high_insider", "oracle.published"],
  "active": true,
  "filters": { "min_amount": 25000 }
}
Response
{
  "data": {
    "id": "3c9a7e21-5f4b-4d8a-a6e2-7b1d0c9f4e53",
    "events": ["trade.high_insider", "oracle.published"],
    "active": true
  }
}

POST /webhooks/{id}/test#

POST/api/v1/webhooks/{id}/test

Send a test delivery

Fires a signed sample payload at the subscription's endpoint so you can verify wiring and signature handling before going live. The body carries test: true and the request an X-Polywhaler-Test: true header. Not recorded in the delivery log and never retried.

  • idstring · pathRequired

    Webhook subscription ID.

Request body
{
  "event": "trade.high_insider"
}
Response
{
  "data": {
    "delivered": true,
    "event": "trade.high_insider",
    "statusCode": 200,
    "error": null
  }
}

DELETE /webhooks/{id}#

DELETE/api/v1/webhooks/{id}

Delete a webhook subscription

Permanent. Delivery history is retained for audit.

  • idstring · pathRequired

    Webhook subscription ID.

Response
{ "data": { "deleted": true } }

Meta#

Account introspection and machine-readable API metadata.

GET /usage#

GET/api/v1/usage

Get quota + account usage

Your current daily quota usage, the authenticated key's details, and account-level counts (active keys, webhooks, pending exports). This endpoint does NOT count against your quota, so it's safe to poll.

Response
{
  "data": {
    "tier": "quant",
    "quota": {
      "dailyLimit": 10000,
      "usedToday": 1420,
      "remaining": 8580,
      "resetsAt": "2026-07-19T00:00:00.000Z",
      "burstLimitPerMinute": 100
    },
    "key": {
      "id": "9a2d4f6b-1c3e-4a5b-8d7f-0e2c4b6a8d91",
      "name": "production",
      "prefix": "pwhk_a1b2c3d",
      "createdAt": "2026-05-01T09:00:00Z",
      "lastUsedAt": "2026-07-18T09:12:33Z"
    },
    "account": { "activeKeys": 2, "activeWebhooks": 3, "pendingExports": 0 }
  }
}

GET /openapi.json#

GET/api/v1/openapi.json

Get the OpenAPI 3.1 spec

Machine-readable OpenAPI 3.1 document covering every endpoint and webhook event, generated from the same catalog as this reference. Served without authentication so SDK generators and API tooling can consume it directly.

Response
{
  "openapi": "3.1.0",
  "info": { "title": "Polywhaler Quant API", "version": "1.0.0" },
  "servers": [{ "url": "https://polywhaler.com/api/v1" }],
  "paths": { /* every endpoint in this reference */ },
  "webhooks": { /* every webhook event */ }
}