OrcaLayer API · v1

Public REST API

Programmatic access to Polymarket whale analytics, ISW territory data, and leaderboards. Available on Premium ($19.99/mo).

Quickstart

  1. Sign up and upgrade to Premium
  2. Go to Settings → API Keys and click Generate Key
  3. Copy the key (format sk_orca_XXXXXX) — it's shown only once
  4. Make authenticated requests:
curl
curl -H "Authorization: Bearer sk_orca_XXXXXX" \
  "https://orcalayer.com/api/public/v1/whales/leaderboard?limit=10"

Authentication

Send your API key as a Bearer token in the Authorization header. Alternative: x-api-key header.

authorization header
Authorization: Bearer sk_orca_XXXXXX
  • Missing key401 Unauthorized
  • Revoked / invalid key401
  • Valid key but plan ≠ premium403 Forbidden
  • Rate limit exceeded429 Too Many Requests with Retry-After: 60

Wins / Losses / Win Rate / Profit Factor — data semantics

As of 6.05.2026 (Phase B5 take 2), wins, losses, win_rate, and profit_factor are computed from Polymarket data-api.polymarket.com/closed-positions data when our cache is complete (≥95% of expected closed positions, measured via Polymarket data-api.polymarket.com/traded API).

  • Cache complete → matches Polymarket UI counters (most wallets).
  • Cache truncated (top whales with 1M+ trade events) → falls back to our v5 markets-resolved metric (1 unit per market, NegRisk multi-market events count once). v5 ≈ Prediction Folio for high-volume wallets.
  • profit_factor uses cache PnL ratio (looser guard — ratios stable even when cache truncated). Capped at 99.99x; real may be higher with rare losses.
  • total_pnl always uses Polymarket lb_api lifetime PnL when synced (authoritative source).

Contract addresses (not user wallets)

Polymarket router/exchange contract addresses are excluded from wallet endpoints (they aggregate ALL trades through the router, not a single user). Querying these returns an explicit error response shape rather than fake whale stats:

response shape for contract addresses
{
  "error": "This address is a Polymarket smart contract, not a user wallet",
  "is_contract": true,
  "contract_type": "NegRisk CTF V2 Exchange",   // or CTF Exchange V1/V2, NegRisk CTF V1
  "address": "0xe2222d279d744050d28e00520010520000310f59"
}

Currently 4 addresses excluded: 0x4bfb41d5... (CTF Exchange V1), 0xc5d563a3... (NegRisk CTF V1), 0xe1111800... (CTF Exchange V2), 0xe2222d27... (NegRisk CTF V2).

Rate limits

Each Premium API key is limited to 300 requests/minute (sliding window 60s). Anonymous (no-key) public endpoints: 200/min per IP; wallet overview/positions endpoints are additionally capped at 300 requests/day per IP for anonymous external clients (Premium keys have no daily cap; normal browsing on this site is unaffected). Custom enterprise tier available on request. Usage headers on every response:

response headers
X-RateLimit-Limit: 300
X-RateLimit-Remaining: 287

Batch endpoints: POST /api/public/v1/wallets/overview (max 50 wallets) and POST /api/public/v1/wallets/positions (max 20, heavier payloads) with body {"wallets": ["0x...", ...]} return per-wallet statuses (ok / computing / not_found) in one call. Batch requests are read-only and idempotent — identical requests return the same data and never mutate state, so retrying after a timeout or network error is always safe. One batch consumes N (= wallet count) of the per-key budget; the X-RateLimit-Consumed header reports it. Wallets in computing status are queued automatically and typically resolve within minutes; for an immediate result fetch the single-wallet overview endpoint.

Need higher limits? Email [email protected] with your use case.

Endpoints

GEThttps://orcalayer.com/api/public/v1/whales/leaderboard?limit=10&sort=pnl&period=all

Top whales ranked by P&L / Win Rate / Profit Factor. Params: limit (1-100), sort (pnl|wr|pf|volume), period (24h|7d|30d|all), min_profit_factor.

