API
Every alert on the live feed is available over HTTP. No key required at this stage — the endpoints below are public and read-only. Authentication, subscriptions and webhook delivery arrive with the notification phase.
Endpoints
Recent qualifying trades, newest first. Add refresh=1 to run a poll cycle inline before responding. The response meta carries total_archived — how many trades the archive behind /trades/history currently holds — so a client can tell whether older trades exist without spending a request to find out.
Paginated archive, newest first. Unlike /trades/latest, which serves a rolling buffer of recent qualifying trades, this reads the archive — every trade detected within the retention window (default 30 days), including ones the public feed filters out. The response meta carries next_cursor (null when exhausted), has_more and total_archived. Pass the cursor back verbatim; it is stable against new trades arriving mid-scroll.
The currently tracked traders, in leaderboard rank order.
One trader. stats holds settled performance counted from Polymarket’s closed-position records — realizedPnl, positionsClosed, wins, losses, breakeven, winRate, avgWin, avgLoss, profitFactor, biggestWin/biggestLoss and a byGame breakdown. observed is a separate, smaller sample derived from trades this deployment has seen — do not conflate the two. winRate excludes breakeven positions from its denominator and is null when nothing has settled; profitFactor is null rather than infinite when there are no losses. truncated is true when the trader has more history than was fetched.
Everything a client needs to run its own feed: tracked wallets, the esports condition-ID index, URL templates and default filters.
Public service status: health, data freshness, measured alert latency per ingest source, and recent activity. Operator internals (storage backend, deploy target, poll plumbing) are omitted unless ADMIN_TOKEN is supplied via ?token= or the X-Admin-Token header, in which case a diagnostics object is included.
Response envelope
{ "ok": true, "data": <payload>, "meta": { "generated_at": "..." } }Errors use the same envelope with { "ok": false, "error": { "code", "message" } }.
Event schema
One normalized shape, independent of both Polymarket’s wire format and any notification provider. It is what the webhook body will carry unchanged.
{
"event_id": "9f2c1ab4e7d0435c8a61f0d3c72b19ee",
"event_type": "new_trade",
"timestamp": "2026-08-22T06:32:13.000Z",
"trader": {
"username": "BOOMBOYS.Kiritych",
"wallet": "0xcd30f4698c6f5f3829893e68e183a8e5ea18f316",
"rank": 1,
"monthly_profit": 1525246.07,
"profile_url": "https://polymarket.com/profile/0xcd30…f316",
"profile_image": null
},
"market": {
"id": "0x72f93cc5347291e42699dc9db76a38e184a00a088b368fe0a71148148d0260b4",
"title": "Dota 2: Nigma Galaxy vs BoomBoys - Game 2 Winner",
"slug": "dota2-ngx-boombo-2026-08-22-game2",
"event_slug": "dota2-ngx-boombo-2026-08-22",
"category": "esports",
"game": "dota2",
"game_label": "Dota 2",
"game_short": "DOTA 2",
"game_source": "title",
"url": "https://polymarket.com/event/dota2-ngx-boombo-2026-08-22/dota2-ngx-boombo-2026-08-22-game2",
"icon": null
},
"trade": {
"side": "BUY",
"outcome": "BoomBoys",
"outcome_index": 1,
"price": 0.4767867447,
"size_usd": 1131.08548,
"shares": 2311.83,
"transaction_hash": "0x93010113387bdefb7b7c162b0c2eae2f05013b267db149af3acd95019ae83816",
"asset": "42974820668140481674546125062447708867633239207219559804172577672111834920324"
},
"timing": {
"source_timestamp": "2026-08-22T06:32:13.000Z",
"detected_timestamp": "2026-08-22T06:32:14.180Z",
"detection_latency_ms": 1180,
"ingest_source": "stream"
}
}Field notes worth reading
outcomeis the real outcome label. Esports markets use team names, not YES/NO — do not assume a binary vocabulary.priceis always the canonical probability in [0,1], whatever odds format the site is displaying. The format selector in the header is a display preference only — it never changes a stored event or an API payload. Convert client-side: decimal is1 / price, American is-100·p/(1-p)forp ≥ 0.5and100·(1-p)/pbelow it, fractional is(1-p)/p.- Those conversions are exact, not approximations of someone’s line. A Polymarket share settles at $1, so the price is the implied probability — unlike a sportsbook price, which carries margin and whose implied probabilities across a market sum to more than 100%.
size_usdcomes from Polymarket’susdcSizewhen available and falls back toshares × priceotherwise.categoryisesportsonly when the market was found in the esports index; otherwiseunknown. It is never guessed.gameis derived, not supplied by Polymarket — its per-game tags are not attached to match markets, so the game is parsed from the title and slug.game_sourcetells you how:titleandslugmean Polymarket named the game outright,leaguemeans it was inferred from a tournament name (e.g. “IEM Cologne” → Counter-Strike), andnonemeans unclassified — in which casegameis"other". Never treatleagueas authoritative.- The public feed excludes markets that are not identifiably esports — a market qualifies if the classifier names a game or its condition ID is under Polymarket’s esports tag.
/trades/historyis unfiltered and still returns them. A market withgame: "other"that is in the tag index carriesgame_label: "Esports"— confirmed esports, unidentified title. detection_latency_msisnullwhen it cannot be measured reliably, rather than being estimated.event_idis derived from trade content, so the same trade seen via the stream and via a poll produces the same ID. Safe to use as an idempotency key.
Examples
curl
curl -s https://YOUR-SITE.netlify.app/api/v1/trades/latest?limit=10 | jq
JavaScript
const res = await fetch('https://YOUR-SITE.netlify.app/api/v1/trades/latest?limit=25');
const { data } = await res.json();
for (const event of data) {
console.log(
event.trader.username,
event.trade.side,
event.trade.outcome,
`$${event.trade.size_usd.toFixed(0)}`,
event.market.title,
);
}Python
import requests
r = requests.get("https://YOUR-SITE.netlify.app/api/v1/trades/latest", params={"limit": 25})
for event in r.json()["data"]:
print(
event["trader"]["username"],
event["trade"]["side"],
event["trade"]["outcome"],
f'${event["trade"]["size_usd"]:.0f}',
event["market"]["title"],
)Rate limits
None enforced on this deployment yet. Upstream, Polymarket’s data API allows 1,000 requests per 10 seconds; this service polls far below that. Please be reasonable until keys and quotas land.
What this API does not claim
- Executed trades only. Unfilled orders are visible only to the trader who placed them — no public API exposes them.
- BUY is not the same as “opened a position”. Distinguishing open from close requires position-state tracking, which is not implemented, so events are labelled by side rather than intent.
- Win rate counts positions, not dollars. It is counted from settled position records, not estimated — but a trader can win well under half their positions and still be far ahead if the winners are larger, which is common here. It is served beside P&L, never instead of it.
- The game is parsed, not published. Markets whose game cannot be identified are
other— never assigned to the most likely title.