API reference

Every endpoint of the Polywhaler REST API, v2: whale trades, Smart Money rankings, wallets, signals, exports and webhooks. 47 operations, generated from the same definitions that serve the API, so the parameters and fields here are the ones it accepts and returns.

All endpoints47

Kept in this tab's memory only, and sent only with the requests you try. Each request counts against that key's quota.

No key yet? The API is part of the Quant plan. Create one in the Developer portal. Without a key, each endpoint still shows an example response.

Authentication#

Send your API key as a bearer token: Authorization: Bearer pwhk_…. Keys belong to Quant accounts; a key on a free or Pro account answers 403 plan_required rather than an empty result. Create, rename, rotate and revoke keys in the Developer portal. A key is shown once, when it is created.

Each key carries scopes. Read-only keys hold just the first; full-access keys hold all four.

  • read: every GET.
  • watchlist:write: add and remove watchlist wallets.
  • webhooks:write: create, change, test and delete webhook endpoints.
  • exports:write: start bulk exports.

A request without the scope it needs answers 403 insufficient_scope. Endpoints marked No key needed (the OpenAPI document and the event-type catalog) are public.

Base URL and versions#

Every v2 endpoint lives under https://polywhaler.com/api/v2. The machine-readable spec is at /api/v2/openapi.json (OpenAPI 3.1, public), for code generators and API clients.

v1 (/api/v1) is frozen: it gets security fixes, nothing new. Its last day is announced in the changelog and on every v1 response (Deprecation and Sunset headers) six months ahead. Migrating from v1 maps each v1 route to its v2 successor.

Responses#

Every success is one envelope: data, plus meta where a route has context to add and pagination on lists. Parameters and fields are camelCase. Rates and probabilities are fractions from 0 to 1 (scores stay 0 to 100), money is in US dollars in fields ending Usd, times are ISO-8601 instants in UTC, and wallets are lowercase 0x addresses.

Every response, errors included, carries X-Request-Id; quote it when you write to support. GETs send an ETag: send it back as If-None-Match and an unchanged resource answers 304 with no body.

Errors#

Every error has the same shape, with a stable code to branch on, the offending param when there is one, and a link to its documentation.

400 invalid_param
{
  "error": {
    "type": "invalid_request_error",
    "code": "invalid_param",
    "message": "limit must be at most 500",
    "param": "limit",
    "docUrl": "https://polywhaler.com/docs/api/v2/errors#invalid_param",
    "requestId": "req_7d0c8e1f2a3b4c5d6e7f8091a2b3c4d5"
  }
}

Unknown query parameters are refused (400 unknown_param), so a typo never silently widens a query. The full list of codes is on API errors.

Pagination#

List endpoints page with an opaque cursor. Ask for up to limit items; when pagination.hasMore is true, pass pagination.nextCursor back as cursor with the same filters for the next page. Cursors are signed: build no logic on their contents, and expect a 400 invalid_cursor if one is changed or reused with other filters. Pages never skip or repeat trades that share a timestamp.

Every trade, page by page
let cursor = null
do {
  const url = new URL("https://polywhaler.com/api/v2/trades")
  url.searchParams.set("limit", "500")
  if (cursor) url.searchParams.set("cursor", cursor)
  const res = await fetch(url, {
    headers: { Authorization: `Bearer ${process.env.POLYWHALER_API_KEY}` },
  })
  const { data, pagination } = await res.json()
  handle(data)
  cursor = pagination.hasMore ? pagination.nextCursor : null
} while (cursor)

Rate limits#

A Quant account gets 10,000 requests a day, reset at 00:00 UTC and shared by v1 and v2, and each key up to 100 requests a minute. Every keyed response says where you stand:

  • RateLimit-Limit, RateLimit-Remaining and RateLimit-Reset (seconds) for whichever window is closer to running out, and RateLimit-Policy for both.
  • X-RateLimit-Limit, X-RateLimit-Remaining and X-RateLimit-Reset (epoch seconds) for the daily quota.

Past either limit the API answers 429 (rate_limited for the minute, quota_exceeded for the day) with Retry-After. GET /v2/usage reports the same numbers and does not count against the daily quota.

Idempotency#

POSTs that create something (an export, a webhook endpoint, a rotated secret) accept an Idempotency-Key header. Retrying with the same key and the same request within 24 hours replays the first response (marked Idempotent-Replayed: true) instead of creating a second one; the same key with a different request answers 409. A replayed response never repeats a secret.

Trades#

2 endpoints

List whale trades#

GET/v2/trades
scope: readCursor paginatedCached 15s

Whale fills ($10k and up on Polymarket; Kalshi under the dashboard feed's floors), newest first. Kalshi trades have wallet: null and a trader.kalshiId. wallet and minInsiderScore need on-chain wallets, so they require venue=polymarket.

Query parameters

  • venueenumdefault polymarket

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

  • walletstring

    Only this wallet's trades (Polymarket only).

    pattern ^0x[0-9a-fA-F]{40}$

  • categorystring

    Comma-separated categories, any of: crypto, politics, economy, geopolitics, tech, culture, science, sports, esports, other, markets.

  • sideenum

    Taker side: BUY or SELL.

  • minAmountnumber

    Minimum fill size in USD, inclusive.

    min 0

  • minInsiderScorenumber

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

    0..100

  • maxProbabilitynumber

    Only fills priced below this probability (0..1, exclusive): long shots.

    0..1

  • eventSlugstring

    Polymarket event slug, or a Kalshi event ticker.

    at most 200 characters · pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$

  • marketSlugstring

    Polymarket market slug, or a Kalshi market ticker.

    at most 200 characters · pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$

  • sportsenumdefault include

    Sports and esports markets: include them, exclude them, or only them.

  • fromstring (date-time)

    Trades at or after this instant.

  • tostring (date-time)

    Trades before this instant.

  • includestring

    smartMoney adds each Polymarket wallet's standing on the Smart Money board (null when it is not on it).

  • limitintegerdefault 100

    Page size, 1 to 500.

    1..500

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • idstring

    Trade id: the transaction hash on Polymarket, kx_<tradeId> on Kalshi. Use with GET /v2/trades/{id}.

  • sideenum

    Taker side of the fill.

    One ofBUYSELL

  • outcomestring | null

    The outcome bought or sold, e.g. Yes.

  • outcomeIndexinteger | null
  • priceProbabilitynumber

    Fill price, which is the implied probability of outcome.

  • sizeSharesnumber

    Shares (Polymarket) or contracts (Kalshi) filled.

  • amountUsdnumber

    Notional of the fill: sizeShares × priceProbability.

  • tradedAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • marketobject

    The market the trade was in.

  • market.titlestring | null

    The market's question.

  • market.eventSlugstring | null

    Polymarket event slug, or the Kalshi event ticker.

  • market.marketSlugstring | null

    Polymarket market slug, or the Kalshi market ticker.

  • market.conditionIdstring | null

    Polymarket condition id. Null on Kalshi.

  • market.assetstring | null

    Polymarket outcome token id. Null on Kalshi.

Show 39 more fields
  • market.tickerstring | null

    Kalshi market ticker. Null on Polymarket.

  • market.iconstring | null
  • market.categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • market.tagsany[]

    Polymarket event tags, as Polymarket returns them.

  • market.endsAtstring (date-time) | null

    Scheduled end of the market, when known.

  • venue"polymarket" | "kalshi"
  • transactionHashstring | null

    An on-chain transaction hash, lowercase.

  • walletstring | null

    A Polygon wallet address, always lowercase.

  • traderobject

    Display identity of the wallet's Polymarket account.

  • trader.namestring | null

    Polymarket display name, when set.

  • trader.pseudonymstring | null

    Polymarket's generated pseudonym.

  • trader.imagestring | null

    Profile image URL.

  • insiderobject | null
  • insider.scorenumber | null

    Insider-risk score, 0..100: above 40 is medium, above 70 high. Null when the trade has not been scored (only Polymarket buys are).

  • insider.breakdownobject

    The insider score's factors, in points. Each factor is capped, and together they add up to score, which is capped at 100 (sports trades are capped lower, below the high band).

  • insider.breakdown.convictionnumber | null

    A large position at long odds. Points, at most 35.

  • insider.breakdown.priceImpactnumber | null

    How far the trade moved the price. No longer scored: 0 on trades scored now, kept for older trades.

  • insider.breakdown.tradeSizenumber | null

    The trade's size on its own. No longer scored: 0 on trades scored now, kept for older trades.

  • insider.breakdown.marketDominancenumber | null

    The trade's size against the market's trading over the previous 24 hours. Points, at most 20.

  • insider.breakdown.timingnumber | null

    How close to the market's resolution the trade came, or a large early position on a distant event. Points, at most 10.

  • insider.breakdown.walletAgenumber | null

    How new the wallet was when it traded. Points, at most 15.

  • insider.breakdown.contextnumber | null

    How much the market's kind of event lends itself to inside knowledge. Points, at most 20.

  • insider.detailsobject | null

    The full scoring record (GET /v2/trades/{id} only). Its fields may grow; treat it as informational.

  • smartMoneyobject | null

    Present with include=smartMoney: null when the wallet is not on the board.

  • smartMoney.rankinteger | null

    The wallet's Smart Money rank (1 = best), null while provisional.

  • smartMoney.scorenumber

    The wallet's smoothed Smart Money Score.

  • smartMoney.tierenum

    Badge band: genius 80+ (methodology 4 also needs a minimum of realized edge in dollars), smart 60+, average 40+, naive below.

  • smartMoney.styleenum | null

    Trading style; null under methodology 3, which has none.

    One oflong-shotbalancedfavoriteunknown

  • smartMoney.methodologyVersioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • resolutionobject | null
  • resolution.statusenum

    One ofwinlosspendingcancelledinvalid

  • resolution.winningOutcomestring | null
  • resolution.entryPricenumber | null
  • resolution.exitPricenumber | null
  • resolution.pnlUsdnumber | null
  • resolution.roinumber | null
  • resolution.resolvedAtstring (date-time) | null
  • resolution.holdSecondsinteger | null
  • resolution.vindicatedboolean

    A high-insider-score trade that won.

meta

  • venueenum

    One ofpolymarketkalshiall

curl 'https://polywhaler.com/api/v2/trades?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "venue": "polymarket",
      "transactionHash": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
      "trader": {
        "name": "sample-whale",
        "pseudonym": "Sample-Heron",
        "image": null
      },
      "side": "BUY",
      "outcome": "Yes",
      "outcomeIndex": 0,
      "priceProbability": 0.42,
      "sizeShares": 50000,
      "amountUsd": 21000,
      "tradedAt": "2026-10-09T14:03:27.000Z",
      "market": {
        "title": "Will BTC close above $130k on Oct 31?",
        "eventSlug": "bitcoin-above-on-october-31",
        "marketSlug": "bitcoin-above-130k-on-october-31",
        "conditionId": "0x1d4e",
        "asset": "51234",
        "ticker": null,
        "icon": null,
        "category": "crypto",
        "tags": [],
        "endsAt": "2026-10-31T16:00:00.000Z"
      },
      "insider": {
        "score": 64,
        "breakdown": {
          "conviction": 28,
          "priceImpact": 0,
          "tradeSize": 0,
          "marketDominance": 13,
          "timing": 5,
          "walletAge": 8,
          "context": 10
        }
      }
    }
  ],
  "meta": {
    "venue": "polymarket"
  },
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Get one trade#

GET/v2/trades/{id}
scope: readCached 15s

A trade by its transaction hash (Polymarket) or kx_<trade_id> (Kalshi), with the full insider scoring record and, once the market resolved, how the trade did.

Path parameters

  • idstringRequiredpath

    A transaction hash, or kx_<trade_id> for Kalshi.

    at most 100 characters

Query parameters

  • includestring

    smartMoney adds each Polymarket wallet's standing on the Smart Money board (null when it is not on it).

Response

data
  • idstring

    Trade id: the transaction hash on Polymarket, kx_<tradeId> on Kalshi. Use with GET /v2/trades/{id}.

  • sideenum

    Taker side of the fill.

    One ofBUYSELL

  • outcomestring | null

    The outcome bought or sold, e.g. Yes.

  • outcomeIndexinteger | null
  • priceProbabilitynumber

    Fill price, which is the implied probability of outcome.

  • sizeSharesnumber

    Shares (Polymarket) or contracts (Kalshi) filled.

  • amountUsdnumber

    Notional of the fill: sizeShares × priceProbability.

  • tradedAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • marketobject

    The market the trade was in.

  • market.titlestring | null

    The market's question.

  • market.eventSlugstring | null

    Polymarket event slug, or the Kalshi event ticker.

  • market.marketSlugstring | null

    Polymarket market slug, or the Kalshi market ticker.

  • market.conditionIdstring | null

    Polymarket condition id. Null on Kalshi.

  • market.assetstring | null

    Polymarket outcome token id. Null on Kalshi.

Show 39 more fields
  • market.tickerstring | null

    Kalshi market ticker. Null on Polymarket.

  • market.iconstring | null
  • market.categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • market.tagsany[]

    Polymarket event tags, as Polymarket returns them.

  • market.endsAtstring (date-time) | null

    Scheduled end of the market, when known.

  • venue"polymarket" | "kalshi"
  • transactionHashstring | null

    An on-chain transaction hash, lowercase.

  • walletstring | null

    A Polygon wallet address, always lowercase.

  • traderobject

    Display identity of the wallet's Polymarket account.

  • trader.namestring | null

    Polymarket display name, when set.

  • trader.pseudonymstring | null

    Polymarket's generated pseudonym.

  • trader.imagestring | null

    Profile image URL.

  • insiderobject | null
  • insider.scorenumber | null

    Insider-risk score, 0..100: above 40 is medium, above 70 high. Null when the trade has not been scored (only Polymarket buys are).

  • insider.breakdownobject

    The insider score's factors, in points. Each factor is capped, and together they add up to score, which is capped at 100 (sports trades are capped lower, below the high band).

  • insider.breakdown.convictionnumber | null

    A large position at long odds. Points, at most 35.

  • insider.breakdown.priceImpactnumber | null

    How far the trade moved the price. No longer scored: 0 on trades scored now, kept for older trades.

  • insider.breakdown.tradeSizenumber | null

    The trade's size on its own. No longer scored: 0 on trades scored now, kept for older trades.

  • insider.breakdown.marketDominancenumber | null

    The trade's size against the market's trading over the previous 24 hours. Points, at most 20.

  • insider.breakdown.timingnumber | null

    How close to the market's resolution the trade came, or a large early position on a distant event. Points, at most 10.

  • insider.breakdown.walletAgenumber | null

    How new the wallet was when it traded. Points, at most 15.

  • insider.breakdown.contextnumber | null

    How much the market's kind of event lends itself to inside knowledge. Points, at most 20.

  • insider.detailsobject | null

    The full scoring record (GET /v2/trades/{id} only). Its fields may grow; treat it as informational.

  • smartMoneyobject | null

    Present with include=smartMoney: null when the wallet is not on the board.

  • smartMoney.rankinteger | null

    The wallet's Smart Money rank (1 = best), null while provisional.

  • smartMoney.scorenumber

    The wallet's smoothed Smart Money Score.

  • smartMoney.tierenum

    Badge band: genius 80+ (methodology 4 also needs a minimum of realized edge in dollars), smart 60+, average 40+, naive below.

  • smartMoney.styleenum | null

    Trading style; null under methodology 3, which has none.

    One oflong-shotbalancedfavoriteunknown

  • smartMoney.methodologyVersioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • resolutionobject | null
  • resolution.statusenum

    One ofwinlosspendingcancelledinvalid

  • resolution.winningOutcomestring | null
  • resolution.entryPricenumber | null
  • resolution.exitPricenumber | null
  • resolution.pnlUsdnumber | null
  • resolution.roinumber | null
  • resolution.resolvedAtstring (date-time) | null
  • resolution.holdSecondsinteger | null
  • resolution.vindicatedboolean

    A high-insider-score trade that won.

curl 'https://polywhaler.com/api/v2/trades/0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
    "venue": "polymarket",
    "transactionHash": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
    "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
    "trader": {
      "name": "sample-whale",
      "pseudonym": "Sample-Heron",
      "image": null
    },
    "side": "BUY",
    "outcome": "Yes",
    "outcomeIndex": 0,
    "priceProbability": 0.42,
    "sizeShares": 50000,
    "amountUsd": 21000,
    "tradedAt": "2026-10-09T14:03:27.000Z",
    "market": {
      "title": "Will BTC close above $130k on Oct 31?",
      "eventSlug": "bitcoin-above-on-october-31",
      "marketSlug": "bitcoin-above-130k-on-october-31",
      "conditionId": "0x1d4e",
      "asset": "51234",
      "ticker": null,
      "icon": null,
      "category": "crypto",
      "tags": [],
      "endsAt": "2026-10-31T16:00:00.000Z"
    },
    "insider": {
      "score": 64,
      "breakdown": {
        "conviction": 28,
        "priceImpact": 0,
        "tradeSize": 0,
        "marketDominance": 13,
        "timing": 5,
        "walletAge": 8,
        "context": 10
      }
    }
  }
}