example response (truncated)
{
  "whales": [
    {
      "wallet": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
      "name": "Theo4",
      "total_pnl": 22053933.75,        // overlay-aware: lb_api truth when synced
      "win_rate": 81.8,                 // Phase B5 take 2: cache breakdown when complete
      "profit_factor": 99.99,           // capped at 99.99 (real ratio may be higher)
      "resolved_markets": 22,
      "wins": 18,
      "losses": 4,
      "total_volume": 52213734.76,
      "total_trades": 39067,
      "main_category": "POLITICS",
      "avg_entry_price": 0.5523,
      "last_trade_ts": 1731476951,
      "active_count": 0,
      "market_win_rate": 85.7,          // per-market basis (1 unit per market)
      "market_wins": 12,
      "market_losses": 2,
      "mint_volume_30d": 0.0,
      "merge_volume_30d": 0.0
    }
  ],
  "total": 193696,
  "sort": "pnl",
  "offset": 0
}
GEThttps://orcalayer.com/api/public/v1/wallet/{address}/overview

Full profile + aggregate stats for any Polygon wallet (0x… or Polymarket username). Primary stats are served from a per-wallet cache (see as_of field, refreshed within ~5 min of trading activity). On a cold cache for a very heavy wallet the API may return HTTP 202 + Retry-After: 30 — retry once and the cache will be warm. Under heavy load, secondary counters (active_count, closed_count, W/L breakdowns) may be omitted (null) with top-level degraded: true — primary stats are always served. view=union and view=signer are computed live and may be slower.

example response (truncated)
{
  "profile": {
    "address": "0x56687bf447db6ffa42ffe2204a05edaa20f55839",
    "name": "Theo4",
    "pseudonym": "Ironclad-Tenement",
    "proxy_wallet": "0x56687bf447db6ffa42ffe2204a05edaa20f55839"
  },
  "overview": {
    "total_trades": 39067,
    "total_markets": 14,
    "total_volume": 52213734.76,
    "last_trade": 1731476951,
    "profit_factor": 99.99,             // overlay-aware (cache PnL ratio when complete)
    "active_count": 0,                  // currently held positions
    "closed_count": 22,                 // matches stats.wins + stats.losses
    "median_hold_days": 21.5,           // typical days a position is held (last 90d window)
    "hold_positions_count": 12          // sample size that produced median (3+ required)
  },
  "stats": {
    "resolved": 22,
    "wins": 18,                         // Phase B5: cache breakdown when complete, sw v5 fallback
    "losses": 4,
    "win_rate": 81.8,
    "total_pnl": 22053933.75,           // lb_api truth (Polymarket /profit endpoint)
    "profit_factor": 99.99,
    "is_smart": true,
    "profitable_streak": 1,
    "market_win_rate": 85.7,
    "market_wins": 12,
    "market_losses": 2,
    "unrealized_pnl": 0,
    "pnl_24h": null
  },
  "rankings": {
    "rank_pnl": 1,
    "rank_winrate": 48344,
    "rank_volume": 125,
    "rank_profit_factor": 7,
    "total_traders": 1298302,
    "is_smart": true
  },
  "categories": {
    "SPORTS": 0.0, "GEOPOLITICS": 0.0, "CRYPTO": 0.0,
    "POLITICS": 85.7, "ECONOMICS": 0.0, "TECH": 0.0
  },
  "as_of": 1765538538,                  // unix ts of the cached snapshot
  "degraded": false                     // true = secondary counters omitted under load
}
GEThttps://orcalayer.com/api/public/v1/wallet/{address}/positions?limit=50

Open positions for the wallet with current P&L.

example response (truncated)
{
  "positions": [
    {
      "market_id": "2099029",
      "condition_id": "0xabc...",
      "outcome": "Yes",
      "shares": 246169,
      "avg_price": 0.119,
      "current_price": 0.085,
      "cost": 29368.0,
      "unrealized_pnl": -8369.0,
      "title": "US x Iran permanent peace deal by May 15, 2026?"
    }
  ],
  "source": "db_estimate",                // or "polymarket_inline" when overlay synced
  "is_estimated": true,                   // false when overlay-fresh
  "count": 1
}
GEThttps://orcalayer.com/api/public/v1/market/{market_id}

