Polydata API v3
A fast analytics API for Polymarket — traders, leaderboards, markets, events, weather, whales, oracle accuracy and more. Everything is served straight from our database; no third-party API ever sits on the request path.
Introduction
The API is organized into 13 verticals. Every endpoint returns JSON and carries the same tariff-metadata envelope, so you always know your plan, whether the result was trimmed, and what to upgrade for more.
All you need to make your first call: a base URL, an API key, and the
X-API-Key header. Jump straight to the
quickstart if you like.
Base URL
https://dev-api.polydata.pro/api/v3
All paths below are relative to this base. HTTPS only.
Authentication
Every request (except the public health/status probes) must send your API
key in the X-API-Key header.
X-API-Key: pk_live_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
- Your key is delivered directly and once. We store only its SHA-256 hash — we cannot recover or re-send the raw key. Treat it like a password.
- Missing / invalid / revoked key → HTTP 401.
- Public probes that need no key:
GET /health,GET /status,GET /status/public.
Plans & rate limits
Beta cohort default tier: explorer — 300 requests / 60 s (≈5 req/s).
| Tier | Requests / 60 s | History cap | Compare wallets | Export |
|---|---|---|---|---|
| free | 30 | 30 d | 1 | no |
| explorer (you) | 300 | 180 d | 2 | no |
| trader | 1 000 | 365 d | 3 | yes |
| alpha | very high | all | 10 | yes |
Retry-After header (seconds until the next window). Back off and
retry after that delay. Need more during the beta? Ask us to bump your key to
trader — no new key required.
Response envelope
Every response carries tariff metadata alongside the data. When you hit a
plan cap the API does not hard-fail — it returns
HTTP 200 with the result trimmed and
truncated: true.
| Field | Meaning |
|---|---|
plan | Your effective tier for this call. |
truncated | true if the result was trimmed by a plan cap. |
lock_reason | e.g. "plan_limit" when a feature is locked, else null. |
upgrade_hint | Tier that would unlock more, or null. |
available_sections / locked_sections | What this tier can / cannot see. |
max_history_days | History window cap (null = no cap / all history). |
max_rows | Row cap for the current call. |
Quickstart
# Health (no key needed) curl https://dev-api.polydata.pro/api/v3/health # Trader stats (with your key) curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/trader/0x015458216dd5521addbe6f9e87a8a0e841ab5b34/stats" # Top traders, 30-day window curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/leaderboard/traders?period=30d"
import requests BASE = "https://dev-api.polydata.pro/api/v3" HEADERS = {"X-API-Key": "pk_live_xxxxxxxx"} # Health (no key needed) requests.get(f"{BASE}/health").json() # Trader stats (with your key) addr = "0x015458216dd5521addbe6f9e87a8a0e841ab5b34" r = requests.get( f"{BASE}/trader/{addr}/stats", headers=HEADERS, timeout=15, ) r.raise_for_status() print(r.json()) # Top traders, 30-day window r = requests.get( f"{BASE}/leaderboard/traders", headers=HEADERS, params={"period": "30d"}, timeout=15, ) print(r.json())
const BASE = "https://dev-api.polydata.pro/api/v3"; const headers = { "X-API-Key": "pk_live_xxxxxxxx" }; // Health (no key needed) await fetch(`${BASE}/health`).then(r => r.json()); // Trader stats (with your key) const addr = "0x015458216dd5521addbe6f9e87a8a0e841ab5b34"; const stats = await fetch(`${BASE}/trader/${addr}/stats`, { headers }) .then(r => r.json()); console.log(stats); // Top traders, 30-day window const top = await fetch(`${BASE}/leaderboard/traders?period=30d`, { headers }) .then(r => r.json()); console.log(top);
A 200 with a JSON body means you're in.
A 401 means the key header is missing or wrong;
a 429 means you hit the rate limit (honor Retry-After).
Health & status
public · no key| GET | /health | Liveness + database reachability. |
| GET | /status | Same payload (legacy alias). |
| GET | /status/public | Per-subsystem freshness grades for the public status page. |
# Public — no key required curl "https://dev-api.polydata.pro/api/v3/health"
import requests B = "https://dev-api.polydata.pro/api/v3" # Public — no key required print(requests.get(f"{B}/health").json())
const B = "https://dev-api.polydata.pro/api/v3"; // Public — no key required const r = await fetch(`${B}/health`).then(r => r.json()); console.log(r);
Trader
{address} = lowercase 0x…| GET | /trader/{address}/stats | Lifetime summary: trades, volume, active days, unique markets, username. |
| GET | /trader/{address}/activity/daily?limit=90 | Per-day time series (max 365). |
| GET | /trader/{address}/markets?limit=50 | Top markets by volume (max 200). |
| GET | /trader/{address}/trades?limit=50 | Recent fills, keyset pagination (max 200). |
| GET | /trader/compare?address=0x..&address=0x.. | Side-by-side compare (explorer: 2 wallets). |
| POST | /trader/{address}/export-jobs | Queue a snapshot export (trader/alpha only). |
| GET | /trader/export-jobs/{job_id} | Export job status. |
| GET | /trader/export-jobs/{job_id}/download | Download finished export. |
# Lifetime stats for one wallet curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/trader/0x015458216dd5521addbe6f9e87a8a0e841ab5b34/stats"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} addr = "0x015458216dd5521addbe6f9e87a8a0e841ab5b34" r = requests.get(f"{B}/trader/{addr}/stats", headers=H, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const addr = "0x015458216dd5521addbe6f9e87a8a0e841ab5b34"; const r = await fetch(`${B}/trader/${addr}/stats`, { headers: H }).then(r => r.json()); console.log(r);
Leaderboard
period = 1d · 7d · 30d| GET | /leaderboard/traders?period=30d | Top traders ranked by total PnL (real_pnl, realized + unrealized). See disclaimer. |
| GET | /leaderboard/markets?period=30d | Top markets by volume. |
# Top traders by realized PnL (30-day window) curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/leaderboard/traders?period=30d"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/leaderboard/traders", headers=H, params={"period": "30d"}, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const r = await fetch(`${B}/leaderboard/traders?period=30d`, { headers: H }).then(r => r.json()); console.log(r);
Market
{condition_id} = String PK| GET | /market/{condition_id}/overview | Metadata + all-time aggregates + resolution + recent fills. |
| GET | /markets/search?q=... | Search markets by title (explorer: up to 50 rows). |
# Search markets by title curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/markets/search?q=bitcoin"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/markets/search", headers=H, params={"q": "bitcoin"}, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const r = await fetch(`${B}/markets/search?q=bitcoin`, { headers: H }).then(r => r.json()); console.log(r);
Event
| GET | /event/{event_id} | Event metadata + aggregate rollup. |
| GET | /event/{event_id}/markets?limit=&offset= | Markets belonging to the event. |
| GET | /event/{event_id}/traders?limit=&offset= | Top traders on the event. |
# Event metadata + aggregate rollup curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/event/12345"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} event_id = "12345" r = requests.get(f"{B}/event/{event_id}", headers=H, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const eventId = "12345"; const r = await fetch(`${B}/event/${eventId}`, { headers: H }).then(r => r.json()); console.log(r);
Platform stats
cache-first| GET | /stats | Platform overview: volume, active markets, traders. |
| GET | /stats/hourly | Last-24h hourly activity. |
| GET | /stats/daily?days= | Per-day platform rollup. |
| GET | /stats/growth | Growth / trend metrics. |
# Platform overview (volume, active markets, traders) curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/stats"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/stats", headers=H, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const r = await fetch(`${B}/stats`, { headers: H }).then(r => r.json()); console.log(r);
PMX Index
open on all tiers| GET | /pmx-index | Current snapshot: composite + 5 sub-indices. |
| GET | /pmx-index/{name}/history?interval=1h|6h|1d | History of one sub-index. name ∈ {PMX-Volume, PMX-Whale, PMX-Politics, PMX-Crypto, PMX-Sentiment}. |
# Current composite + 5 sub-indices curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/pmx-index"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/pmx-index", headers=H, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const r = await fetch(`${B}/pmx-index`, { headers: H }).then(r => r.json()); console.log(r);
Oracle (PolyOracle)
Brier score · calibration| GET | /oracle/leaderboard | Top traders by forecasting accuracy. |
| GET | /oracle/trader/{address} | Accuracy breakdown for one wallet. |
# Top forecasters by accuracy (Brier score) curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/oracle/leaderboard"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/oracle/leaderboard", headers=H, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const r = await fetch(`${B}/oracle/leaderboard`, { headers: H }).then(r => r.json()); console.log(r);
Whales
| GET | /whales | Top wallets by size / activity (each row carries total real_pnl; see disclaimer). |
| GET | /whales/moves | Recent large trades. |
| GET | /whales/flow?market_id= | Net whale flow (optionally per market). |
# Recent large trades curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/whales/moves"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/whales/moves", headers=H, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const r = await fetch(`${B}/whales/moves`, { headers: H }).then(r => r.json()); console.log(r);
Screener
| GET | /screener | Advanced market filtering. |
| GET | /screener/summary | Aggregate screener summary. |
# Advanced market filtering curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/screener"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/screener", headers=H, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const r = await fetch(`${B}/screener`, { headers: H }).then(r => r.json()); console.log(r);
Research & export
export → trader/alpha| GET | /research/datasets | Catalog of exportable datasets. |
| POST | /research/export-jobs | Queue a CSV/Parquet dataset export. |
| GET | /research/export-jobs/{job_id} | Export job status. |
| GET | /research/export-jobs/{job_id}/download | Download finished export. |
# Catalog of exportable datasets curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/research/datasets"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/research/datasets", headers=H, timeout=15) r.raise_for_status() print(r.json())
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const r = await fetch(`${B}/research/datasets`, { headers: H }).then(r => r.json()); console.log(r);
Weather
9 endpoints| GET | /weather/cities | City grid + live events, forecasts, and volume rollups. Returns top-level volume_24h_total (vertical daily headline) plus per-city lifetime / 24h / 7d fields. |
| GET | /weather/city/{city} | City card: forecasts, actuals, anomaly, and the same volume fields as one row in /weather/cities. |
| GET | /weather/city/{city}/forecast | Multi-source TMAX/TMIN forecast time series. |
| GET | /weather/city/{city}/actuals | Observed daily highs/lows. |
| GET | /weather/city/{city}/anomalies | Deviation vs climate normals. |
| GET | /weather/city/{city}/markets | Active temperature brackets + per-market trade stats (volume there is lifetime on the bracket). |
| GET | /weather/traders | Top weather-market traders. |
| GET | /weather/forecast-evolution/{city}/{date} | How a forecast for a date evolved over time. |
| GET | /weather/bot-bundle | Compact multi-city snapshot for bots. |
/weather/cities and /weather/city/{city},
volume and volume_lifetime are cumulative
USD all-time for that city's temperature markets — do
not use them for “traded today” headlines.
Use volume_24h (UTC calendar day so far) or the vertical sum
volume_24h_total on /weather/cities.
volume_7d covers the last 7 UTC calendar days incl. today.
volume_as_of is the UTC timestamp when rollups were computed.
Key response fields on GET /weather/cities:
| Field | Scope | Meaning |
|---|---|---|
volume_24h_total | response | Sum of all cities' volume_24h — use for daily weather vertical headlines. |
volume_lifetime_total | response | Sum of all cities' lifetime volume (≈ historical cumulative; stable day-to-day). |
volume_7d_total | response | Sum of all cities' 7-day rolling volume. |
volume_24h | each city | USD traded today (UTC calendar day). |
volume_7d | each city | USD over last 7 UTC calendar days. |
volume / volume_lifetime | each city | Same value — cumulative all-time USD (backward-compatible alias). |
open_count / market_count | each city | Active brackets / events from live snapshot; falls back to rollup when live sync is empty. |
# City grid — use volume_24h_total for "traded today", not sum(volume) curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/weather/cities" # Single city — volume_24h vs volume_lifetime curl -H "X-API-Key: pk_live_xxxxxxxx" \ "https://dev-api.polydata.pro/api/v3/weather/city/nyc"
import requests B = "https://dev-api.polydata.pro/api/v3" H = {"X-API-Key": "pk_live_xxxxxxxx"} r = requests.get(f"{B}/weather/cities", headers=H, timeout=15) r.raise_for_status() data = r.json() print("24h vertical", data["volume_24h_total"]) print("lifetime vertical", data["volume_lifetime_total"]) nyc = next(c for c in data["cities"] if c["city"] == "nyc") print("nyc 24h", nyc["volume_24h"], "lifetime", nyc["volume_lifetime"])
const B = "https://dev-api.polydata.pro/api/v3"; const H = { "X-API-Key": "pk_live_xxxxxxxx" }; const data = await fetch(`${B}/weather/cities`, { headers: H }).then(r => r.json()); console.log("24h vertical", data.volume_24h_total); console.log("lifetime vertical", data.volume_lifetime_total); const nyc = data.cities.find(c => c.city === "nyc"); console.log("nyc 24h", nyc.volume_24h, "lifetime", nyc.volume_lifetime);
What real_pnl means
The real_pnl value on /trader/*, /whales
and the rank on /leaderboard/traders is realized PnL,
matching the figure shown on a trader's Polymarket profile ("Profit") and on
Polymarket's leaderboard API (responses report
pnl_method = "cash_basis_v3"):
Open positions are excluded — exactly like the Polymarket
headline number. Polymarket shows the current value of your open positions as a
separate figure, not inside "Profit". (An earlier version of this API
added open-position mark-to-market into real_pnl; that made active
traders read higher than the site, so it was removed.)
Known limitations:
- Open-position value is not yet exposed. We omit a separate open/unrealized field for now rather than publish an inaccurate one (our last-trade marks underprice open books vs Polymarket).
- Residual overcount on a few high-volume market-makers. Winning tokens that leave a wallet via peer-to-peer ERC-1155 transfers or umbrella (NegRisk) redemptions have no matching sell trade, so a small set of MM/arbitrage wallets can read a few percent above the site.
- Resolutions we don't yet have a winner record for. A class of short-term markets (minute-scale crypto Up/Down, dated sports) is archived upstream faster than our sync captures the resolution; those positions read low until the resolution lands.
/trader/{address}/stats also returns
realized_pnl (== real_pnl),
roi (real_pnl / buy_volume_usd, approximate),
win_rate (the share of your resolved markets
that closed in profit; null when you have no resolved markets)
and pnl_method. unrealized_pnl (open-position value)
is reserved and always null for now.
Status page
Live system health (no login):
https://dev-api.polydata.pro/api/v3/status/public # raw JSON https://polydata.pro/status # human-readable page
If you see elevated errors, check the status page before contacting support — it shows per-subsystem freshness (serving tables, trades, markets).
Support & beta terms
Pre-GA. Endpoints, response shapes, and limits may change with notice. Export jobs are best-effort. No uptime SLA during the beta.
You're in the beta because your feedback shapes GA. Please report any number that looks wrong (send the wallet/market + what you expected), latency spikes, 5xx errors, or endpoints/fields you wish existed.
Contact: support@polydata.pro (or your direct beta channel).