Smart Money#

5 endpoints

Get the active Smart Money methodology#

GET/v2/methodology
scope: readCached 300s

How the score the board currently ranks by is computed. Generated from the scoring code: the components with their weights, the constants, the tiers, the provisional rules and the formula. The example below is trimmed and its numbers are placeholders.

Response

data
  • versioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • namestring
  • activeboolean

    Whether the board currently ranks by this version.

  • componentsobject[]
  • components[].keystring

    The component's key in a score's components.

  • components[].weightnumber

    The component's share of the score; the weights add up to 1.

  • components[].rangeany[]

    The lowest and highest value the component takes.

  • components[].neutralnumber

    The component's no-edge value.

  • components[].descriptionstring
  • constantsobject

    The version's named constants, as the scoring code uses them.

  • tiersobject[]
  • tiers[].tierenum

    Badge band: genius 80+ (methodology 4 also needs a minimum of realized edge in dollars), smart 60+, average 40+, naive below.

  • tiers[].minScorenumber
  • tiers[].minDecayedEdgeUsdnumber
Show 9 more fields
  • stylesobject | null
  • styles.longShotBelownumber
  • styles.favoriteAbovenumber
  • provisionalobject
  • provisional.minDecisiveClosesinteger
  • provisional.minActiveWeeksinteger | null
  • provisional.minWhaleTradesinteger | null
  • provisional.reasonsstring[]
  • scorestring

    How the components combine into the score.

meta

  • activeVersioninteger

    The methodology the board currently ranks by.

  • versionsinteger[]

    Every published version (the active one and older), newest first.

curl 'https://polywhaler.com/api/v2/methodology' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "version": 3,
    "name": "recent-form",
    "active": true,
    "components": [
      {
        "key": "roi",
        "weight": 0,
        "range": [
          0,
          1
        ],
        "neutral": 0.5,
        "description": "Realized return on cost over closed positions"
      }
    ],
    "constants": {
      "EMA_ALPHA": 0
    },
    "tiers": [
      {
        "tier": "genius",
        "minScore": 80
      },
      {
        "tier": "smart",
        "minScore": 60
      },
      {
        "tier": "average",
        "minScore": 40
      },
      {
        "tier": "naive",
        "minScore": 0
      }
    ],
    "styles": null,
    "provisional": {
      "minDecisiveCloses": 0,
      "minActiveWeeks": null,
      "minWhaleTrades": null,
      "reasons": [
        "few_closes"
      ]
    },
    "score": "The formula that combines the components, the confidence and the cluster penalty into the score."
  },
  "meta": {
    "activeVersion": 4,
    "versions": [
      0
    ]
  }
}

Get a Smart Money methodology version#

GET/v2/methodology/{version}
scope: readCached 300s

One published methodology: the active version or an older one. A version newer than the active one is not published until the board ranks by it, and answers 404 not_found like a version that never existed; meta.versions lists the published ones. Generated from the scoring code: the components with their weights, the constants, the tiers, the provisional rules and the formula. The example below is trimmed and its numbers are placeholders.

Path parameters

  • versionintegerRequiredpath

    The methodology version.

    1..1000

Response

data
  • versioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • namestring
  • activeboolean

    Whether the board currently ranks by this version.

  • componentsobject[]
  • components[].keystring

    The component's key in a score's components.

  • components[].weightnumber

    The component's share of the score; the weights add up to 1.

  • components[].rangeany[]

    The lowest and highest value the component takes.

  • components[].neutralnumber

    The component's no-edge value.

  • components[].descriptionstring
  • constantsobject

    The version's named constants, as the scoring code uses them.

  • tiersobject[]
  • tiers[].tierenum

    Badge band: genius 80+ (methodology 4 also needs a minimum of realized edge in dollars), smart 60+, average 40+, naive below.

  • tiers[].minScorenumber
  • tiers[].minDecayedEdgeUsdnumber
Show 9 more fields
  • stylesobject | null
  • styles.longShotBelownumber
  • styles.favoriteAbovenumber
  • provisionalobject
  • provisional.minDecisiveClosesinteger
  • provisional.minActiveWeeksinteger | null
  • provisional.minWhaleTradesinteger | null
  • provisional.reasonsstring[]
  • scorestring

    How the components combine into the score.

meta

  • activeVersioninteger

    The methodology the board currently ranks by.

  • versionsinteger[]

    Every published version (the active one and older), newest first.

curl 'https://polywhaler.com/api/v2/methodology/3' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "version": 3,
    "name": "recent-form",
    "active": true,
    "components": [
      {
        "key": "roi",
        "weight": 0,
        "range": [
          0,
          1
        ],
        "neutral": 0.5,
        "description": "Realized return on cost over closed positions"
      }
    ],
    "constants": {
      "EMA_ALPHA": 0
    },
    "tiers": [
      {
        "tier": "genius",
        "minScore": 80
      },
      {
        "tier": "smart",
        "minScore": 60
      },
      {
        "tier": "average",
        "minScore": 40
      },
      {
        "tier": "naive",
        "minScore": 0
      }
    ],
    "styles": null,
    "provisional": {
      "minDecisiveCloses": 0,
      "minActiveWeeks": null,
      "minWhaleTrades": null,
      "reasons": [
        "few_closes"
      ]
    },
    "score": "The formula that combines the components, the confidence and the cluster penalty into the score."
  },
  "meta": {
    "activeVersion": 4,
    "versions": [
      0
    ]
  }
}

Get Smart Money capital flow#

GET/v2/smart-money/flow
scope: readCached 60s

Net whale flow by category, all whales against the smart tier (smoothed score 60+). Sports excluded.

Query parameters

  • rangeenumdefault 7d

    The window ending now.

    One of24h7d30d

Response

data
  • rangeenum

    One of24h7d30d

  • totalsobject
  • totals.netFlowUsdnumber

    US dollars.

  • totals.smartNetFlowUsdnumber

    US dollars.

  • totals.tradeCountinteger
  • categoriesobject[]
  • categories[].categorystring
  • categories[].netFlowUsdnumber

    Buys minus sells across all whales.

  • categories[].smartNetFlowUsdnumber

    Buys minus sells by the smart cohort.

  • categories[].buyVolumeUsdnumber

    US dollars.

  • categories[].sellVolumeUsdnumber

    US dollars.

  • categories[].tradeCountinteger
  • topAccumulatingobject[]

    Markets with the strongest smart-cohort net buying.

  • topAccumulating[].keystring
Show 7 more fields
  • topAccumulating[].titlestring
  • topAccumulating[].eventSlugstring | null
  • topAccumulating[].marketSlugstring | null
  • topAccumulating[].iconstring | null
  • topAccumulating[].categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • topAccumulating[].smartNetFlowUsdnumber

    US dollars.

  • topAccumulating[].tradeCountinteger

meta

  • methodologyVersioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • cohortobject
  • cohort.tier"smart"
  • cohort.minScorenumber
  • cohort.rankedOnlyboolean

    Methodology 4 leaves provisional wallets out of the cohort.

  • cohort.descriptionstring
curl 'https://polywhaler.com/api/v2/smart-money/flow' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "range": "24h",
    "totals": {
      "netFlowUsd": 0,
      "smartNetFlowUsd": 0,
      "tradeCount": 0
    },
    "categories": [
      {
        "category": "crypto",
        "netFlowUsd": 0,
        "smartNetFlowUsd": 0,
        "buyVolumeUsd": 0,
        "sellVolumeUsd": 0,
        "tradeCount": 0
      }
    ],
    "topAccumulating": [
      {
        "key": "string",
        "title": "Will BTC close above $130k on Oct 31?",
        "eventSlug": "bitcoin-above-on-october-31",
        "marketSlug": "bitcoin-above-130k-on-october-31",
        "icon": "string",
        "category": "crypto",
        "smartNetFlowUsd": 0,
        "tradeCount": 0
      }
    ]
  },
  "meta": {
    "methodologyVersion": 4,
    "cohort": {
      "tier": "smart",
      "minScore": 0,
      "rankedOnly": true,
      "description": "Trading desk"
    }
  }
}

List the Smart Money leaderboard#

GET/v2/smart-money/leaderboard
scope: readCursor paginatedCached 300s

Wallets by stored Smart Money rank. Provisional wallets (too little history to rank) are left out unless provisional=include. With the default sports=exclude, ranks are the board without sports traders.

Query parameters

  • stylestring

    Trading styles, comma-separated (methodology 4 only).

  • minScorenumber

    Minimum smoothed score.

    0..100

  • maxRankinteger

    Only ranks up to this one.

    1..100000

  • provisionalenumdefault exclude

    Whether to list provisional (unranked) wallets after the ranked ones.

    One ofexcludeinclude

  • sportsenumdefault exclude

    Sports and esports markets: include them, exclude them, or only them.

  • windowenumdefault all

    Which stats block (and P&L sort) to use: all time, or the last 90 days.

  • sortenumdefault rank

    rank ascending; score, pnlUsd and winRate descending.

  • limitintegerdefault 50

    Page size, 1 to 200.

    1..200

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • rankinteger | null

    Rank on the requested board (ex-sports by default); null for provisional rows.

  • rankChange1dinteger | null

    Places the wallet's overall rank moved since the previous daily snapshot (positive = up). Null when either rank is missing or the two were scored by different methodologies.

  • walletstring

    A Polygon wallet address, always lowercase.

  • traderobject

    Display identity of the wallet's Polymarket account.

  • trader.namestring | null

    Polymarket display name, when set.

  • trader.pseudonymstring | null

    Polymarket's generated pseudonym.

  • trader.imagestring | null

    Profile image URL.

  • smartMoneySmartMoney

    A wallet's Smart Money standing. Fields that only methodology 4 defines are null under methodology 3; methodologyVersion says which applies.

  • smartMoney.scorenumber

    Smoothed Smart Money Score, the value the board ranks by. 50 = no demonstrated edge.

  • smartMoney.scoreRawnumber

    Today's unsmoothed score.

  • smartMoney.rankinteger | null

    Rank across every scored wallet (1 = best); null while provisional.

  • smartMoney.rankExSportsinteger | null

    Rank among wallets that do not trade sports, which is the default board.

  • smartMoney.tierenum

    Badge band: genius 80+ (methodology 4 also needs a minimum of realized edge in dollars), smart 60+, average 40+, naive below.

  • smartMoney.provisionalboolean

    A provisional score is shown but carries no rank and no signal.

Show 29 more fields
  • smartMoney.provisionalReasonenum | null

    One offew_closesfew_weeksfew_whale_tradespnl_unverifiedunscored

  • smartMoney.confidencenumber | null

    How much of the skill estimate the score keeps, from the amount of evidence.

  • smartMoney.clusterPenaltynumber | null

    Points subtracted for membership in a coordinated-trading cluster.

  • smartMoney.methodologyVersion4 | 3
  • smartMoney.styleenum | null

    Where the wallet's entry prices usually sit, weighted by stake: long-shot (low prices), favorite (high prices), balanced (in between), unknown (no entry prices to read). Methodology 4 only.

  • smartMoney.componentsobject

    Methodology 4 components, each −1..1 with 0 = no edge.

  • smartMoney.components.edgenumber

    Weekly profit against the prices paid, compared with what chance alone would produce.

  • smartMoney.components.hitnumber

    How often positions win compared with what their entry prices implied.

  • smartMoney.components.recentnumber

    The same edge over the last few weeks only.

  • smartMoney.components.roinumber

    Profit per dollar staked, recent weeks counting most.

  • smartMoney.components.sizenumber

    Realized edge in dollars, counted only to the extent the edge repeats.

  • smartMoney.zEdgenumber | null

    How far the weekly edge stands out from chance, in standard deviations (0 = none).

  • smartMoney.zHitnumber | null

    How far the win rate stands out from what the entry prices implied, in standard deviations.

  • smartMoney.zRecentnumber | null

    zEdge over the last few weeks only.

  • smartMoney.evidenceWeeksnumber | null

    Active settlement weeks behind the score, older weeks counting for less.

  • smartMoney.topWeekSharenumber | null

    Share of positive weekly P&L earned in the single best week.

  • smartMoney.bestWeekstring (date) | null

    Monday (UTC) of the best settlement week.

  • smartMoney.bestWeekPnlUsdnumber | null
  • smartMoney.decayedEdgeUsdnumber | null

    Realized edge in dollars after an allowance for the cost of copying, recent weeks counting most. The Genius tier needs a minimum of it.

  • smartMoney.lifetimeCappedboolean | null

    The wallet has lost money over its lifetime, so its score is capped just above neutral.

  • statsobject
  • stats.windowenum

    Which record the stats cover.

    One ofall90d

  • stats.winRatenumber | null
  • stats.decisivePositionsinteger | null
  • stats.roinumber | null
  • stats.pnlUsdnumber | null
  • stats.recentPnlUsdnumber | null
  • stats.lastTradeAtstring (date-time) | null
  • followerCountinteger

    Polywhaler users following the wallet.

meta

  • methodologyVersioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • windowenum

    One ofall90d

  • sortenum

    One ofrankscorepnlUsdwinRate

  • sportsenum

    One ofincludeexcludeonly

curl 'https://polywhaler.com/api/v2/smart-money/leaderboard?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "rank": 1,
      "rankChange1d": 0,
      "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
      "trader": {
        "name": "sample-whale",
        "pseudonym": "Sample-Heron",
        "image": null
      },
      "smartMoney": {
        "score": 64,
        "scoreRaw": 0,
        "rank": 1,
        "rankExSports": 1,
        "tier": "genius",
        "provisional": true,
        "provisionalReason": "few_closes",
        "confidence": 0,
        "clusterPenalty": 0,
        "methodologyVersion": 4,
        "style": "long-shot",
        "components": {
          "edge": 0,
          "hit": 0,
          "recent": 0,
          "roi": 0,
          "size": 0
        },
        "zEdge": 0,
        "zHit": 0,
        "zRecent": 0,
        "evidenceWeeks": 0,
        "topWeekShare": 0,
        "bestWeek": "2026-10-09",
        "bestWeekPnlUsd": 0,
        "decayedEdgeUsd": 0,
        "lifetimeCapped": true
      },
      "stats": {
        "window": "all",
        "winRate": 0,
        "decisivePositions": 0,
        "roi": 0,
        "pnlUsd": 0,
        "recentPnlUsd": 0,
        "lastTradeAt": "2026-10-09T14:03:27.000Z"
      },
      "followerCount": 0
    }
  ],
  "meta": {
    "methodologyVersion": 4,
    "window": "all",
    "sort": "rank",
    "sports": "include"
  },
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

List trades by the Smart Money cohort#

GET/v2/smart-money/trades
scope: readCursor paginatedCached 15s

Whale trades by ranked wallets: the top maxRank (default 100, at most 1000), or every ranked wallet with a smoothed score of minScore or more (the best-ranked 1000 of them). Each trade carries the wallet's rank, score and style. Polymarket only.

Query parameters

  • maxRankinteger

    Cohort: ranks 1 to this (default 100).

    1..1000

  • minScorenumber

    Cohort: ranked wallets with at least this smoothed score.

    0..100

  • categorystring

    Comma-separated categories, any of: crypto, politics, economy, geopolitics, tech, culture, science, sports, esports, other, markets.

  • sideenum

    Taker side: BUY or SELL.

  • minAmountnumber

    Minimum fill size in USD, inclusive.

    min 0

  • maxProbabilitynumber

    Only fills priced below this probability.

    0..1

  • eventSlugstring

    at most 200 characters · pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$

  • marketSlugstring

    at most 200 characters · pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$

  • sportsenumdefault include

    Sports and esports markets: include them, exclude them, or only them.

  • fromstring (date-time)

    Trades at or after this instant.

  • tostring (date-time)

    Trades before this instant.

  • limitintegerdefault 100

    Page size, 1 to 500.

    1..500

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • idstring

    Trade id: the transaction hash on Polymarket, kx_<tradeId> on Kalshi. Use with GET /v2/trades/{id}.

  • sideenum

    Taker side of the fill.

    One ofBUYSELL

  • outcomestring | null

    The outcome bought or sold, e.g. Yes.

  • outcomeIndexinteger | null
  • priceProbabilitynumber

    Fill price, which is the implied probability of outcome.

  • sizeSharesnumber

    Shares (Polymarket) or contracts (Kalshi) filled.

  • amountUsdnumber

    Notional of the fill: sizeShares × priceProbability.

  • tradedAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • marketobject

    The market the trade was in.

  • market.titlestring | null

    The market's question.

  • market.eventSlugstring | null

    Polymarket event slug, or the Kalshi event ticker.

  • market.marketSlugstring | null

    Polymarket market slug, or the Kalshi market ticker.

  • market.conditionIdstring | null

    Polymarket condition id. Null on Kalshi.

  • market.assetstring | null

    Polymarket outcome token id. Null on Kalshi.