Market details + smart whale consensus + 3 trader rankings (Smart / Top Size / Top Shares). Each ranking has list of top 10 + aggregate metrics (count, invested, avg WR, agg P&L). yes_team_shares / no_team_shares added 6.05.2026 — sorts by share count (matches Polymarket UI Top Holders order).

example response (truncated)
{
  "market": {
    "id": "2099029",
    "question": "US x Iran permanent peace deal by May 15, 2026?",
    "price_yes": 0.085,
    "price_no": 0.915,
    "category": "GEOPOLITICS",
    "volume": 65397286.0
  },
  "whales": { "yes_count": 321, "no_count": 474, "score": 0.60 },

  // Smart Money tab — only smart whales, sorted by USD invested
  "yes_team": [...], "no_team": [...],
  "yes_team_total_count": 321, "no_team_total_count": 474,
  "yes_team_total_invested": 217400, "no_team_total_invested": 1730000,
  "yes_team_avg_wr": 67.0, "yes_team_agg_pnl": -117300, "yes_team_agg_pnl_pct": -23.8,

  // Top Size tab — all traders, sorted by USD invested
  "yes_team_size": [...], "no_team_size": [...],
  "yes_team_size_total_count": 2391, "yes_team_size_avg_wr": 36.5,
  "yes_team_size_agg_pnl": -117300, ...

  // Top Shares tab — all traders, sorted by share count (NEW 6.05.2026)
  "yes_team_shares": [
    { "wallet": "0xc851...", "name": "betwick",
      "side": "YES", "shares": 246169, "avg_price": 0.119,
      "cost": 29368, "pnl": 3234, "pnl_pct": 11.0,
      "win_rate": 60.0, "global_pnl": 285000, "is_smart": false }
  ],
  "no_team_shares": [...],
  "yes_team_shares_avg_wr": 35.2, "no_team_shares_avg_wr": 47.1,
  "yes_team_shares_agg_pnl": ...,
  ...
}
GEThttps://orcalayer.com/api/public/v1/whale-flips?days=1&limit=20

Smart whales що reversed direction (sign flip) на market у last 24h. Compares today vs N-days-ago snapshot. $200K+ swing required to qualify. Historical N up to 14 days. Strong drama signal — whale changed conviction.

example response (truncated)
{
  "flips": [
    {
      "wallet": "0x...",
      "display_name": "Brokie",
      "market_id": "1808970",
      "old_side": "YES",
      "new_side": "NO",
      "old_size_usd": 500000,
      "new_size_usd": 700000,
      "swing_usd": 1200000,
      "win_rate": 75.7,
      "lifetime_pnl": 1900000
    }
  ],
  "count": 1,
  "lookback_days": 1
}
GEThttps://orcalayer.com/api/public/v1/conviction-clusters?min_whales=3&limit=10

Conviction clusters — events where 3+ smart whales aligned same side across 2+ related markets. Aggregate signal stronger than single-market position. Returns event group, side (YES/NO), whale count, market count, combined capital.

example response (truncated)
{
  "clusters": [
    {
      "event_slug": "presidential-election-winner-2028",
      "side": "NO",
      "whale_count": 13,
      "market_count": 3,
      "total_capital_usd": 5458963,
      "whales": [{"name": "Kickstand7", "wr": 63.5, "pnl": 1402041}, ...],
      "markets": [{"market_id": "123", "question": "..."}]
    }
  ],
  "count": 1
}
GEThttps://orcalayer.com/api/public/v1/isw/status

ISW Territory Monitor — all tracked cities with per-market subscriptions. Each city has 1+ subscriptions (single landmark, any-territory polygon, or capture-all polygon). Each subscription has its own threat level computed from polygon intersection with combined ISW shading (control + advance + gains_24h).