Show 39 more fields
  • market.tickerstring | null

    Kalshi market ticker. Null on Polymarket.

  • market.iconstring | null
  • market.categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • market.tagsany[]

    Polymarket event tags, as Polymarket returns them.

  • market.endsAtstring (date-time) | null

    Scheduled end of the market, when known.

  • venue"polymarket"
  • transactionHashstring

    An on-chain transaction hash, lowercase.

  • walletstring

    A Polygon wallet address, always lowercase.

  • traderobject

    Display identity of the wallet's Polymarket account.

  • trader.namestring | null

    Polymarket display name, when set.

  • trader.pseudonymstring | null

    Polymarket's generated pseudonym.

  • trader.imagestring | null

    Profile image URL.

  • insiderobject | null
  • insider.scorenumber | null

    Insider-risk score, 0..100: above 40 is medium, above 70 high. Null when the trade has not been scored (only Polymarket buys are).

  • insider.breakdownobject

    The insider score's factors, in points. Each factor is capped, and together they add up to score, which is capped at 100 (sports trades are capped lower, below the high band).

  • insider.breakdown.convictionnumber | null

    A large position at long odds. Points, at most 35.

  • insider.breakdown.priceImpactnumber | null

    How far the trade moved the price. No longer scored: 0 on trades scored now, kept for older trades.

  • insider.breakdown.tradeSizenumber | null

    The trade's size on its own. No longer scored: 0 on trades scored now, kept for older trades.

  • insider.breakdown.marketDominancenumber | null

    The trade's size against the market's trading over the previous 24 hours. Points, at most 20.

  • insider.breakdown.timingnumber | null

    How close to the market's resolution the trade came, or a large early position on a distant event. Points, at most 10.

  • insider.breakdown.walletAgenumber | null

    How new the wallet was when it traded. Points, at most 15.

  • insider.breakdown.contextnumber | null

    How much the market's kind of event lends itself to inside knowledge. Points, at most 20.

  • insider.detailsobject | null

    The full scoring record (GET /v2/trades/{id} only). Its fields may grow; treat it as informational.

  • smartMoneyobject

    The trading wallet's Smart Money standing at request time.

  • smartMoney.rankinteger | null

    The wallet's Smart Money rank (1 = best), null while provisional.

  • smartMoney.scorenumber

    The wallet's smoothed Smart Money Score.

  • smartMoney.tierenum

    Badge band: genius 80+ (methodology 4 also needs a minimum of realized edge in dollars), smart 60+, average 40+, naive below.

  • smartMoney.styleenum | null

    Trading style; null under methodology 3, which has none.

    One oflong-shotbalancedfavoriteunknown

  • smartMoney.methodologyVersioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • resolutionobject | null
  • resolution.statusenum

    One ofwinlosspendingcancelledinvalid

  • resolution.winningOutcomestring | null
  • resolution.entryPricenumber | null
  • resolution.exitPricenumber | null
  • resolution.pnlUsdnumber | null
  • resolution.roinumber | null
  • resolution.resolvedAtstring (date-time) | null
  • resolution.holdSecondsinteger | null
  • resolution.vindicatedboolean

    A high-insider-score trade that won.

meta

  • methodologyVersioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • cohortobject
  • cohort.sizeinteger

    Wallets in the cohort.

  • cohort.maxRankinteger | null
  • cohort.minScorenumber | null
  • cohort.truncatedboolean

    minScore matched more than 1000 wallets; the best-ranked 1000 are used.

curl 'https://polywhaler.com/api/v2/smart-money/trades?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "side": "BUY",
      "outcome": "Yes",
      "outcomeIndex": 0,
      "priceProbability": 0.42,
      "sizeShares": 50000,
      "amountUsd": 21000,
      "tradedAt": "2026-10-09T14:03:27.000Z",
      "market": {
        "title": "Will BTC close above $130k on Oct 31?",
        "eventSlug": "bitcoin-above-on-october-31",
        "marketSlug": "bitcoin-above-130k-on-october-31",
        "conditionId": "0x1d4e",
        "asset": "51234",
        "ticker": null,
        "icon": null,
        "category": "crypto",
        "tags": [],
        "endsAt": "2026-10-31T16:00:00.000Z"
      },
      "venue": "polymarket",
      "transactionHash": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
      "trader": {
        "name": "sample-whale",
        "pseudonym": "Sample-Heron",
        "image": null
      },
      "insider": {
        "score": 64,
        "breakdown": {
          "conviction": 28,
          "priceImpact": 0,
          "tradeSize": 0,
          "marketDominance": 13,
          "timing": 5,
          "walletAge": 8,
          "context": 10
        },
        "details": {}
      },
      "smartMoney": {
        "rank": 1,
        "score": 64,
        "tier": "genius",
        "style": "long-shot",
        "methodologyVersion": 4
      },
      "resolution": {
        "status": "win",
        "winningOutcome": "string",
        "entryPrice": 0,
        "exitPrice": 0,
        "pnlUsd": 0,
        "roi": 0,
        "resolvedAt": "2026-10-09T14:03:27.000Z",
        "holdSeconds": 0,
        "vindicated": true
      }
    }
  ],
  "meta": {
    "methodologyVersion": 4,
    "cohort": {
      "size": 0,
      "maxRank": 0,
      "minScore": 0,
      "truncated": true
    }
  },
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Wallets#

5 endpoints

Get a wallet#

GET/v2/wallets/{address}
scope: readCached 60s

A wallet's Smart Money standing and stats, from the cron-refreshed board. A wallet with whale trades that is not on the board yet answers 200 with status: "unscored" and smartMoney: null; a wallet with neither is 404 wallet_not_tracked.

Path parameters

  • addressstringRequiredpath

    Wallet address; any case, answered lowercase.

    pattern ^0x[0-9a-fA-F]{40}$

Response

data
  • walletstring

    A Polygon wallet address, always lowercase.

  • statusenum

    unscored: tracked whale trades, but not on the board yet.

  • traderobject

    Display identity of the wallet's Polymarket account.

  • trader.namestring | null

    Polymarket display name, when set.

  • trader.pseudonymstring | null

    Polymarket's generated pseudonym.

  • trader.imagestring | null

    Profile image URL.

  • smartMoneySmartMoney | null
  • statsobject

    Realized record, from the board refresh (at most about 15 minutes old).

  • stats.winRatenumber | null

    Wins over decisive closed positions.

  • stats.winCountinteger | null
  • stats.lossCountinteger | null
  • stats.decisivePositionsinteger | null

    Closed positions with a non-zero realized P&L.

  • stats.roinumber | null

    Realized P&L over cost of closed positions.

  • stats.pnlUsdnumber | null

    Realized P&L over closed positions.

Show 15 more fields
  • stats.recentPnlUsdnumber | null

    Realized P&L over the last 90 days.

  • stats.polymarketPnlUsdnumber | null

    Polymarket's own all-time P&L, when checked.

  • stats.pnlVerifiedboolean | null

    Whether our realized P&L agreed with Polymarket's at the last check.

  • stats.openCashPnlUsdnumber | null

    Net cash P&L of open positions.

  • stats.totalTradesinteger | null

    Whale trades ($10k+) on record.

  • stats.volumeUsdnumber | null

    Notional of those whale trades.

  • stats.firstTradeAtstring (date-time) | null
  • stats.lastTradeAtstring (date-time) | null
  • confirmedInsiderboolean
  • followerCountinteger
  • clustersobject[]

    Active coordinated-trading clusters the wallet belongs to.

  • clusters[].clusterIdstring
  • clusters[].confidencenumber

    Detection confidence, 0..1.

  • clusters[].sizeinteger
  • updatedAtstring (date-time) | null

meta

  • methodologyVersioninteger

    The methodology the board currently ranks by. Each score also says which version produced it.

curl 'https://polywhaler.com/api/v2/wallets/0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
    "status": "scored",
    "trader": {
      "name": "sample-whale",
      "pseudonym": "Sample-Heron",
      "image": null
    },
    "smartMoney": {
      "score": 64,
      "scoreRaw": 0,
      "rank": 1,
      "rankExSports": 1,
      "tier": "genius",
      "provisional": true,
      "provisionalReason": "few_closes",
      "confidence": 0,
      "clusterPenalty": 0,
      "methodologyVersion": 4,
      "style": "long-shot",
      "components": {
        "edge": 0,
        "hit": 0,
        "recent": 0,
        "roi": 0,
        "size": 0
      },
      "zEdge": 0,
      "zHit": 0,
      "zRecent": 0,
      "evidenceWeeks": 0,
      "topWeekShare": 0,
      "bestWeek": "2026-10-09",
      "bestWeekPnlUsd": 0,
      "decayedEdgeUsd": 0,
      "lifetimeCapped": true
    },
    "stats": {
      "winRate": 0,
      "winCount": 0,
      "lossCount": 0,
      "decisivePositions": 0,
      "roi": 0,
      "pnlUsd": 0,
      "recentPnlUsd": 0,
      "polymarketPnlUsd": 0,
      "pnlVerified": true,
      "openCashPnlUsd": 0,
      "totalTrades": 0,
      "volumeUsd": 0,
      "firstTradeAt": "2026-10-09T14:03:27.000Z",
      "lastTradeAt": "2026-10-09T14:03:27.000Z"
    },
    "confirmedInsider": true,
    "followerCount": 0,
    "clusters": [
      {
        "clusterId": "string",
        "confidence": 0,
        "size": 0
      }
    ],
    "updatedAt": "2026-10-09T14:03:27.000Z"
  },
  "meta": {
    "methodologyVersion": 4
  }
}

Get the clusters a wallet is in#

GET/v2/wallets/{address}/cluster
scope: readCached 300s

The active clusters that include the wallet, highest confidence first. Empty when a tracked wallet is in none; 404 wallet_not_tracked for a wallet Polywhaler has no trades for.

Path parameters

  • addressstringRequiredpath

    Wallet address; any case, answered lowercase.

    pattern ^0x[0-9a-fA-F]{40}$

Response

data is an array of
  • clusterIdstring
  • walletsstring[]
  • sizeinteger
  • detectionMethodenum

    One oftiming_correlationoutcome_correlationfunding_sourcemulti_factor

  • confidencenumber

    Detection confidence, 0..1.

  • outcomeAlignmentnumber | null

    Share of shared markets where the wallets chose the same outcome.

  • coordinatedTradeCountinteger
  • firstCoordinatedTradeAtstring (date-time) | null
  • lastCoordinatedTradeAtstring (date-time) | null
  • activeboolean
  • flaggedForReviewboolean
  • detectedAtstring (date-time) | null
curl 'https://polywhaler.com/api/v2/wallets/0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913/cluster' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "clusterId": "string",
      "wallets": [
        "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913"
      ],
      "size": 2,
      "detectionMethod": "timing_correlation",
      "confidence": 0,
      "outcomeAlignment": 0,
      "coordinatedTradeCount": 0,
      "firstCoordinatedTradeAt": "2026-10-09T14:03:27.000Z",
      "lastCoordinatedTradeAt": "2026-10-09T14:03:27.000Z",
      "active": true,
      "flaggedForReview": true,
      "detectedAt": "2026-10-09T14:03:27.000Z"
    }
  ]
}

Get a wallet's live positions#

GET/v2/wallets/{address}/positions
scope: readCached 30s

Open and closed positions, read live from Polymarket (cached 30 seconds). 502 upstream_error when Polymarket does not answer.

Path parameters

  • addressstringRequiredpath

    Wallet address; any case, answered lowercase.

    pattern ^0x[0-9a-fA-F]{40}$

Query parameters

  • statusenumdefault all

    Open positions, closed ones, or both.

    One ofcurrentclosedall

  • limitinteger

    Most positions per list (default: 1000 open, 2000 closed).

    1..2000

Response

data
  • walletstring

    A Polygon wallet address, always lowercase.

  • summaryobject
  • summary.valueUsdnumber

    US dollars.

  • summary.unrealizedPnlUsdnumber

    US dollars.

  • summary.realizedPnlUsdnumber

    US dollars.

  • summary.totalPnlUsdnumber

    US dollars.

  • summary.openCountinteger
  • summary.redeemableCountinteger
  • summary.mergeableCountinteger
  • summary.closedCountinteger
  • summary.winCountinteger
  • summary.lossCountinteger
  • summary.winRatenumber | null
  • summary.roinumber | null

    Average return on closed positions, as a fraction.

Show 22 more fields
  • currentobject[]
  • current[].titlestring | null
  • current[].outcomestring | null
  • current[].eventSlugstring | null
  • current[].marketSlugstring | null
  • current[].sizeSharesnumber | null
  • current[].avgPriceProbabilitynumber | null
  • current[].currentPriceProbabilitynumber | null
  • current[].valueUsdnumber | null
  • current[].cashPnlUsdnumber | null
  • current[].redeemableboolean
  • current[].mergeableboolean
  • closedobject[]
  • closed[].titlestring | null
  • closed[].outcomestring | null
  • closed[].eventSlugstring | null
  • closed[].marketSlugstring | null
  • closed[].avgPriceProbabilitynumber | null
  • closed[].sharesBoughtnumber | null
  • closed[].costUsdnumber | null
  • closed[].realizedPnlUsdnumber | null
  • closed[].closedAtstring (date-time) | null

Errors

Also invalid_param, unknown_param, api_key_expired, api_key_revoked, invalid_api_key, missing_api_key, insufficient_scope, plan_required, quota_exceeded, rate_limited, internal_error, temporarily_unavailable.

curl 'https://polywhaler.com/api/v2/wallets/0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913/positions' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
    "summary": {
      "valueUsd": 0,
      "unrealizedPnlUsd": 0,
      "realizedPnlUsd": 0,
      "totalPnlUsd": 0,
      "openCount": 0,
      "redeemableCount": 0,
      "mergeableCount": 0,
      "closedCount": 0,
      "winCount": 0,
      "lossCount": 0,
      "winRate": 0,
      "roi": 0
    },
    "current": [
      {
        "title": "Will BTC close above $130k on Oct 31?",
        "outcome": "Yes",
        "eventSlug": "bitcoin-above-on-october-31",
        "marketSlug": "bitcoin-above-130k-on-october-31",
        "sizeShares": 50000,
        "avgPriceProbability": 0,
        "currentPriceProbability": 0,
        "valueUsd": 0,
        "cashPnlUsd": 0,
        "redeemable": true,
        "mergeable": true
      }
    ],
    "closed": [
      {
        "title": "Will BTC close above $130k on Oct 31?",
        "outcome": "Yes",
        "eventSlug": "bitcoin-above-on-october-31",
        "marketSlug": "bitcoin-above-130k-on-october-31",
        "avgPriceProbability": 0,
        "sharesBought": 0,
        "costUsd": 0,
        "realizedPnlUsd": 0,
        "closedAt": "2026-10-09T14:03:27.000Z"
      }
    ]
  }
}

Get a wallet's score history#

GET/v2/wallets/{address}/score/history
scope: readCached 300s

Daily Smart Money Score and rank for up to 210 days (the snapshot retention). meta.methodologyChanges lists the days the score's scale changed (the v3 to v4 cutover): a step on those days is not movement.

Path parameters

  • addressstringRequiredpath

    Wallet address; any case, answered lowercase.

    pattern ^0x[0-9a-fA-F]{40}$

Query parameters

  • fromstring (date)

    First day, inclusive. Defaults to 210 days ago.

  • tostring (date)

    Last day, exclusive. Defaults to tomorrow.

Response

data is an array of
  • datestring (date)

    A UTC calendar day, YYYY-MM-DD.

  • scorenumber | null

    Smoothed score that day.

  • scoreRawnumber | null
  • rankinteger | null
  • methodologyVersioninteger | null

meta

  • fromstring (date)

    A UTC calendar day, YYYY-MM-DD.

  • tostring (date)

    A UTC calendar day, YYYY-MM-DD.

  • methodologyChangesobject[]
  • methodologyChanges[].datestring (date)

    First day on the new methodology.

  • methodologyChanges[].fromVersioninteger | null
  • methodologyChanges[].toVersioninteger | null
  • methodologyVersioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

curl 'https://polywhaler.com/api/v2/wallets/0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913/score/history' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "date": "2026-10-09",
      "score": 64,
      "scoreRaw": 0,
      "rank": 1,
      "methodologyVersion": 4
    }
  ],
  "meta": {
    "from": "2026-10-09",
    "to": "2026-10-09",
    "methodologyChanges": [
      {
        "date": "2026-10-09",
        "fromVersion": 4,
        "toVersion": 4
      }
    ],
    "methodologyVersion": 4
  }
}

List a wallet's whale trades#

GET/v2/wallets/{address}/trades
scope: readCursor paginatedCached 15s

The wallet's whale trades on Polymarket, newest first. 404 wallet_not_tracked for a wallet Polywhaler has never seen.

Path parameters

  • addressstringRequiredpath

    Wallet address; any case, answered lowercase.

    pattern ^0x[0-9a-fA-F]{40}$

Query parameters

  • categorystring

    Comma-separated categories, any of: crypto, politics, economy, geopolitics, tech, culture, science, sports, esports, other, markets.

  • sideenum

    Taker side: BUY or SELL.

  • minAmountnumber

    Minimum fill size in USD, inclusive.

    min 0

  • fromstring (date-time)

    Trades at or after this instant.

  • tostring (date-time)

    Trades before this instant.

  • includestring

    smartMoney adds each Polymarket wallet's standing on the Smart Money board (null when it is not on it).

  • limitintegerdefault 100

    Page size, 1 to 500.

    1..500

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • idstring

    Trade id: the transaction hash on Polymarket, kx_<tradeId> on Kalshi. Use with GET /v2/trades/{id}.

  • sideenum

    Taker side of the fill.

    One ofBUYSELL

  • outcomestring | null

    The outcome bought or sold, e.g. Yes.

  • outcomeIndexinteger | null
  • priceProbabilitynumber

    Fill price, which is the implied probability of outcome.

  • sizeSharesnumber

    Shares (Polymarket) or contracts (Kalshi) filled.

  • amountUsdnumber

    Notional of the fill: sizeShares × priceProbability.

  • tradedAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • marketobject

    The market the trade was in.

  • market.titlestring | null

    The market's question.

  • market.eventSlugstring | null

    Polymarket event slug, or the Kalshi event ticker.

  • market.marketSlugstring | null

    Polymarket market slug, or the Kalshi market ticker.

  • market.conditionIdstring | null

    Polymarket condition id. Null on Kalshi.

  • market.assetstring | null

    Polymarket outcome token id. Null on Kalshi.

Show 39 more fields
  • market.tickerstring | null

    Kalshi market ticker. Null on Polymarket.

  • market.iconstring | null
  • market.categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • market.tagsany[]

    Polymarket event tags, as Polymarket returns them.

  • market.endsAtstring (date-time) | null

    Scheduled end of the market, when known.

  • venue"polymarket" | "kalshi"
  • transactionHashstring | null

    An on-chain transaction hash, lowercase.

  • walletstring | null

    A Polygon wallet address, always lowercase.

  • traderobject

    Display identity of the wallet's Polymarket account.

  • trader.namestring | null

    Polymarket display name, when set.

  • trader.pseudonymstring | null

    Polymarket's generated pseudonym.

  • trader.imagestring | null

    Profile image URL.

  • insiderobject | null
  • insider.scorenumber | null

    Insider-risk score, 0..100: above 40 is medium, above 70 high. Null when the trade has not been scored (only Polymarket buys are).

  • insider.breakdownobject

    The insider score's factors, in points. Each factor is capped, and together they add up to score, which is capped at 100 (sports trades are capped lower, below the high band).

  • insider.breakdown.convictionnumber | null

    A large position at long odds. Points, at most 35.

  • insider.breakdown.priceImpactnumber | null

    How far the trade moved the price. No longer scored: 0 on trades scored now, kept for older trades.

  • insider.breakdown.tradeSizenumber | null

    The trade's size on its own. No longer scored: 0 on trades scored now, kept for older trades.

  • insider.breakdown.marketDominancenumber | null

    The trade's size against the market's trading over the previous 24 hours. Points, at most 20.

  • insider.breakdown.timingnumber | null

    How close to the market's resolution the trade came, or a large early position on a distant event. Points, at most 10.

  • insider.breakdown.walletAgenumber | null

    How new the wallet was when it traded. Points, at most 15.

  • insider.breakdown.contextnumber | null

    How much the market's kind of event lends itself to inside knowledge. Points, at most 20.

  • insider.detailsobject | null

    The full scoring record (GET /v2/trades/{id} only). Its fields may grow; treat it as informational.

  • smartMoneyobject | null

    Present with include=smartMoney: null when the wallet is not on the board.

  • smartMoney.rankinteger | null

    The wallet's Smart Money rank (1 = best), null while provisional.

  • smartMoney.scorenumber

    The wallet's smoothed Smart Money Score.

  • smartMoney.tierenum

    Badge band: genius 80+ (methodology 4 also needs a minimum of realized edge in dollars), smart 60+, average 40+, naive below.

  • smartMoney.styleenum | null

    Trading style; null under methodology 3, which has none.

    One oflong-shotbalancedfavoriteunknown

  • smartMoney.methodologyVersioninteger

    The Smart Money Score methodology version that produced this score. GET /v2/methodology/{version} returns its specification.

  • resolutionobject | null
  • resolution.statusenum

    One ofwinlosspendingcancelledinvalid

  • resolution.winningOutcomestring | null
  • resolution.entryPricenumber | null
  • resolution.exitPricenumber | null
  • resolution.pnlUsdnumber | null
  • resolution.roinumber | null
  • resolution.resolvedAtstring (date-time) | null
  • resolution.holdSecondsinteger | null
  • resolution.vindicatedboolean

    A high-insider-score trade that won.

curl 'https://polywhaler.com/api/v2/wallets/0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913/trades?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "venue": "polymarket",
      "transactionHash": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
      "trader": {
        "name": "sample-whale",
        "pseudonym": "Sample-Heron",
        "image": null
      },
      "side": "BUY",
      "outcome": "Yes",
      "outcomeIndex": 0,
      "priceProbability": 0.42,
      "sizeShares": 50000,
      "amountUsd": 21000,
      "tradedAt": "2026-10-09T14:03:27.000Z",
      "market": {
        "title": "Will BTC close above $130k on Oct 31?",
        "eventSlug": "bitcoin-above-on-october-31",
        "marketSlug": "bitcoin-above-130k-on-october-31",
        "conditionId": "0x1d4e",
        "asset": "51234",
        "ticker": null,
        "icon": null,
        "category": "crypto",
        "tags": [],
        "endsAt": "2026-10-31T16:00:00.000Z"
      },
      "insider": {
        "score": 64,
        "breakdown": {
          "conviction": 28,
          "priceImpact": 0,
          "tradeSize": 0,
          "marketDominance": 13,
          "timing": 5,
          "walletAge": 8,
          "context": 10
        }
      }
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Markets#

1 endpoint

List markets by whale volume#

GET/v2/markets
scope: readCached 60s

Events ranked by whale volume over the window, with buy/sell split, net flow and insider scores.

Query parameters

  • timeenumdefault 24h

    The window ending now.

    One of1h6h24h7d30d

  • sportsenumdefault include

    Sports and esports markets: include them, exclude them, or only them.

  • categorystring

    Comma-separated categories, any of: crypto, politics, economy, geopolitics, tech, culture, science, sports, esports, other, markets.

  • minVolumenumber

    Minimum whale volume in USD.

    min 0

  • limitintegerdefault 25

    Page size, 1 to 100.

    1..100

Response

data is an array of
  • eventSlugstring
  • titlestring
  • iconstring | null
  • categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • volumeUsdnumber

    Whale notional in the window.

  • buyVolumeUsdnumber

    US dollars.

  • sellVolumeUsdnumber

    US dollars.

  • netFlowUsdnumber

    Buys minus sells.

  • tradeCountinteger
  • uniqueWalletsinteger
  • maxInsiderScorenumber | null
  • insiderScorenumber | null

    Volume-weighted insider score.

meta

  • timeenum

    One of1h6h24h7d30d

  • sportsenum

    One ofincludeexcludeonly

curl 'https://polywhaler.com/api/v2/markets' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "eventSlug": "bitcoin-above-on-october-31",
      "title": "Will BTC close above $130k on Oct 31?",
      "icon": "string",
      "category": "crypto",
      "volumeUsd": 0,
      "buyVolumeUsd": 0,
      "sellVolumeUsd": 0,
      "netFlowUsd": 0,
      "tradeCount": 0,
      "uniqueWallets": 0,
      "maxInsiderScore": 0,
      "insiderScore": 0
    }
  ],
  "meta": {
    "time": "1h",
    "sports": "include"
  }
}

Signals#

1 endpoint

Get the Black Swan radar#

GET/v2/black-swan
scope: readCached 60s

The latest tail-risk reading. 503 until the radar has produced its first reading.

Response

data
  • indexnumber

    Geopolitical risk index, 0..100.

  • statusenum

    One ofcalmripplesstormblack_swan

  • statusLabelstring
  • statusDescriptionstring
  • componentsobject
  • components.probabilitynumber | null
  • components.insidernumber | null
  • components.resolvednumber | null
  • highestRiskMarketobject | null
  • highestRiskMarket.slugstring
  • highestRiskMarket.titlestring
  • highestRiskMarket.probabilitynumber

    A rate on 0..1.

  • highestRiskMarket.categorystring | null
  • highestRiskMarket.aiScorenumber | null
Show 7 more fields
  • trackedMarketCountinteger
  • insiderActivityobject | null
  • insiderActivity.volumeUsdnumber

    US dollars.

  • insiderActivity.averageScorenumber | null
  • insiderActivity.tradeCountinteger
  • insiderActivity.yesBuyVolumeUsdnumber

    US dollars.

  • updatedAtstring (date-time) | null
curl 'https://polywhaler.com/api/v2/black-swan' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "index": 0,
    "status": "calm",
    "statusLabel": "string",
    "statusDescription": "string",
    "components": {
      "probability": 0,
      "insider": 0,
      "resolved": 0
    },
    "highestRiskMarket": {
      "slug": "string",
      "title": "Will BTC close above $130k on Oct 31?",
      "probability": 0,
      "category": "crypto",
      "aiScore": 0
    },
    "trackedMarketCount": 0,
    "insiderActivity": {
      "volumeUsd": 0,
      "averageScore": 0,
      "tradeCount": 0,
      "yesBuyVolumeUsd": 0
    },
    "updatedAt": "2026-10-09T14:03:27.000Z"
  }
}

Insider#

1 endpoint

List events with insider-like flow#

GET/v2/insider/events
scope: readCached 60s

Events whose whale trades score high for insider risk over the window, most suspicious first.

Query parameters

  • timeenumdefault 7d

    The window ending now.

    One of1h6h24h7d30d

  • categoryenum

    A category.

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • limitintegerdefault 50

    Page size, 1 to 200.

    1..200

Response

data is an array of
  • eventSlugstring
  • titlestring
  • iconstring | null
  • endsAtstring (date-time) | null
  • categorystring
  • insiderScorenumber

    Aggregate insider-risk score of the event's flagged buying.

  • highRiskTradeCountinteger
  • mediumRiskTradeCountinteger
  • flaggedVolumeUsdnumber

    US dollars.

  • outcomesobject[]
  • outcomes[].outcomestring
  • outcomes[].insiderScorenumber

    A score on 0..100.

  • outcomes[].tradeCountinteger
  • outcomes[].volumeUsdnumber

    US dollars.

Show 7 more fields
  • outcomes[].dominantSideenum

    Taker side of the fill.

    One ofBUYSELL

  • topWalletsobject[]
  • topWallets[].walletstring

    A Polygon wallet address, always lowercase.

  • topWallets[].namestring | null
  • topWallets[].imagestring | null
  • topWallets[].volumeUsdnumber

    US dollars.

  • topWallets[].insiderScorenumber

    A score on 0..100.

meta

  • timeenum

    One of1h6h24h7d30d

curl 'https://polywhaler.com/api/v2/insider/events' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "eventSlug": "bitcoin-above-on-october-31",
      "title": "Will BTC close above $130k on Oct 31?",
      "icon": "string",
      "endsAt": "2026-10-09T14:03:27.000Z",
      "category": "crypto",
      "insiderScore": 0,
      "highRiskTradeCount": 0,
      "mediumRiskTradeCount": 0,
      "flaggedVolumeUsd": 0,
      "outcomes": [
        {
          "outcome": "Yes",
          "insiderScore": 0,
          "tradeCount": 0,
          "volumeUsd": 0,
          "dominantSide": "BUY"
        }
      ],
      "topWallets": [
        {
          "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
          "name": "sample-whale",
          "image": "string",
          "volumeUsd": 0,
          "insiderScore": 0
        }
      ]
    }
  ],
  "meta": {
    "time": "1h"
  }
}

Clusters#

1 endpoint

List wallet clusters#

GET/v2/clusters
scope: readCursor paginatedCached 300s

Groups of wallets that appear to be one trader. confidence is 0..1. Highest confidence first.

Query parameters

  • activeenumdefault true

    Active clusters (default), inactive ones, or all.

    One oftruefalseall

  • minConfidencenumber

    Minimum confidence, 0..1.

    0..1

  • minSizeinteger

    Minimum number of wallets.

    2..1000

  • methodstring

    at most 64 characters · pattern ^[a-z0-9_-]+$

  • flaggedenum

    Only clusters flagged for review (true), or unflagged ones (false).

  • limitintegerdefault 50

    Page size, 1 to 200.

    1..200

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • clusterIdstring
  • walletsstring[]
  • sizeinteger
  • detectionMethodenum

    One oftiming_correlationoutcome_correlationfunding_sourcemulti_factor

  • confidencenumber

    Detection confidence, 0..1.

  • outcomeAlignmentnumber | null

    Share of shared markets where the wallets chose the same outcome.

  • coordinatedTradeCountinteger
  • firstCoordinatedTradeAtstring (date-time) | null
  • lastCoordinatedTradeAtstring (date-time) | null
  • activeboolean
  • flaggedForReviewboolean
  • detectedAtstring (date-time) | null
curl 'https://polywhaler.com/api/v2/clusters?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "clusterId": "string",
      "wallets": [
        "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913"
      ],
      "size": 2,
      "detectionMethod": "timing_correlation",
      "confidence": 0,
      "outcomeAlignment": 0,
      "coordinatedTradeCount": 0,
      "firstCoordinatedTradeAt": "2026-10-09T14:03:27.000Z",
      "lastCoordinatedTradeAt": "2026-10-09T14:03:27.000Z",
      "active": true,
      "flaggedForReview": true,
      "detectedAt": "2026-10-09T14:03:27.000Z"
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Positions#

1 endpoint

List aggregated whale positions#

GET/v2/positions/aggregated
scope: readCursor paginatedCached 60s

Positions built from many smaller fills that together cross the whale threshold, most recently added to first.

Query parameters

  • walletstring

    Wallet address; any case, answered lowercase.

    pattern ^0x[0-9a-fA-F]{40}$

  • categorystring

    Comma-separated categories, any of: crypto, politics, economy, geopolitics, tech, culture, science, sports, esports, other, markets.

  • sideenum

    Taker side: BUY or SELL.

  • minInsiderScorenumber

    0..100

  • minValuenumber

    Minimum total position value in USD.

    min 0

  • fromstring (date-time)

    Positions last added to at or after this instant.

  • tostring (date-time)

    Positions last added to before this instant.

  • limitintegerdefault 100

    Page size, 1 to 500.

    1..500

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • positionKeystring

    Stable id of the (wallet, event, outcome, side) position.

  • walletstring

    A Polygon wallet address, always lowercase.

  • traderobject
  • trader.namestring | null
  • trader.imagestring | null
  • eventSlugstring
  • marketSlugstring | null
  • titlestring | null
  • iconstring | null
  • categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • tagsany[]
  • outcomestring
  • sideenum

    Taker side of the fill.

    One ofBUYSELL

  • valueUsdnumber

    Combined notional of the trades in the position.

Show 7 more fields
  • tradeCountinteger
  • avgPriceProbabilitynumber

    A probability or price on 0..1.

  • firstTradeAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • lastTradeAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • tradeIdsstring[]

    Transaction hashes of the trades rolled up.

  • insiderScorenumber | null

    Value-weighted insider score of the trades.

  • endsAtstring (date-time) | null