example response (truncated)
{
  "summary": {
    "total_cities": 51,
    "total_subscriptions": 62,
    "captured_subscriptions": 2,
    "threats_by_subscription": { "CRITICAL": 2, "HIGH": 8, ... }
  },
  "cities": {
    "Kupiansk-Vuzlovyi": {
      "coordinates": { "lon": 37.6439, "lat": 49.6605 },
      "worst_threat": "SAFE",
      "last_check": "2026-04-26T19:50:30",
      "subscriptions": [
        {
          "id": 5,
          "label": "Any territory",
          "check_type": "enter",
          "geometry": [[37.644, 49.673], ...],   // closed-ring polygon
          "threat": "SAFE",
          "coverage": 0.0,                       // 0..1 polygon area shaded
          "closest_distance_m": 3666,            // distance to nearest shading
          "market_slug_pattern": "will-russia-enter-kupiansk-vuzlovyi-by-*",
          "description_excerpt": "Russia captures any territory of Kupiansk-Vuzlovyi...",
          "market": { "id": "...", "question": "...", "price_yes": 1.0, "volume": 10652 }
        },
        {
          "id": 6,
          "label": "Railroad station",
          "check_type": "single",
          "geometry": [[37.644326, 49.654359]],  // single point
          "buffer_advance_m": 250,
          "threat": "SAFE",
          "point_shaded": false,
          "closest_distance_m": 5646,
          "market_slug_pattern": "will-russia-capture-kupiansk-vuzlovyi-by-*",
          "description_excerpt": "Russia captures the Kupyansjk-Vuzlovij railroad station..."
        }
      ]
    }
  }
}
GEThttps://orcalayer.com/api/public/v1/wallet/{addr}/trades

Premium: historical trades for wallet+market+time slice. Required: market (condition_id), before (unix ts). Optional: after (default 0 = from start), limit (default 100, max 500). Auth: Bearer Premium key. Performance note: omitting after on busy wallets does a full scan (~25s); for fast queries pass a narrow time window (e.g. last 7 days).

example response (truncated)
Authorization: Bearer sk_orca_XXXXXX

GET /api/v2/wallet/0xabc.../trades?market=0xcond...&before=1777000000&after=1776900000&limit=50

{
  "wallet": "0xabc...",
  "market": "0xcond...",
  "count": 12,
  "trades": [
    {
      "id": 588001234,
      "timestamp": 1776999500,
      "wallet_role": "maker",
      "direction": "BUY",
      "side": "YES",
      "price": 0.245,
      "usd_amount": 612.5,
      "token_amount": 2500,
      "entry_type": "MINT",
      "tx_hash": "0xab09...",
      "counterparty": "0xdef..."
    }
  ]
}
GEThttps://orcalayer.com/api/public/v1/market/{condition_id}/whale-trades

Premium: whale-class trades on a market (either maker or taker is in smart_whales). Required: before (unix ts). Optional: min_usd (default 500 — also echoed in response), limit (default 100, max 500). Auth: Bearer Premium key. Performance: ~10-15s on busy markets (resolves condition_id → market_id internally, 20s statement timeout protects against runaway).

example response (truncated)
Authorization: Bearer sk_orca_XXXXXX

GET /api/v2/market/0xcond.../whale-trades?before=1777000000&min_usd=1000&limit=50

{
  "market": "0xcond...",
  "count": 23,
  "trades": [
    {
      "timestamp": 1776999000,
      "maker": "0xabc...",
      "taker": "0xdef...",
      "maker_is_whale": true,
      "taker_is_whale": false,
      "price": 0.245,
      "usd_amount": 1200,
      "side": "YES",
      "entry_type": "MINT"
    }
  ]
}
GEThttps://orcalayer.com/api/public/v1/wallet/{addr}/market-summary/{condition_id}

Premium: wallet position snapshot on a market. Without 'at': current state from wallet_market_stats (per-side YES/NO rows). With 'at=<unix_ts>': reconstructs aggregate from trades up to that timestamp. Auth: Bearer Premium key.

example response (truncated)
Authorization: Bearer sk_orca_XXXXXX