curl 'https://polywhaler.com/api/v2/positions/aggregated?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "positionKey": "string",
      "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
      "trader": {
        "name": "sample-whale",
        "image": "string"
      },
      "eventSlug": "bitcoin-above-on-october-31",
      "marketSlug": "bitcoin-above-130k-on-october-31",
      "title": "Will BTC close above $130k on Oct 31?",
      "icon": "string",
      "category": "crypto",
      "tags": [],
      "outcome": "Yes",
      "side": "BUY",
      "valueUsd": 0,
      "tradeCount": 0,
      "avgPriceProbability": 0,
      "firstTradeAt": "2026-10-09T14:03:27.000Z",
      "lastTradeAt": "2026-10-09T14:03:27.000Z",
      "tradeIds": [
        "string"
      ],
      "insiderScore": 0,
      "endsAt": "2026-10-09T14:03:27.000Z"
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Outcomes#

1 endpoint

List trade outcomes#

GET/v2/outcomes
scope: readCursor paginatedCached 60s

How whale trades did once their market resolved, newest trade first. roi is a fraction (0.25 = +25%).

Query parameters

  • walletstring

    Only this wallet's trades.

    pattern ^0x[0-9a-fA-F]{40}$

  • eventSlugstring

    at most 200 characters · pattern ^[A-Za-z0-9][A-Za-z0-9._-]*$

  • statusenum

    win, loss, pending, cancelled or invalid.

  • vindicatedenum

    Only trades whose low-probability call came true (true), or the rest (false).

  • minRoinumber

    Minimum return as a fraction: 0.5 is +50%.

  • fromstring (date-time)

    Trades at or after this instant.

  • tostring (date-time)

    Trades before this instant.

  • limitintegerdefault 100

    Page size, 1 to 500.

    1..500

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • tradeIdstring

    The trade's id (its transaction hash).

  • walletstring

    A Polygon wallet address, always lowercase.

  • eventSlugstring
  • statusenum

    One ofwinlosspendingcancelledinvalid

  • winningOutcomestring | null
  • sideenum

    Taker side of the fill.

    One ofBUYSELL

  • entryPricenumber

    A probability or price on 0..1.

  • exitPricenumber | null
  • sizeSharesnumber
  • costUsdnumber | null

    sizeShares × entryPrice.

  • pnlUsdnumber | null
  • roinumber | null

    Return on cost as a fraction (0.25 = +25%).

  • tradedAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • resolvedAtstring (date-time) | null
Show 13 more fields
  • holdSecondsinteger | null
  • vindicatedboolean

    A high-insider-score trade that won.

  • tradeobject | null

    The originating trade's market context; null if the trade row is gone.

  • trade.titlestring | null
  • trade.outcomestring | null
  • trade.categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • trade.marketSlugstring | null
  • trade.iconstring | null
  • trade.insiderScorenumber | null
  • trade.traderobject

    Display identity of the wallet's Polymarket account.

  • trade.trader.namestring | null

    Polymarket display name, when set.

  • trade.trader.pseudonymstring | null

    Polymarket's generated pseudonym.

  • trade.trader.imagestring | null

    Profile image URL.

curl 'https://polywhaler.com/api/v2/outcomes?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "tradeId": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
      "eventSlug": "bitcoin-above-on-october-31",
      "status": "win",
      "winningOutcome": "string",
      "side": "BUY",
      "entryPrice": 0,
      "exitPrice": 0,
      "sizeShares": 50000,
      "costUsd": 0,
      "pnlUsd": 0,
      "roi": 0,
      "tradedAt": "2026-10-09T14:03:27.000Z",
      "resolvedAt": "2026-10-09T14:03:27.000Z",
      "holdSeconds": 0,
      "vindicated": true,
      "trade": {
        "title": "Will BTC close above $130k on Oct 31?",
        "outcome": "Yes",
        "category": "crypto",
        "marketSlug": "bitcoin-above-130k-on-october-31",
        "icon": "string",
        "insiderScore": 0,
        "trader": {
          "name": "sample-whale",
          "pseudonym": "Sample-Heron",
          "image": null
        }
      }
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Oracle#

1 endpoint

List Oracle editions#

GET/v2/oracle
scope: readCursor paginatedCached 300s

The daily Oracle briefings, newest first.

Query parameters

  • fromstring (date)

    Editions on or after this day.

  • tostring (date)

    Editions before this day.

  • limitintegerdefault 30

    Page size, 1 to 90.

    1..90

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • idstring
  • datestring (date)
  • volumeNumberinteger
  • publishedAtstring (date-time) | null
  • articlesobject[]
  • articles[].eventSlugstring
  • articles[].categorystring
  • articles[].headlinestring
  • articles[].ledestring
  • articles[].bodystring
  • articles[].predictedOutcomestring | null
  • articles[].confidencenumber | null
  • articles[].imageUrlstring | null
  • articles[].resolvesAtstring (date-time) | null
Show 9 more fields
  • articles[].evidenceobject | null
  • articles[].evidence.volumeUsdnumber | null
  • articles[].evidence.tradeCountinteger | null
  • articles[].evidence.buyVolumeUsdnumber | null
  • articles[].evidence.sellVolumeUsdnumber | null
  • articles[].evidence.avgInsiderScorenumber | null
  • articles[].evidence.maxInsiderScorenumber | null
  • articles[].evidence.dominantSideenum | null

    One ofBUYSELL

  • articles[].evidence.avgProbabilitynumber | null
curl 'https://polywhaler.com/api/v2/oracle?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "date": "2026-10-09",
      "volumeNumber": 0,
      "publishedAt": "2026-10-09T14:03:27.000Z",
      "articles": [
        {
          "eventSlug": "bitcoin-above-on-october-31",
          "category": "crypto",
          "headline": "string",
          "lede": "string",
          "body": "string",
          "predictedOutcome": "string",
          "confidence": 0,
          "imageUrl": "string",
          "resolvesAt": "2026-10-09T14:03:27.000Z",
          "evidence": {
            "volumeUsd": 0,
            "tradeCount": 0,
            "buyVolumeUsd": 0,
            "sellVolumeUsd": 0,
            "avgInsiderScore": 0,
            "maxInsiderScore": 0,
            "dominantSide": "BUY",
            "avgProbability": 0
          }
        }
      ]
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Crypto#

1 endpoint

List crypto price-prediction markets#

GET/v2/crypto/markets
scope: readCursor paginatedCached 15s

Polymarket's open crypto price markets, by event, as the /crypto dashboard lists them: up-or-down rounds from 5 minutes to 4 hours, daily to yearly above/below and price-range ladders, and hit-price targets, each with every open market's outcome prices (implied probabilities, 0..1). The catalogue is read from Polymarket every 15 seconds (the rolling rounds) to 60 seconds (the rest). When Polymarket does not answer, the last good copy is served with meta.stale: true; before any copy exists the route answers 503 temporarily_unavailable. Prices and volumes move between pages, so an event can shift across a page boundary under sort=volume. The Bitcoin model fitted to these markets is GET /v2/forecasts/btc.

Query parameters

  • assetstring

    An asset id (bitcoin) or symbol (BTC), any case. meta.assets lists the catalogue's assets.

    at most 64 characters

  • windowenum

    Only events of this duration: 5m to 4h are rolling up-or-down rounds, targets one-off events.

  • typeenum

    Only events of this prediction type: up or down (directional), above or below a strike (threshold), a price range (range) or hitting a price (target).

  • qstring

    Text search, any case: the event title, its assets' names and symbols, and its markets' questions and labels.

    at most 100 characters

  • sortenumdefault volume

    volume: 24-hour volume, highest first. endsAt: soonest end first.

  • limitintegerdefault 25

    Page size, 1 to 100.

    1..100

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • idstring

    Polymarket event id.

  • slugstring

    Polymarket event slug.

  • titlestring
  • urlstring

    The event on Polymarket.

  • imageUrlstring | null

    The event's icon, from Polymarket.

  • assetsobject[]
  • assets[].idstring

    Polywhaler's asset id, e.g. bitcoin. The asset filter takes it, or the symbol.

  • assets[].namestring
  • assets[].symbolstring

    Ticker symbol, e.g. BTC.

  • windowenum

    How long the event runs: 5m, 15m, 1h and 4h are rolling up-or-down rounds; daily, weekly, monthly and yearly recur on that schedule; targets are one-off events (hit-price and similar).

  • typeenum

    What the markets ask: directional, up or down over the round; threshold, above or below a strike at the end; range, between two strikes; target, whether the price reaches a level before the end.

  • endsAtstring (date-time)

    When the event ends.

  • volume24hUsdnumber | null

    The event's volume over the last 24 hours: the default sort.

  • liquidityUsdnumber | null
Show 17 more fields
  • marketsobject[]

    The event's open markets. Closed and ended markets, and rounds that have not started, are left out.

  • markets[].idstring

    Polymarket market id.

  • markets[].slugstring

    Polymarket market slug.

  • markets[].questionstring
  • markets[].labelstring

    The market's name within its event: a strike (120,000), a range (110,000-115,000), or the question.

  • markets[].conditionIdstring | null

    Polymarket condition id.

  • markets[].startsAtstring (date-time) | null

    When the market's price window opens: the start of an up-or-down round. Null when Polymarket gives none.

  • markets[].endsAtstring (date-time)

    When the market closes and resolves.

  • markets[].strikeLowUsdnumber | null

    The price level the market is about, in US dollars: the strike of an above/below or hit-price market, the floor of a range. Null when it has none (up or down).

  • markets[].strikeHighUsdnumber | null

    The ceiling of a range, or the level of a < bucket. Null otherwise.

  • markets[].volume24hUsdnumber | null

    Volume over the last 24 hours.

  • markets[].volumeUsdnumber | null

    Volume since the market opened.

  • markets[].liquidityUsdnumber | null

    Order-book liquidity.

  • markets[].outcomesobject[]

    Every outcome with its price, in Polymarket's order.

  • markets[].outcomes[].labelstring

    The outcome, e.g. Yes or Up.

  • markets[].outcomes[].tokenIdstring | null

    Polymarket CLOB token id of the outcome.

  • markets[].outcomes[].probabilitynumber | null

    The outcome's price on Polymarket, which is its implied probability. Null while the market has no quote (no liquidity, volume or trade yet).

meta

  • updatedAtstring (date-time)

    When the catalogue was last read from Polymarket (the older of its two halves).

  • staleboolean

    True when Polymarket did not answer the last refresh and this is the last good copy: see updatedAt.

  • partialboolean

    True when one half of the catalogue (the 5m to 4h rounds, or everything longer) has not loaded yet, so its events are missing.

  • assetsobject[]

    Every asset in the catalogue, unfiltered: the values asset takes.

  • assets[].idstring

    Polywhaler's asset id, e.g. bitcoin. The asset filter takes it, or the symbol.

  • assets[].namestring
  • assets[].symbolstring

    Ticker symbol, e.g. BTC.

curl 'https://polywhaler.com/api/v2/crypto/markets?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "id": "61240",
      "slug": "bitcoin-above-on-october-31",
      "title": "Bitcoin above ___ on October 31?",
      "url": "https://polymarket.com/event/bitcoin-above-on-october-31",
      "imageUrl": null,
      "assets": [
        {
          "id": "bitcoin",
          "name": "Bitcoin",
          "symbol": "BTC"
        }
      ],
      "window": "daily",
      "type": "threshold",
      "endsAt": "2026-10-31T16:00:00.000Z",
      "volume24hUsd": 1843210.55,
      "liquidityUsd": 612400,
      "markets": [
        {
          "id": "561201",
          "slug": "bitcoin-above-120k-on-october-31",
          "question": "Will the price of Bitcoin be above $120,000 on October 31?",
          "label": "120,000",
          "conditionId": "0x3f9a6c1e8b2d4f70a5c9e1b3d7f02468ace13579bdf02468ace13579bdf02468",
          "startsAt": null,
          "endsAt": "2026-10-31T16:00:00.000Z",
          "strikeLowUsd": 120000,
          "strikeHighUsd": null,
          "volume24hUsd": 402115.2,
          "volumeUsd": 2210400.75,
          "liquidityUsd": 88210.4,
          "outcomes": [
            {
              "label": "Yes",
              "tokenId": "71321045679252212594626385532706912750332728571942532289631379312455583992563",
              "probability": 0.62
            },
            {
              "label": "No",
              "tokenId": "48331043336612883890938759509493159234755048973500640148014422747788308965732",
              "probability": 0.38
            }
          ]
        }
      ]
    }
  ],
  "meta": {
    "updatedAt": "2026-10-09T14:03:27.000Z",
    "stale": false,
    "partial": false,
    "assets": [
      {
        "id": "bitcoin",
        "name": "Bitcoin",
        "symbol": "BTC"
      }
    ]
  },
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Forecasts#

3 endpoints

Get the latest Bitcoin forecast#

GET/v2/forecasts/btc
scope: readCached 60s

The latest hourly run, the one the forecast tab of polywhaler.com/crypto?tab=forecast shows. Each horizon is a price distribution fitted to every open Polymarket and Kalshi BTC price market, with deep, tight, active markets counting for more than thin ones. Markets priced as near-certain either way are left out of the fit. The full specification and version history are in the Developer portal for Quant subscribers: polywhaler.com/developer/methodology#bitcoin-forecast.

Query parameters

  • horizonsstring

    Only these horizons, comma-separated: 1w, 1m, 3m, 6m, 1y.

  • includestring

    density adds each horizon's density grid; fan the daily fan path.

Response

data
  • runAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • spotUsdnumber

    US dollars.

  • spotSourcestring
  • modelVersionstring

    The model that produced the run, e.g. btc-forecast/1.1.0. Methods change between versions, so segment backtests by it. The full specification and version history are in the Developer portal for Quant subscribers: polywhaler.com/developer/methodology#bitcoin-forecast.

  • marketCountinteger
  • confidencenumber

    Quality of the evidence behind the run, 0..1: the horizons' confidence combined, the better-evidenced ones counting more.

  • horizonsobject[]
  • horizons[].keyenum

    One of1w1m3m6m1y

  • horizons[].targetAtstring (date-time) | null

    The instant the fit targets (now + tYears).

  • horizons[].tYearsnumber

    The time the horizon looks ahead, in years: the average time to expiry of its markets, the more influential ones counting more, or for a synthetic horizon its target of 7, 30, 91, 182 or 365 days.

  • horizons[].medianUsdnumber

    US dollars.

  • horizons[].meanUsdnumber

    US dollars.

  • horizons[].ci50Usdany[]
  • horizons[].ci80Usdany[]
Show 36 more fields
  • horizons[].ci95Usdany[]
  • horizons[].scenariosUsdobject

    The 10th, 50th and 90th percentiles.

  • horizons[].scenariosUsd.bearnumber

    US dollars.

  • horizons[].scenariosUsd.basenumber

    US dollars.

  • horizons[].scenariosUsd.bullnumber

    US dollars.

  • horizons[].sigmaAnnualnumber

    Annualized volatility of the horizon's price distribution, as a fraction (0.55 = 55%).

  • horizons[].sigmaBorrowedboolean

    The volatility came from the neighbouring horizons, because this horizon's own markets could not pin it down.

  • horizons[].syntheticboolean

    No market expires near this horizon; the fit is interpolated.

  • horizons[].confidencenumber

    Quality of the evidence behind this horizon's fit, 0..1. Not a directional view.

  • horizons[].pointCountinteger

    Strike observations in the fit.

  • horizons[].clusterCountinteger

    Distinct strike clusters among them.

  • horizons[].rmsenumber

    How far the markets' prices sit from the fitted forecast on average, in probability (0..1; lower is a closer fit).

  • horizons[].effectiveSampleSizenumber

    How many equally weighted markets the evidence is worth.

  • horizons[].keyLevelsobject[]
  • horizons[].keyLevels[].levelUsdnumber

    US dollars.

  • horizons[].keyLevels[].probabilityAbovenumber

    A rate on 0..1.

  • horizons[].densityobject[]
  • horizons[].density[].priceUsdnumber

    US dollars.

  • horizons[].density[].densitynumber
  • insightsobject[]

    Plain-language bullets computed from the run (no LLM).

  • insights[].typeenum

    driver: one of the most influential markets in a horizon, and which way it pulls the forecast. divergence: Polymarket and Kalshi disagree at the same level and horizon. delta: the featured horizon's median and confidence against the run about 24 hours earlier. new_market: an influential market that was not in that run. inconsistency: strike pairs priced out of order, which count for less. lean: the featured horizon's implied chance that Bitcoin ends above spot, when the markets lean clearly one way. note: pipeline notes and the daily up-or-down market. skew appears only in runs stored before btc-forecast/1.1.0, which replaced it with lean. New types may be added: skip ones you do not know.

  • insights[].textstring
  • insights[].refsstring[]

    The market ids the bullet is about.

  • flagsobject[]
  • flags[].typeenum

    monotonicity: a higher strike priced above a lower one on the same platform, event and expiry (both count for less). cross_platform: the platforms disagree clearly on the same strike and expiry (the shallower book counts for less).

  • flags[].detailstring
  • flags[].marketIdsstring[]
  • fanPathobject[]
  • fanPath[].atstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • fanPath[].medianUsdnumber

    US dollars.

  • fanPath[].p025Usdnumber

    US dollars.

  • fanPath[].p10Usdnumber

    US dollars.

  • fanPath[].p25Usdnumber

    US dollars.

  • fanPath[].p75Usdnumber

    US dollars.

  • fanPath[].p90Usdnumber

    US dollars.

  • fanPath[].p975Usdnumber

    US dollars.

curl 'https://polywhaler.com/api/v2/forecasts/btc' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "runAt": "2026-07-19T15:05:00.000Z",
    "spotUsd": 64491,
    "spotSource": "coingecko",
    "modelVersion": "btc-forecast/1.1.0",
    "marketCount": 508,
    "confidence": 0.81,
    "horizons": [
      {
        "key": "1m",
        "targetAt": "2026-08-18T15:05:00.000Z",
        "tYears": 0.082,
        "medianUsd": 62570,
        "meanUsd": 63640,
        "ci50Usd": [
          56900,
          68800
        ],
        "ci80Usd": [
          46569,
          84070
        ],
        "ci95Usd": [
          40100,
          97600
        ],
        "scenariosUsd": {
          "bear": 46569,
          "base": 62570,
          "bull": 84070
        },
        "sigmaAnnual": 0.8,
        "sigmaBorrowed": true,
        "synthetic": true,
        "confidence": 0.68,
        "pointCount": 0,
        "clusterCount": 0,
        "rmse": 0,
        "effectiveSampleSize": 0,
        "keyLevels": [
          {
            "levelUsd": 60000,
            "probabilityAbove": 0.58
          },
          {
            "levelUsd": 70000,
            "probabilityAbove": 0.28
          }
        ]
      }
    ],
    "insights": [
      {
        "type": "divergence",
        "text": "Polymarket and Kalshi disagree near $61,500: 83% vs 90% for the same level and horizon."
      },
      {
        "type": "lean",
        "text": "The 1 month forecast leans bearish: markets imply a 45% chance Bitcoin is above today's $64,491 on Aug 18."
      }
    ],
    "flags": []
  }
}

List past Bitcoin forecasts#

GET/v2/forecasts/btc/history
scope: readCursor paginatedCached 300s

Earlier forecast runs, newest first, kept for a year, as summaries (no key levels, insights, flags, density grids or fan path). Each run carries the modelVersion that produced it. btc-forecast/1.1.0 changed the fit and the market weights, so segment backtests by modelVersion.

Query parameters

  • horizonsstring

    Only these horizons, comma-separated: 1w, 1m, 3m, 6m, 1y.

  • fromstring (date-time)

    Runs at or after this instant.

  • tostring (date-time)

    Runs before this instant.

  • limitintegerdefault 24

    Page size, 1 to 168.

    1..168

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • runAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • spotUsdnumber

    US dollars.

  • spotSourcestring
  • modelVersionstring

    The model that produced the run, e.g. btc-forecast/1.1.0. Methods change between versions, so segment backtests by it. The full specification and version history are in the Developer portal for Quant subscribers: polywhaler.com/developer/methodology#bitcoin-forecast.

  • marketCountinteger
  • confidencenumber

    Quality of the evidence behind the run, 0..1: the horizons' confidence combined, the better-evidenced ones counting more.

  • horizonsobject[]
  • horizons[].keyenum

    One of1w1m3m6m1y

  • horizons[].targetAtstring (date-time) | null

    The instant the fit targets (now + tYears).

  • horizons[].tYearsnumber

    The time the horizon looks ahead, in years: the average time to expiry of its markets, the more influential ones counting more, or for a synthetic horizon its target of 7, 30, 91, 182 or 365 days.

  • horizons[].medianUsdnumber

    US dollars.

  • horizons[].meanUsdnumber

    US dollars.

  • horizons[].ci50Usdany[]
  • horizons[].ci80Usdany[]
Show 36 more fields
  • horizons[].ci95Usdany[]
  • horizons[].scenariosUsdobject

    The 10th, 50th and 90th percentiles.

  • horizons[].scenariosUsd.bearnumber

    US dollars.

  • horizons[].scenariosUsd.basenumber

    US dollars.

  • horizons[].scenariosUsd.bullnumber

    US dollars.

  • horizons[].sigmaAnnualnumber

    Annualized volatility of the horizon's price distribution, as a fraction (0.55 = 55%).

  • horizons[].sigmaBorrowedboolean

    The volatility came from the neighbouring horizons, because this horizon's own markets could not pin it down.

  • horizons[].syntheticboolean

    No market expires near this horizon; the fit is interpolated.

  • horizons[].confidencenumber

    Quality of the evidence behind this horizon's fit, 0..1. Not a directional view.

  • horizons[].pointCountinteger

    Strike observations in the fit.

  • horizons[].clusterCountinteger

    Distinct strike clusters among them.

  • horizons[].rmsenumber

    How far the markets' prices sit from the fitted forecast on average, in probability (0..1; lower is a closer fit).

  • horizons[].effectiveSampleSizenumber

    How many equally weighted markets the evidence is worth.

  • horizons[].keyLevelsobject[]
  • horizons[].keyLevels[].levelUsdnumber

    US dollars.

  • horizons[].keyLevels[].probabilityAbovenumber

    A rate on 0..1.

  • horizons[].densityobject[]
  • horizons[].density[].priceUsdnumber

    US dollars.

  • horizons[].density[].densitynumber
  • insightsobject[]

    Plain-language bullets computed from the run (no LLM).

  • insights[].typeenum

    driver: one of the most influential markets in a horizon, and which way it pulls the forecast. divergence: Polymarket and Kalshi disagree at the same level and horizon. delta: the featured horizon's median and confidence against the run about 24 hours earlier. new_market: an influential market that was not in that run. inconsistency: strike pairs priced out of order, which count for less. lean: the featured horizon's implied chance that Bitcoin ends above spot, when the markets lean clearly one way. note: pipeline notes and the daily up-or-down market. skew appears only in runs stored before btc-forecast/1.1.0, which replaced it with lean. New types may be added: skip ones you do not know.

  • insights[].textstring
  • insights[].refsstring[]

    The market ids the bullet is about.

  • flagsobject[]
  • flags[].typeenum

    monotonicity: a higher strike priced above a lower one on the same platform, event and expiry (both count for less). cross_platform: the platforms disagree clearly on the same strike and expiry (the shallower book counts for less).

  • flags[].detailstring
  • flags[].marketIdsstring[]
  • fanPathobject[]
  • fanPath[].atstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • fanPath[].medianUsdnumber

    US dollars.

  • fanPath[].p025Usdnumber

    US dollars.

  • fanPath[].p10Usdnumber

    US dollars.

  • fanPath[].p25Usdnumber

    US dollars.

  • fanPath[].p75Usdnumber

    US dollars.

  • fanPath[].p90Usdnumber

    US dollars.

  • fanPath[].p975Usdnumber

    US dollars.

curl 'https://polywhaler.com/api/v2/forecasts/btc/history?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "runAt": "2026-07-19T15:05:00.000Z",
      "spotUsd": 64491,
      "spotSource": "coingecko",
      "modelVersion": "btc-forecast/1.1.0",
      "marketCount": 508,
      "confidence": 0.81,
      "horizons": [
        {
          "key": "1m",
          "targetAt": "2026-08-18T15:05:00.000Z",
          "tYears": 0.082,
          "medianUsd": 62570,
          "meanUsd": 63640,
          "ci50Usd": [
            56900,
            68800
          ],
          "ci80Usd": [
            46569,
            84070
          ],
          "ci95Usd": [
            40100,
            97600
          ],
          "scenariosUsd": {
            "bear": 46569,
            "base": 62570,
            "bull": 84070
          },
          "sigmaAnnual": 0.8,
          "sigmaBorrowed": true,
          "synthetic": true,
          "confidence": 0.68,
          "pointCount": 0,
          "clusterCount": 0,
          "rmse": 0,
          "effectiveSampleSize": 0
        }
      ]
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

List the markets behind the Bitcoin forecast#

GET/v2/forecasts/btc/markets
scope: readCached 60s

The prediction markets the latest run was fitted to, heaviest first (at most 500), with each one's weight and the factors behind it. Markets priced as near-certain either way are left out of the fit and do not appear. Up-or-down dailies appear with weight 0: they feed only the insights.

Query parameters

  • platformenum

    One ofpolymarketkalshi

  • horizonenum

    One of1w1m3m6m1y

Response

data is an array of
  • platformenum

    One ofpolymarketkalshi

  • marketIdstring
  • eventIdstring | null
  • titlestring
  • urlstring | null
  • kindstring

    How the market maps onto the price distribution: terminal_above, terminal_range, touch_above / touch_below (converted to an ending-price estimate; derived is true) or directional (up-or-down, not in the fit).

  • strikeLowUsdnumber | null
  • strikeHighUsdnumber | null
  • expiresAtstring (date-time) | null
  • probabilitynumber | null

    The market's price of Yes: the book's mid when both sides quote, else the last trade.

  • probabilityDebiasednumber | null

    The market's implied chance that Bitcoin ends at or above the strike, as the fit used it: corrected for prediction markets' habit of overpricing long shots. Null for up-or-down markets.

  • bidnumber | null
  • asknumber | null
  • spreadnumber | null

    The bid-ask spread relative to the price of the cheaper side of the book, so it reads the same whichever outcome is Yes. A tighter spread earns a higher weightFactors.spreadScore.

Show 10 more fields
  • liquidityUsdnumber | null
  • volume24hUsdnumber | null
  • volumeUsdnumber | null
  • openInterestUsdnumber | null
  • weightnumber

    The market's weight in its horizon's fit; 0 for up-or-down markets.

  • weightFactorsobject

    The factors behind weight, higher meaning more weight: liquidity, volume, spreadScore, freshness and oiScore (open interest), each 0..1; kindPenalty (lower for converted touch markets), clusterHaircut (the same strike quoted more than once shares its credit) and consistencyPenalty (lower for prices out of order), each up to 1; and platformPrior, centred on 1, which rewards platforms and market types whose resolved prices have been well calibrated. Empty for up-or-down markets.

  • contributionnumber | null

    Share of its horizon's total weight.

  • horizonstring | null

    The horizon the market is fitted in; null for up-or-down markets.

  • derivedboolean
  • flaggedboolean

meta

  • runAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • spotUsdnumber

    US dollars.

  • modelVersionstring

    The model that produced the run, e.g. btc-forecast/1.1.0. Methods change between versions, so segment backtests by it. The full specification and version history are in the Developer portal for Quant subscribers: polywhaler.com/developer/methodology#bitcoin-forecast.

curl 'https://polywhaler.com/api/v2/forecasts/btc/markets' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "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",
      "strikeLowUsd": 64249.99,
      "strikeHighUsd": null,
      "expiresAt": "2026-07-19T21:05:00.000Z",
      "probability": 0.62,
      "probabilityDebiased": 0.632,
      "bid": 0.61,
      "ask": 0.63,
      "spread": 0.053,
      "liquidityUsd": null,
      "volume24hUsd": 114329.95,
      "volumeUsd": 214100,
      "openInterestUsd": 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
    }
  ],
  "meta": {
    "runAt": "2026-07-19T15:05:00.000Z",
    "spotUsd": 64491,
    "modelVersion": "btc-forecast/1.1.0"
  }
}

Watchlist#

4 endpoints

List your watchlist#

GET/v2/watchlist
scope: readCursor paginated

The wallets your account follows, most recently added first.

Query parameters

  • limitintegerdefault 100

    Page size, 1 to 1000.

    1..1000

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • walletstring

    A Polygon wallet address, always lowercase.

  • namestring | null
  • imagestring | null
  • addedAtstring (date-time) | null
curl 'https://polywhaler.com/api/v2/watchlist?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
      "name": "sample-whale",
      "image": "string",
      "addedAt": "2026-10-09T14:03:27.000Z"
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Follow a wallet#

POST/v2/watchlist
scope: watchlist:write

Adds a wallet to your watchlist: 201 when it is new, 200 when it was already there (the name and image are updated). A watchlist holds at most 1000 wallets.

Request body

application/json
  • walletstringRequired

    Wallet address; any case, answered lowercase.

  • namestring | null

    A label for the wallet.

  • imagestring | null

    An image URL for the wallet.

Response

data
  • walletstring

    A Polygon wallet address, always lowercase.

  • namestring | null
  • imagestring | null
  • addedAtstring (date-time) | null
curl -X POST 'https://polywhaler.com/api/v2/watchlist' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"wallet":"0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913"}'
Example response
{
  "data": {
    "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
    "name": "sample-whale",
    "image": "string",
    "addedAt": "2026-10-09T14:03:27.000Z"
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

List watchlist consensus#

GET/v2/watchlist/consensus
scope: readCached 60s

Positions that several wallets on your watchlist took on the same side over the window.

Query parameters

  • timeenumdefault 7d

    The window ending now.

    One of1h6h24h7d30d

  • minWhalesintegerdefault 3

    Least number of watched wallets in agreement.

    2..100

  • sideenumdefault BUY

    Taker side: BUY or SELL.

  • minAmountnumber

    Minimum combined position value in USD.

    min 0

Response

data is an array of
  • positionKeystring

    Stable id of the converged position.

  • titlestring | null
  • outcomestring | null
  • outcomeIndexinteger | null
  • eventSlugstring | null
  • marketSlugstring | null
  • iconstring | null
  • categoryenum | null

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • sideenum

    Taker side of the fill.

    One ofBUYSELL

  • whaleCountinteger
  • valueUsdnumber

    Combined notional across the converging wallets.

  • lastTradeAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • whalesobject[]
  • whales[].walletstring

    A Polygon wallet address, always lowercase.

Show 4 more fields
  • whales[].namestring | null
  • whales[].imagestring | null
  • whales[].valueUsdnumber

    US dollars.

  • whales[].lastTradeAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

meta

  • watchlistSizeinteger
  • timeenum

    One of1h6h24h7d30d

  • minWhalesinteger
  • sideenum

    One ofBUYSELL

curl 'https://polywhaler.com/api/v2/watchlist/consensus' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "positionKey": "string",
      "title": "Will BTC close above $130k on Oct 31?",
      "outcome": "Yes",
      "outcomeIndex": 0,
      "eventSlug": "bitcoin-above-on-october-31",
      "marketSlug": "bitcoin-above-130k-on-october-31",
      "icon": "string",
      "category": "crypto",
      "side": "BUY",
      "whaleCount": 2,
      "valueUsd": 0,
      "lastTradeAt": "2026-10-09T14:03:27.000Z",
      "whales": [
        {
          "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
          "name": "sample-whale",
          "image": "string",
          "valueUsd": 0,
          "lastTradeAt": "2026-10-09T14:03:27.000Z"
        }
      ]
    }
  ],
  "meta": {
    "watchlistSize": 0,
    "time": "1h",
    "minWhales": 0,
    "side": "BUY"
  }
}

Unfollow a wallet#

DELETE/v2/watchlist/{address}
scope: watchlist:write

Removes a wallet from your watchlist. 404 when it was not on it.

Path parameters

  • addressstringRequiredpath

    Wallet address; any case, answered lowercase.

    pattern ^0x[0-9a-fA-F]{40}$

Response

data
  • walletstring

    A Polygon wallet address, always lowercase.

  • removedtrue
curl -X DELETE 'https://polywhaler.com/api/v2/watchlist/0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "wallet": "0x5c1d7e93a0b4f26c8e1a9d3b7f05c2e4a6b8d913",
    "removed": true
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

Exports#

4 endpoints

List your exports#

GET/v2/exports
scope: readCursor paginated

Your bulk exports, newest first.

Query parameters

  • limitintegerdefault 20

    Page size, 1 to 100.

    1..100

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • idstring
  • statusenum

    One ofqueuedprocessingcompletedfailed

  • formatenum

    One ofcsvjsonparquet

  • datasetenum

    One oftradesoutcomes

  • filtersobject

    The filters the export ran with, camelCase.

  • rowCountinteger | null
  • fileSizeBytesinteger | null
  • errorstring | null

    Why a failed export failed.

  • requestedAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • startedAtstring (date-time) | null
  • completedAtstring (date-time) | null
  • expiresAtstring (date-time) | null

    When the file is deleted. GET /v2/exports/{id}/download returns 410 after it.

  • downloadUrlstring | null

    Direct link to the file while the export is completed and not expired; null otherwise. Fetch it without your API key: it is on another host.