GET /api/v2/wallet/0xabc.../market-summary/0xcond...?at=1776999000

{
  "wallet": "0xabc...",
  "condition_id": "0xcond...",
  "at": 1776999000,
  "found": true,
  "sides": {
    "YES": {
      "buy_tokens": 2500, "buy_cost_usd": 612.5,
      "sell_tokens": 0, "sell_cost_usd": 0,
      "net_tokens": 2500, "net_cost_usd": 612.5,
      "trade_count": 1,
      "avg_entry_price": 0.245,
      "first_trade_ts": 1776999500, "last_trade_ts": 1776999500
    }
  }
}
GEThttps://orcalayer.com/api/public/v1/isw/events

ISW threat events stream — CAPTURED, THREAT_CRITICAL, THREAT_HIGH, etc. Per-subscription. Query params: hours (default 24, max 720), limit (default 50, max 200), city, subscription_id, check_type ('single'/'enter'/'entirety'), min_severity ('SAFE'..'CRITICAL').

example response (truncated)
{
  "events": [
    {
      "id": 749,
      "city": "Kindrashivka",
      "event_type": "CAPTURED",
      "severity": "CRITICAL",
      "details": "Kindrashivka (Any territory) captured",
      "timestamp": "2026-04-26 19:49:26.631036",
      "subscription_id": 51,
      "subscription_label": "Any territory",
      "check_type": "enter",
      "market_slug_pattern": "will-russia-enter-kindrashivka-by-*",
      "coverage": 0.058,
      "closest_distance_m": 0
    }
  ],
  "count": 1
}
GEThttps://orcalayer.com/api/public/v1/isw/premium/status

Premium-tier mirror of /isw/status (Bearer auth). Same payload.

example response (truncated)
Authorization: Bearer sk_orca_XXXXXX
GEThttps://orcalayer.com/api/public/v1/isw/premium/events

Premium-tier mirror of /isw/events (Bearer auth). Same query params and payload.

example response (truncated)
Authorization: Bearer sk_orca_XXXXXX

Real-time trade stream (SSE)

Server-Sent Events stream of every Polymarket trade event. Our relay forwards events sub-second from Polymarket's on-chain confirmation. Polymarket V2 batches trade submissions on-chain every ~60–120 seconds (gas optimization) — this is the structural settlement window inherited by all on-chain data sources, including ours. Use it for copy-trading bots, live dashboards, or any consumer that would otherwise poll.

GET · SSEhttps://orcalayer.com/api/public/v1/live/trades

Streams one event: trade frame per on-chain OrderFilled event (V1 + V2). Keep the connection open; the server sends : keepalive comments every 15s when the upstream is quiet.

curl
curl -N -H "Authorization: Bearer sk_orca_XXXXXX" \
  https://orcalayer.com/api/public/v1/live/trades

Event payload (V2 — recommended)

event: trade
{
  "asset": "54709499356...",           // ERC-1155 CTF token id
  "conditionId": "0xb4766e...",
  "eventSlug": "btc-updown-5m-...",
  "proxyWallet": "0xd9013df8...",      // = maker address
  "side": "BUY",                       // maker side
  "price": 0.66,                       // 0.0 .. 1.0
  "size": 4.06,                        // shares
  "outcome": "Up",
  "pseudonym": "Dear-Colonial",
  "bio": "",                           // whale's social metadata
  "profileImage": "",
  "title": "Bitcoin Up or Down - ...",
  "timestamp": 1776759129,
  "transactionHash": "0xab09c4..."
}

Enriched fields (from our indexer)

When you query historical trade data via /wallet/{address} endpoints, each row includes these enriched fields derived from V2 OrdersMatched events:

FieldValuesMeaning
entry_typeMINT | MERGE | COMPLEMENTARY | nullMINT = primary market entry. Whale split USDC into a fresh YES+NO share pair (1 USDC → 1 YES + 1 NO) and kept one side. Strongest smart-money signal — capital deployment, not flipping inventory from another holder.
MERGE = full exit. Whale combined a YES+NO pair back into USDC (1 YES + 1 NO → 1 USDC). Pre-resolution unwind or profit-take signal.
COMPLEMENTARY = NegRisk multi-market auto-spawn. When a whale buys YES on one market in a NegRisk event group (e.g. "X wins 2028 nomination" with 12 candidates), the NegRisk adapter contract auto-creates offsetting NO positions on sister markets. These rows are the auto-spawned legs, routed through adapter contracts0xe111180000d2663c0091e4f400237545b87b996b and0xe2222d279d744050d28e00520010520000310f59. Useful signal for "this whale is committing across the whole event group" — not one-market noise.
null = V1 legacy trade (pre-cutover 28.04.2026), not classified.
fee_usdnumberExchange fee in USDC. Subtract from usd_amount for exact net P&L.

Limits

  • Max 5 concurrent SSE connections per API key
  • Each SSE connection counts as 1 against the 600 req/min limit (not per event)
  • If upstream drops, the server auto-reconnects up to 5× with exponential backoff before sending event: error

Examples

python (filter MINT trades only)
import json, requests

KEY = "sk_orca_XXXXXX"
with requests.get(
    "https://orcalayer.com/api/public/v1/live/trades",
    headers={"Authorization": f"Bearer {KEY}"},
    stream=True,
    timeout=None,
) as r:
    for line in r.iter_lines(decode_unicode=True):
        if not line or not line.startswith("data:"):
            continue
        trade = json.loads(line[5:].strip())
        # entry_type is enriched on historical rows; live SSE delivers the raw event.
        # Large-wallet MINTs are the strongest smart-money signals:
        usd = trade["size"] * trade["price"]
        if usd > 10_000:
            print(f"🐋 {trade['proxyWallet'][:10]}.. {trade['side']} {trade['outcome']} "
                  f"USD {usd:.0f} @ {trade['price']}")
javascript (browser EventSource)
// EventSource does NOT support custom headers — pass the key as query param
// via a thin proxy, OR use fetch() with ReadableStream:
const resp = await fetch("https://orcalayer.com/api/public/v1/live/trades", {
  headers: { Authorization: "Bearer sk_orca_XXXXXX" }
});
const reader = resp.body.getReader();
const decoder = new TextDecoder();
let buf = "";
while (true) {
  const { value, done } = await reader.read();
  if (done) break;
  buf += decoder.decode(value, { stream: true });
  const events = buf.split("

");
  buf = events.pop() || "";
  for (const e of events) {
    const data = e.split("
").find(l => l.startsWith("data:"));
    if (data) console.log(JSON.parse(data.slice(5)));
  }
}

Code examples

javascript (node / browser)
const resp = await fetch("https://orcalayer.com/api/public/v1/whales/leaderboard?limit=5", {
  headers: { Authorization: "Bearer " + process.env.ORCA_KEY }
});
const data = await resp.json();
console.log(data.whales);
python (requests)
import os, requests

headers = {"Authorization": f"Bearer {os.environ['ORCA_KEY']}"}
r = requests.get("https://orcalayer.com/api/public/v1/whales/leaderboard", params={"limit": 5}, headers=headers)
r.raise_for_status()
for w in r.json()["whales"]:
    print(w["name"], w["total_pnl"])
python (polling whale consensus on a market)
import time, requests
headers = {"Authorization": "Bearer sk_orca_XXXXXX"}
while True:
    r = requests.get("https://orcalayer.com/api/public/v1/market/1919417", headers=headers).json()
    score = r["consensus"]["score"]
    print(f"whale_score={score:.2f}")
    time.sleep(60)  # once per minute, well under 1000/min limit

Error responses

StatusMeaning
401Missing, invalid, or revoked API key
403Key valid but user's plan is not Premium
404Endpoint not in public whitelist, or resource not found
429Rate limit exceeded — wait Retry-After seconds
502Upstream data service temporarily unavailable

Ready to build?

Premium unlocks the full API, WebSocket feed, and priority support.

Upgrade to Premium — $19.99/mo

Questions? [email protected]