curl 'https://polywhaler.com/api/v2/exports?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "status": "queued",
      "format": "csv",
      "dataset": "trades",
      "filters": {},
      "rowCount": 0,
      "fileSizeBytes": 0,
      "error": "string",
      "requestedAt": "2026-10-09T14:03:27.000Z",
      "startedAt": "2026-10-09T14:03:27.000Z",
      "completedAt": "2026-10-09T14:03:27.000Z",
      "expiresAt": "2026-10-09T14:03:27.000Z",
      "downloadUrl": "string"
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Start an export#

POST/v2/exports
scope: exports:writeIdempotency-Key

Queues a bulk export of up to 100,000 rows (202). Poll GET /v2/exports/{id}, or subscribe to export.completed. At most 3 exports can be queued or running at once. Send an Idempotency-Key to retry safely.

Headers

  • Idempotency-Keystringheader

    Retry-safe creation: the same key and request within 24 hours replay the first response.

    at most 255 characters

Request body

application/json
  • formatenum

    One ofcsvjsonparquet

  • datasetenum

    One oftradesoutcomes

  • filtersobject

    Row filters. Each dataset takes its own subset; a filter the dataset does not take is 400.

  • filters.fromstring (date-time)

    Rows at or after this instant.

  • filters.tostring (date-time)

    Rows up to this instant, inclusive.

  • filters.walletstring

    Wallet address; any case, answered lowercase.

  • filters.categoryenum

    trades only.

    One ofcryptopoliticseconomygeopoliticstechculturesciencesportsesportsothermarkets

  • filters.sideenum

    trades only.

    One ofBUYSELL

  • filters.minAmountnumber

    trades only.

  • filters.minInsiderScorenumber

    trades only.

  • filters.eventSlugstring
  • filters.marketSlugstring

    trades only.

  • filters.statusenum

    outcomes only.

    One ofwinlosspendingcancelledinvalid

  • filters.vindicatedboolean

    outcomes only.

Response

data
  • idstring
  • statusenum

    One ofqueuedprocessingcompletedfailed

  • formatenum

    One ofcsvjsonparquet

  • datasetenum

    One oftradesoutcomes

  • filtersobject

    The filters the export ran with, camelCase.

  • rowCountinteger | null
  • fileSizeBytesinteger | null
  • errorstring | null

    Why a failed export failed.

  • requestedAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • startedAtstring (date-time) | null
  • completedAtstring (date-time) | null
  • expiresAtstring (date-time) | null

    When the file is deleted. GET /v2/exports/{id}/download returns 410 after it.

  • downloadUrlstring | null

    Direct link to the file while the export is completed and not expired; null otherwise. Fetch it without your API key: it is on another host.

curl -X POST 'https://polywhaler.com/api/v2/exports' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"format":"csv","dataset":"trades","filters":{}}'
Example response
{
  "data": {
    "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
    "status": "queued",
    "format": "csv",
    "dataset": "trades",
    "filters": {},
    "rowCount": 0,
    "fileSizeBytes": 0,
    "error": "string",
    "requestedAt": "2026-10-09T14:03:27.000Z",
    "startedAt": "2026-10-09T14:03:27.000Z",
    "completedAt": "2026-10-09T14:03:27.000Z",
    "expiresAt": "2026-10-09T14:03:27.000Z",
    "downloadUrl": "string"
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

Get an export#

GET/v2/exports/{id}
scope: read

Path parameters

  • idstringRequiredpath

    at most 100 characters

Response

data
  • idstring
  • statusenum

    One ofqueuedprocessingcompletedfailed

  • formatenum

    One ofcsvjsonparquet

  • datasetenum

    One oftradesoutcomes

  • filtersobject

    The filters the export ran with, camelCase.

  • rowCountinteger | null
  • fileSizeBytesinteger | null
  • errorstring | null

    Why a failed export failed.

  • requestedAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • startedAtstring (date-time) | null
  • completedAtstring (date-time) | null
  • expiresAtstring (date-time) | null

    When the file is deleted. GET /v2/exports/{id}/download returns 410 after it.

  • downloadUrlstring | null

    Direct link to the file while the export is completed and not expired; null otherwise. Fetch it without your API key: it is on another host.

curl 'https://polywhaler.com/api/v2/exports/6f1c3d2b-8a9e-4f50-8172-8394a5b6c7d8' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
    "status": "queued",
    "format": "csv",
    "dataset": "trades",
    "filters": {},
    "rowCount": 0,
    "fileSizeBytes": 0,
    "error": "string",
    "requestedAt": "2026-10-09T14:03:27.000Z",
    "startedAt": "2026-10-09T14:03:27.000Z",
    "completedAt": "2026-10-09T14:03:27.000Z",
    "expiresAt": "2026-10-09T14:03:27.000Z",
    "downloadUrl": "string"
  }
}

Get an export's download link#

GET/v2/exports/{id}/download
scope: read

The file's URL as JSON (not a redirect, so your client never forwards its API key to the file host). Fetch the URL without your key. 404 while the export is queued or running, 410 gone once it has expired and the file is deleted.

Path parameters

  • idstringRequiredpath

    at most 100 characters

Response

data
  • urlstring

    A direct link to the file. Fetch it without your API key: it is on another host.

  • expiresAtstring (date-time) | null
  • fileSizeBytesinteger | null
  • formatenum

    One ofcsvjsonparquet

curl 'https://polywhaler.com/api/v2/exports/6f1c3d2b-8a9e-4f50-8172-8394a5b6c7d8/download' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "url": "https://hooks.example.com/polywhaler",
    "expiresAt": "2026-10-09T14:03:27.000Z",
    "fileSizeBytes": 0,
    "format": "csv"
  }
}

Webhooks#

14 endpoints

List webhook endpoints#

GET/v2/webhook-endpoints
scope: readCursor paginated

Your endpoints, newest first. v1 subscriptions are included with apiVersion: "v1".

Query parameters

  • limitintegerdefault 50

    Page size, 1 to 100.

    1..100

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • idstring

    Endpoint id, we_… (v1 subscriptions created before v2 keep their uuid).

  • apiVersionenum

    Payload and signature format. v1 endpoints are frozen; upgrade to get v2 events.

  • urlstring
  • typesstring[]

    Event types delivered to the endpoint.

  • descriptionstring | null
  • filtersobject
  • enabledboolean
  • healthenum

    healthy: last attempt succeeded; degraded: recent failures; failing: failing continuously; disabled: not sending.

  • disabledReasonenum | null

    One ofmanualfailinggonesunset

  • disabledAtstring (date-time) | null
  • consecutiveFailuresinteger
  • lastSuccessAtstring (date-time) | null
  • createdAtstring (date-time) | null
  • updatedAtstring (date-time) | null
Show 1 more fields
  • secretstring

    The signing secret, whsec_…. Only in the response that created or rotated it.

curl 'https://polywhaler.com/api/v2/webhook-endpoints?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "id": "we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ",
      "apiVersion": "v2",
      "url": "https://hooks.example.com/polywhaler",
      "types": [
        "trade.smart_money",
        "wallet.rank_changed"
      ],
      "description": "Trading desk",
      "filters": {
        "maxRank": 25,
        "minAmount": 25000
      },
      "enabled": true,
      "health": "healthy",
      "disabledReason": null,
      "disabledAt": null,
      "consecutiveFailures": 0,
      "lastSuccessAt": "2026-10-09T14:03:29.000Z",
      "createdAt": "2026-10-01T09:00:00.000Z",
      "updatedAt": "2026-10-01T09:00:00.000Z"
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Create a webhook endpoint#

POST/v2/webhook-endpoints
scope: webhooks:writeIdempotency-Key

Creates a v2 endpoint (Standard Webhooks signing). The response carries the signing secret, whsec_…, once: store it. At most 10 endpoints per account. Supports Idempotency-Key; a replay returns the endpoint without its secret.

Headers

  • Idempotency-Keystringheader

    Retry-safe creation: the same key and request within 24 hours replay the first response.

    at most 255 characters

Request body

application/json
  • urlstringRequired

    Public https endpoint, port 443 or 8443. Redirects are not followed.

  • typesenum[]Required

    Event types to deliver.

    One oftrade.whaletrade.smart_moneytrade.followed_wallettrade.insider_flaggedtrade.vindicatedwallet.rank_changedwallet.score_changedleaderboard.updatedcluster.detectedconsensus.detectedblack_swan.status_changedoracle.edition_publishedexport.completedexport.failedbtc_forecast.updated

  • descriptionstring | null
  • filtersany

    WebhookEndpointFilters; validated against types.

Response

data
  • idstring

    Endpoint id, we_… (v1 subscriptions created before v2 keep their uuid).

  • apiVersionenum

    Payload and signature format. v1 endpoints are frozen; upgrade to get v2 events.

  • urlstring
  • typesstring[]

    Event types delivered to the endpoint.

  • descriptionstring | null
  • filtersobject
  • enabledboolean
  • healthenum

    healthy: last attempt succeeded; degraded: recent failures; failing: failing continuously; disabled: not sending.

  • disabledReasonenum | null

    One ofmanualfailinggonesunset

  • disabledAtstring (date-time) | null
  • consecutiveFailuresinteger
  • lastSuccessAtstring (date-time) | null
  • createdAtstring (date-time) | null
  • updatedAtstring (date-time) | null
Show 1 more fields
  • secretstring

    The signing secret, whsec_…. Only in the response that created or rotated it.

Errors

  • 400filter_not_applicableA filter does not apply to any of the subscribed event types.
  • 400url_not_allowedThe URL must be a public https endpoint on port 443 or 8443, without credentials, whose host does not resolve to a private address.
  • 409resource_limit_reachedThe account has reached the limit for this resource.

Also invalid_json, invalid_param, api_key_expired, api_key_revoked, invalid_api_key, missing_api_key, insufficient_scope, plan_required, idempotency_in_progress, idempotency_key_reused, payload_too_large, unsupported_media_type, quota_exceeded, rate_limited, internal_error, temporarily_unavailable.

curl -X POST 'https://polywhaler.com/api/v2/webhook-endpoints' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/polywhaler","types":["trade.whale"]}'
Example response
{
  "data": {
    "id": "we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ",
    "apiVersion": "v2",
    "url": "https://hooks.example.com/polywhaler",
    "types": [
      "trade.smart_money",
      "wallet.rank_changed"
    ],
    "description": "Trading desk",
    "filters": {
      "maxRank": 25,
      "minAmount": 25000
    },
    "enabled": true,
    "health": "healthy",
    "disabledReason": null,
    "disabledAt": null,
    "consecutiveFailures": 0,
    "lastSuccessAt": "2026-10-09T14:03:29.000Z",
    "createdAt": "2026-10-01T09:00:00.000Z",
    "updatedAt": "2026-10-01T09:00:00.000Z",
    "secret": "whsec_MfKQ9r8GKYqrTwjUPD8ILPZIo2LaLaSw"
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

Get a webhook endpoint#

GET/v2/webhook-endpoints/{id}
scope: read

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Response

data
  • idstring

    Endpoint id, we_… (v1 subscriptions created before v2 keep their uuid).

  • apiVersionenum

    Payload and signature format. v1 endpoints are frozen; upgrade to get v2 events.

  • urlstring
  • typesstring[]

    Event types delivered to the endpoint.

  • descriptionstring | null
  • filtersobject
  • enabledboolean
  • healthenum

    healthy: last attempt succeeded; degraded: recent failures; failing: failing continuously; disabled: not sending.

  • disabledReasonenum | null

    One ofmanualfailinggonesunset

  • disabledAtstring (date-time) | null
  • consecutiveFailuresinteger
  • lastSuccessAtstring (date-time) | null
  • createdAtstring (date-time) | null
  • updatedAtstring (date-time) | null
Show 1 more fields
  • secretstring

    The signing secret, whsec_…. Only in the response that created or rotated it.

curl 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "id": "we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ",
    "apiVersion": "v2",
    "url": "https://hooks.example.com/polywhaler",
    "types": [
      "trade.smart_money",
      "wallet.rank_changed"
    ],
    "description": "Trading desk",
    "filters": {
      "maxRank": 25,
      "minAmount": 25000
    },
    "enabled": true,
    "health": "healthy",
    "disabledReason": null,
    "disabledAt": null,
    "consecutiveFailures": 0,
    "lastSuccessAt": "2026-10-09T14:03:29.000Z",
    "createdAt": "2026-10-01T09:00:00.000Z",
    "updatedAt": "2026-10-01T09:00:00.000Z"
  }
}

Update a webhook endpoint#

PATCH/v2/webhook-endpoints/{id}
scope: webhooks:write

Change the URL, types, description or filters, or pause (enabled: false) and resume (enabled: true, which also clears a failing or gone state). v1 endpoints keep their events and filters; upgrade them to change those.

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Request body

application/json
  • urlstring

    Public https endpoint, port 443 or 8443. Redirects are not followed.

  • typesenum[]

    One oftrade.whaletrade.smart_moneytrade.followed_wallettrade.insider_flaggedtrade.vindicatedwallet.rank_changedwallet.score_changedleaderboard.updatedcluster.detectedconsensus.detectedblack_swan.status_changedoracle.edition_publishedexport.completedexport.failedbtc_forecast.updated

  • descriptionstring | null
  • filtersany

    Replaces the filters. null or {} clears them.

  • enabledboolean

    false pauses sending; true resumes and clears a failing or gone state.

Response

data
  • idstring

    Endpoint id, we_… (v1 subscriptions created before v2 keep their uuid).

  • apiVersionenum

    Payload and signature format. v1 endpoints are frozen; upgrade to get v2 events.

  • urlstring
  • typesstring[]

    Event types delivered to the endpoint.

  • descriptionstring | null
  • filtersobject
  • enabledboolean
  • healthenum

    healthy: last attempt succeeded; degraded: recent failures; failing: failing continuously; disabled: not sending.

  • disabledReasonenum | null

    One ofmanualfailinggonesunset

  • disabledAtstring (date-time) | null
  • consecutiveFailuresinteger
  • lastSuccessAtstring (date-time) | null
  • createdAtstring (date-time) | null
  • updatedAtstring (date-time) | null
Show 1 more fields
  • secretstring

    The signing secret, whsec_…. Only in the response that created or rotated it.

Errors

  • 400filter_not_applicableA filter does not apply to any of the subscribed event types.
  • 400url_not_allowedThe URL must be a public https endpoint on port 443 or 8443, without credentials, whose host does not resolve to a private address.
  • 404not_foundThe resource does not exist.
  • 409conflictThe resource is not in a state that allows this.

Also invalid_json, invalid_param, api_key_expired, api_key_revoked, invalid_api_key, missing_api_key, insufficient_scope, plan_required, payload_too_large, unsupported_media_type, quota_exceeded, rate_limited, internal_error, temporarily_unavailable.

curl -X PATCH 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"url":"https://hooks.example.com/polywhaler","types":["trade.whale"],"description":"Trading desk","filters":null,"enabled":true}'
Example response
{
  "data": {
    "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
    "apiVersion": "v1",
    "url": "https://hooks.example.com/polywhaler",
    "types": [
      "string"
    ],
    "description": "Trading desk",
    "filters": {},
    "enabled": true,
    "health": "healthy",
    "disabledReason": "manual",
    "disabledAt": "2026-10-09T14:03:27.000Z",
    "consecutiveFailures": 0,
    "lastSuccessAt": "2026-10-09T14:03:27.000Z",
    "createdAt": "2026-10-09T14:03:27.000Z",
    "updatedAt": "2026-10-09T14:03:27.000Z",
    "secret": "string"
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

Delete a webhook endpoint#

DELETE/v2/webhook-endpoints/{id}
scope: webhooks:write

Stops delivery at once. Pending deliveries are cancelled; the delivery log is kept 30 days.

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Response

data
  • idstring
  • deletedtrue
curl -X DELETE 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
    "deleted": true
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

List an endpoint's deliveries#

GET/v2/webhook-endpoints/{id}/deliveries
scope: readCursor paginated

Newest first, kept 30 days.

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Query parameters

  • statusenum

    One ofpendingretryingdeliveredfailed

  • typeenum

    The event type (a v1 name on v1 endpoints).

    One oftrade.whaletrade.smart_moneytrade.followed_wallettrade.insider_flaggedtrade.vindicatedwallet.rank_changedwallet.score_changedleaderboard.updatedcluster.detectedconsensus.detectedblack_swan.status_changedoracle.edition_publishedexport.completedexport.failedbtc_forecast.updatedtrade.high_insiderand 2 more

  • sincestring (date-time)

    Deliveries created at or after this instant.

  • limitintegerdefault 50

    Page size, 1 to 100.

    1..100

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • idstring

    The message id receivers see in webhook-id (msg_…; v1: the X-Polywhaler-Delivery value).

  • eventIdstring | null
  • typestring
  • statusenum

    pending: not attempted yet; retrying: attempted and will be retried; delivered: a 2xx; failed: out of attempts.

  • attemptsinteger
  • maxAttemptsinteger
  • lastStatusCodeinteger | null
  • lastLatencyMsinteger | null
  • lastErrorstring | null

    A short error class (e.g. timeout, http_500), never the remote body.

  • nextAttemptAtstring (date-time) | null
  • deliveredAtstring (date-time) | null
  • createdAtstring (date-time) | null
  • attemptHistoryobject[]

    Every attempt (GET of one delivery only).

  • attemptHistory[].attemptinteger
Show 5 more fields
  • attemptHistory[].atstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • attemptHistory[].statusCodeinteger | null
  • attemptHistory[].latencyMsinteger | null
  • attemptHistory[].errorstring | null
  • attemptHistory[].responseSnippetstring | null

    The first 2 KB of the response body.

curl 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/deliveries?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "eventId": "string",
      "type": "string",
      "status": "pending",
      "attempts": 0,
      "maxAttempts": 1,
      "lastStatusCode": 0,
      "lastLatencyMs": 0,
      "lastError": "string",
      "nextAttemptAt": "2026-10-09T14:03:27.000Z",
      "deliveredAt": "2026-10-09T14:03:27.000Z",
      "createdAt": "2026-10-09T14:03:27.000Z",
      "attemptHistory": [
        {
          "attempt": 1,
          "at": "2026-10-09T14:03:27.000Z",
          "statusCode": 0,
          "latencyMs": 0,
          "error": "string",
          "responseSnippet": "string"
        }
      ]
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}

Get a delivery#

GET/v2/webhook-endpoints/{id}/deliveries/{msgId}
scope: read

Every attempt with its status, latency and the first 2 KB of the response, and the request as sent (signatures redacted).

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

  • msgIdstringRequiredpath

    The delivery id receivers saw in webhook-id (msg_…; v1: X-Polywhaler-Delivery).

    at most 64 characters

Response

data
  • idstring

    The message id receivers see in webhook-id (msg_…; v1: the X-Polywhaler-Delivery value).

  • eventIdstring | null
  • typestring
  • statusenum

    pending: not attempted yet; retrying: attempted and will be retried; delivered: a 2xx; failed: out of attempts.

  • attemptsinteger
  • maxAttemptsinteger
  • lastStatusCodeinteger | null
  • lastLatencyMsinteger | null
  • lastErrorstring | null

    A short error class (e.g. timeout, http_500), never the remote body.

  • nextAttemptAtstring (date-time) | null
  • deliveredAtstring (date-time) | null
  • createdAtstring (date-time) | null
  • attemptHistoryobject[]

    Every attempt (GET of one delivery only).

  • attemptHistory[].attemptinteger
Show 8 more fields
  • attemptHistory[].atstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • attemptHistory[].statusCodeinteger | null
  • attemptHistory[].latencyMsinteger | null
  • attemptHistory[].errorstring | null
  • attemptHistory[].responseSnippetstring | null

    The first 2 KB of the response body.

  • requestobject | null
  • request.headersobject

    As sent on the last attempt; signatures redacted.

  • request.bodystring

    The exact body sent.

curl 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/deliveries/msg_01K7A0Z8M3Q4R5S6T7V8W9X0YZ' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
    "eventId": "string",
    "type": "string",
    "status": "pending",
    "attempts": 0,
    "maxAttempts": 1,
    "lastStatusCode": 0,
    "lastLatencyMs": 0,
    "lastError": "string",
    "nextAttemptAt": "2026-10-09T14:03:27.000Z",
    "deliveredAt": "2026-10-09T14:03:27.000Z",
    "createdAt": "2026-10-09T14:03:27.000Z",
    "attemptHistory": [
      {
        "attempt": 1,
        "at": "2026-10-09T14:03:27.000Z",
        "statusCode": 0,
        "latencyMs": 0,
        "error": "string",
        "responseSnippet": "string"
      }
    ],
    "request": {
      "headers": {},
      "body": "string"
    }
  }
}

Redeliver a delivery#

POST/v2/webhook-endpoints/{id}/deliveries/{msgId}/redeliver
scope: webhooks:write

Sends it again with the same webhook-id. 10 an hour per endpoint.

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

  • msgIdstringRequiredpath

    The delivery id receivers saw in webhook-id (msg_…; v1: X-Polywhaler-Delivery).

    at most 64 characters

Response

data
  • idstring
  • status"pending"
  • nextAttemptAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

curl -X POST 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/deliveries/msg_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/redeliver' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
    "status": "pending",
    "nextAttemptAt": "2026-10-09T14:03:27.000Z"
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

Resend failed deliveries#

POST/v2/webhook-endpoints/{id}/recover
scope: webhooks:write

Sends again every failed delivery created since since (at most 1,000, within the 30-day log), e.g. after re-enabling an endpoint.

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Request body

application/json
  • sincestringRequired

    ISO-8601 instant: failed deliveries created since then are sent again (at most 1,000).

Response

data
  • requeuedinteger
curl -X POST 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/recover' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"since":"2026-10-09T14:03:27.000Z"}'
Example response
{
  "data": {
    "requeued": 0
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

Rotate the signing secret#

POST/v2/webhook-endpoints/{id}/rotate-secret
scope: webhooks:writeIdempotency-Key

Issues a new secret, shown once. The current one keeps signing alongside it (two v1, signatures) for expireCurrentInSeconds, default 24 hours. v2 endpoints only.

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Headers

  • Idempotency-Keystringheader

    Retry-safe creation: the same key and request within 24 hours replay the first response.

    at most 255 characters

Request body

application/json, optional
  • expireCurrentInSecondsinteger

    How long the current secret keeps signing alongside the new one. Default 86400 (24 hours), at most 7 days.

Response

data
  • secretstring

    The new signing secret, whsec_…. Shown once.

  • previousSecretExpiresAtstring (date-time) | null
curl -X POST 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/rotate-secret' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"expireCurrentInSeconds":0}'
Example response
{
  "data": {
    "secret": "string",
    "previousSecretExpiresAt": "2026-10-09T14:03:27.000Z"
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

Delivery statistics#

GET/v2/webhook-endpoints/{id}/stats
scope: readCached 30s

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Query parameters

  • windowenumdefault 24h

    24h (hourly series) or 7d (6-hourly).

Response

data
  • windowenum

    One of24h7d

  • attemptsinteger
  • succeededinteger
  • failedinteger
  • successRatenumber | null
  • p50LatencyMsnumber | null
  • p95LatencyMsnumber | null
  • statusesobject

    Attempts by HTTP status, or by error class when there was no response.

  • deliveriesobject
  • deliveries.totalinteger
  • deliveries.deliveredinteger
  • deliveries.failedinteger
  • deliveries.pendinginteger
  • seriesobject[]
Show 3 more fields
  • series[].atstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • series[].attemptsinteger
  • series[].succeededinteger
curl 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/stats' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "window": "24h",
    "attempts": 0,
    "succeeded": 0,
    "failed": 0,
    "successRate": 0,
    "p50LatencyMs": 0,
    "p95LatencyMs": 0,
    "statuses": {},
    "deliveries": {
      "total": 0,
      "delivered": 0,
      "failed": 0,
      "pending": 0
    },
    "series": [
      {
        "at": "2026-10-09T14:03:27.000Z",
        "attempts": 0,
        "succeeded": 0
      }
    ]
  }
}

Send a test event#

POST/v2/webhook-endpoints/{id}/test
scope: webhooks:write

Sends one event of type (test: true, one attempt) and answers with the result. source: "latest" uses the newest real record of the type (your own, for owner-scoped types), "fixture" a fixed sample. 20 an hour per endpoint. The test appears in the delivery log.

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Request body

application/json, optional
  • typeenum

    Defaults to the endpoint's first type.

    One oftrade.whaletrade.smart_moneytrade.followed_wallettrade.insider_flaggedtrade.vindicatedwallet.rank_changedwallet.score_changedleaderboard.updatedcluster.detectedconsensus.detectedblack_swan.status_changedoracle.edition_publishedexport.completedexport.failedbtc_forecast.updated

  • sourceenum

    latest: the newest real record of that type (your own, for owner-scoped types); fixture: a fixed sample. Default fixture.

Response

data
  • eventIdstring
  • deliveryIdstring

    The webhook-id the endpoint received.

  • typestring
  • deliveredboolean
  • statusCodeinteger | null
  • latencyMsinteger | null
  • errorstring | null
curl -X POST 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/test' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"type":"trade.whale","source":"latest"}'
Example response
{
  "data": {
    "eventId": "string",
    "deliveryId": "string",
    "type": "string",
    "delivered": true,
    "statusCode": 0,
    "latencyMs": 0,
    "error": "string"
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

Upgrade a v1 endpoint to v2#

POST/v2/webhook-endpoints/{id}/upgrade
scope: webhooks:writeIdempotency-Key

Creates a v2 endpoint with the same URL, the v1 events mapped to their v2 types, the filters in v2 form and a new whsec_ secret (shown once). The v1 endpoint keeps running until you disable or delete it; during the overlap each event arrives on both, with the same X-Polywhaler-Event-Id / envelope id.

Path parameters

  • idstringRequiredpath

    The endpoint id, we_… (a v1 subscription's uuid works too).

    at most 64 characters

Headers

  • Idempotency-Keystringheader

    Retry-safe creation: the same key and request within 24 hours replay the first response.

    at most 255 characters

Response

data
  • endpointWebhookEndpoint

    A URL Polywhaler delivers events to.

  • endpoint.idstring

    Endpoint id, we_… (v1 subscriptions created before v2 keep their uuid).

  • endpoint.apiVersionenum

    Payload and signature format. v1 endpoints are frozen; upgrade to get v2 events.

  • endpoint.urlstring
  • endpoint.typesstring[]

    Event types delivered to the endpoint.

  • endpoint.descriptionstring | null
  • endpoint.filtersobject
  • endpoint.enabledboolean
  • endpoint.healthenum

    healthy: last attempt succeeded; degraded: recent failures; failing: failing continuously; disabled: not sending.

  • endpoint.disabledReasonenum | null

    One ofmanualfailinggonesunset

  • endpoint.disabledAtstring (date-time) | null
  • endpoint.consecutiveFailuresinteger
  • endpoint.lastSuccessAtstring (date-time) | null
  • endpoint.createdAtstring (date-time) | null
Show 4 more fields
  • endpoint.updatedAtstring (date-time) | null
  • endpoint.secretstring

    The signing secret, whsec_…. Only in the response that created or rotated it.

  • secretstring
  • replacesstring

    The v1 endpoint, left running until you disable it.

Errors

Also invalid_param, api_key_expired, api_key_revoked, invalid_api_key, missing_api_key, insufficient_scope, plan_required, idempotency_in_progress, idempotency_key_reused, quota_exceeded, rate_limited, internal_error, temporarily_unavailable.

curl -X POST 'https://polywhaler.com/api/v2/webhook-endpoints/we_01K7A0Z8M3Q4R5S6T7V8W9X0YZ/upgrade' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "endpoint": {
      "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "apiVersion": "v1",
      "url": "https://hooks.example.com/polywhaler",
      "types": [
        "string"
      ],
      "description": "Trading desk",
      "filters": {},
      "enabled": true,
      "health": "healthy",
      "disabledReason": "manual",
      "disabledAt": "2026-10-09T14:03:27.000Z",
      "consecutiveFailures": 0,
      "lastSuccessAt": "2026-10-09T14:03:27.000Z",
      "createdAt": "2026-10-09T14:03:27.000Z",
      "updatedAt": "2026-10-09T14:03:27.000Z",
      "secret": "string"
    },
    "secret": "string",
    "replaces": "string"
  }
}

Try it sends GET requests only. Run the samples above with your own key to call this endpoint.

List webhook event types#

GET/v2/webhook-event-types
No key neededCached 3600s

Every event type, with the filters that apply to it, its v1 name, the JSON Schema of its data and an example delivery. No key needed.

Response

data is an array of
  • typestring
  • descriptionstring
  • resourcestring

    The resource data carries, as in components.schemas.

  • filtersstring[]

    Endpoint filters that apply to this type.

  • v1Aliasstring | null
  • schemaobject

    JSON Schema of the event's data.

  • exampleany
curl 'https://polywhaler.com/api/v2/webhook-event-types' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "type": "string",
      "description": "Trading desk",
      "resource": "string",
      "filters": [
        "string"
      ],
      "v1Alias": "string",
      "schema": {},
      "example": null
    }
  ]
}

Account#

2 endpoints

Get your quota and key#

GET/v2/usage
scope: read

Today's request count against the daily quota (shared by v1 and v2, reset at 00:00 UTC), this minute's count, the calling key and account totals. Does not count against the daily quota.

Response

data
  • tier"quant"
  • quotaobject
  • quota.dailyLimitinteger

    Requests allowed per UTC day, across v1 and v2.

  • quota.usedTodayinteger
  • quota.remaininginteger
  • quota.resetsAtstring (date-time)

    Next UTC midnight.

  • rateLimitobject
  • rateLimit.perMinuteinteger
  • rateLimit.usedThisMinuteinteger
  • rateLimit.resetsAtstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • keyobject | null
  • key.idstring
  • key.namestring | null
  • key.prefixstring | null
Show 8 more fields
  • key.scopesenum[]

    One ofreadwatchlist:writewebhooks:writeexports:write

  • key.createdAtstring (date-time) | null
  • key.lastUsedAtstring (date-time) | null
  • key.expiresAtstring (date-time) | null
  • accountobject
  • account.activeKeysinteger
  • account.activeWebhookEndpointsinteger
  • account.pendingExportsinteger
curl 'https://polywhaler.com/api/v2/usage' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": {
    "tier": "quant",
    "quota": {
      "dailyLimit": 0,
      "usedToday": 0,
      "remaining": 0,
      "resetsAt": "2026-10-09T14:03:27.000Z"
    },
    "rateLimit": {
      "perMinute": 0,
      "usedThisMinute": 0,
      "resetsAt": "2026-10-09T14:03:27.000Z"
    },
    "key": {
      "id": "0x6f1c3d2b8a9e4f5061728394a5b6c7d8e9f0011223344556677889aabbccddee",
      "name": "sample-whale",
      "prefix": "string",
      "scopes": [
        "read"
      ],
      "createdAt": "2026-10-09T14:03:27.000Z",
      "lastUsedAt": "2026-10-09T14:03:27.000Z",
      "expiresAt": "2026-10-09T14:03:27.000Z"
    },
    "account": {
      "activeKeys": 0,
      "activeWebhookEndpoints": 0,
      "pendingExports": 0
    }
  }
}

List your recent API requests#

GET/v2/usage/requests
scope: readCursor paginated

Your account's API requests (v1 and v2, every key) over the last 14 days, newest first: route template, status, error code and latency. Does not count against the daily quota.

Query parameters

  • keyIdstring

    Only requests made with this key (one of yours).

    at most 64 characters

  • statusenum

    Only responses in this class.

    One of4292xx4xx5xx

  • routestring

    Only this route template, e.g. /v2/wallets/{address}.

    at most 200 characters · pattern ^\/v[12](\/[A-Za-z0-9.{}_-]+)*$

  • apiVersionenum

    Only requests to this API version: v1 or v2 (as apiVersion in each entry).

  • fromstring (date-time)

    Requests at or after this instant.

  • tostring (date-time)

    Requests before this instant.

  • limitintegerdefault 50

    Page size, 1 to 200.

    1..200

  • cursorstring

    Opaque cursor from pagination.nextCursor of the previous page.

    at most 1024 characters

Response

data is an array of
  • requestIdstring

    The X-Request-Id the response carried.

  • atstring (date-time)

    An instant, ISO-8601 in UTC with a Z suffix.

  • apiVersionenum

    One ofv1v2

  • methodstring
  • routestring

    The route template, e.g. /v2/wallets/{address}.

  • statusinteger
  • errorCodestring | null
  • durationMsinteger
  • responseBytesinteger | null
  • keyIdstring
  • userAgentstring | null
  • queryobject | null

    Query parameters, with anything secret-looking removed.

curl 'https://polywhaler.com/api/v2/usage/requests?limit=5' \
  -H "Authorization: Bearer $POLYWHALER_API_KEY"
Example response
{
  "data": [
    {
      "requestId": "string",
      "at": "2026-10-09T14:03:27.000Z",
      "apiVersion": "v1",
      "method": "string",
      "route": "string",
      "status": 0,
      "errorCode": "string",
      "durationMs": 0,
      "responseBytes": 0,
      "keyId": "string",
      "userAgent": "string",
      "query": {}
    }
  ],
  "pagination": {
    "limit": 5,
    "hasMore": true,
    "nextCursor": "eyJ0IjoxNzkxNDE4MDIwfQ.c2lnbmF0dXJl"
  }
}