Polymarket API docs
Base URL https://data.vike.io/v1, one header, 16 routes. Every endpoint below with its parameters and a call you can copy in cURL, Python, the CLI or MCP.
“Find the 5-minute BTC market trading now and show its order book”
polymarket_markets polymarket_orderbook“How did this market resolve, and who held the winning side? (paste a link)”
polymarket_settlement polymarket_market_trades- Sign in at vike.io
Any account. No card, no plan to pick.
- Create a key
On
/account?tab=api-keys. Up to 3 active keys per account; every key startsvk_. - Send the header
X-API-KEY: vk_…on every request. Or skip the code:vike-data initin the CLI, or connect MCP.
Find a market, then its tokens
Every market carries its condition_id and the token_ids of its outcomes: the two ids every other route takes.
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/markets?query=btc-updown-5m&active=true&limit=5"
import os, requests
markets = requests.get(
"https://data.vike.io/v1/polymarket/markets?query=btc-updown-5m&active=true&limit=5",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
).json()
for m in markets:
print(m["slug"], m["condition_id"], m["token_ids"])
vike-data pm markets --view search --query btc-updown-5m --active true --limit 5
- condition_id (
0x+ 64 hex) names a market; the market routes and settlements take it. - token_id (a long decimal) names one outcome;
/orderbook,/events,/ohlcvand/market_statstake it. - Detail routes answer a bare array: an unknown id is
200 [], not a 404. - When the request alone explains an empty answer, the reason is in the
X-Vike-Noteheader.
Free, with one ceiling per key
| Requests | 20,000 per hour, per key, a rolling hour |
|---|---|
| Per minute | 200 per minute, per key, on /v1 |
| Keys | Up to 3 active per account; each key is its own budget |
| Cost of a call | 1 per REST request; MCP tools 1 or 2 |
| Archive downloads | Not counted against the hourly limit; samples and manifest.json need no key |
| Price | $0, nothing is billed |
Request builder: all 16 routes
Pick a route; the parameters come filled with real ids. The call is rebuilt as you type, and a captured route shows its real answer.
All 16 routes
Click a route to jump to its parameters and a call you can copy.
Markets and settlements
Find a market and its outcome tokens, its trades with the wallet behind each, and how the chain resolved it.
List Polymarket markets
Search/filter recorded markets. Returns a BARE ARRAY, newest end_date first; page with limit and offset.
- Route
GET /v1/polymarket/markets- CLI
vike-data pm markets- MCP
polymarket_markets- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
query | query | Case-insensitive substring match on the market slug. |
active | query | true = only active, false = only inactive, omit = all. |
closed | query | The complement of active: true (or 1) = only closed. |
limit | query | Max rows. Default 50, hard cap 100 — a larger value is silently clamped, not rejected. |
offset | query | Rows to skip, an integer from 0 to 10000. Default 0. Page with limit and offset; with an offset, rows with the same end_date order by condition_id, so pages do not repeat or skip a market. Above 10000 is a 400. |
activeandclosedare derived rather than stored: a market is closed when its on-chain resolution exists or Polymarket's ownclosedflag says so.?active=falsetherefore returns the closed markets, and a settled 5-minute market reports itself settled.- ⚠
?active=is also the SLOW path — ~1.00 s against ~46 ms for an unfiltered page, because a filter on a derived flag has to derive it for every market before it can limit.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/markets?query=btc-updown-5m&active=true&limit=5"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/markets?query=btc-updown-5m&active=true&limit=5",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm markets --view search --query btc-updown-5m --active true --limit 5
# any MCP client; or from the terminal:
vike-data call polymarket_markets --args '{"view": "search", "query": "btc-updown-5m", "limit": 5}'
One market
One market by condition_id, as an ARRAY of 0 or 1 rows.
- Route
GET /v1/polymarket/markets/{condition_id}- CLI
vike-data pm market- MCP
polymarket_market_detail- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
condition_idrequired | path | The 0x-prefixed market condition id. |
- A market outside the recorded universe is answered from Polymarket's full market catalog:
sourcesays which list answered (recordedorcatalog), and a catalog row hastick_sizeandneg_risk=null.active/closedare derived the same way for both. - ⚠ An unknown id is
200 [], never a 404. ⚠ A TOKEN id in this position returns[]too: the route cannot look up by token (the MCP tool can).
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/markets/0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/markets/0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm market 0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846
# any MCP client; or from the terminal:
vike-data call polymarket_market_detail --args '{"condition_id": "0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846"}'
One market's trades, with wallets (paged)
One market's trades newest first, with the wallet behind each: who traded, which side, how much and at what price. History runs from 2026-01-01 to a few minutes ago.
- Route
GET /v1/polymarket/markets/{condition_id}/trades- CLI
vike-data pm market-trades- MCP
polymarket_market_trades- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
condition_idrequired | path | The 0x-prefixed market condition id (0x and 64 hex digits). An unknown or malformed id is 200 with an empty trades, never a 404. |
from_ms | query | Trade time, epoch ms, inclusive. Default: from the first trade. |
to_ms | query | Trade time, epoch ms, exclusive. Must be greater than from_ms. Default: no upper bound. |
cursor | query | Resume strictly after the last row of a previous page: pass its next_cursor back unchanged. |
limit | query | Rows per page. Default 1000, hard cap 10000. A page of 1,000 typically takes 0.5 to 2 seconds and a full 10,000 up to about 10 seconds when the service is busy; each page counts as one request, so ask for full pages. |
- Envelope
{trades, has_more, next_cursor};next_cursoris a string only whilehas_moreis true andnullotherwise. Walk a market's whole history by followingnext_cursor; bound it withfrom_ms/to_ms, a half-open[from_ms, to_ms)window. Rows are ordered by time and then by a fixed tie-break, so a page boundary inside one block neither repeats nor skips a row. - Each row:
ts(epoch ms, the block's time, whole seconds),tx_hash,log_index,block_number,proxy_wallet,nameandpseudonym(Polymarket's display labels, empty when unset),side(BUYorSELL),role(makerortaker),outcome,outcome_index,token_id,size(shares) andprice(0 to 1). - ⚠
proxy_walletis the Polymarket proxy wallet that placed the order, not necessarily the person's own address. ⚠sideis per token: a buy of one outcome and a sell of the other can be the same bet. - ⚠ One row is one ORDER's fill, not one match. A match is several rows: the resting
makerorders that were hit, and onetakerrow for the aggressor, whose size is the sum of theirs. Addingsize * priceover both roles counts the match twice, and the two roles price a complementary-token match differently (over 15 minutes of one market: $11,060 summed overmakerrows, $17,066 overtakerrows). Pick one role and stay with it; never add both. - ⚠ A few percent of fills are not rows. Fills one wallet made on one token inside a single transaction can be merged into one row, so a count here can sit under the on-chain fill count (-3.2% of fills and -2.3% of USD over 58 markets checked on 2026-10-17).
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/markets/0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846/trades?limit=50"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/markets/0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846/trades?limit=50",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm market-trades 0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846 --limit 50
# any MCP client; or from the terminal:
vike-data call polymarket_market_trades --args '{"condition_id": "0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846", "limit": 50}'
On-chain settlement
The ON-CHAIN resolution row for one market, as an array of 0 or 1 rows.
- Route
GET /v1/polymarket/settlements/{condition_id}- CLI
vike-data pm settlement- MCP
polymarket_settlement- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
condition_idrequired | path | The 0x-prefixed market condition id. As with /markets/{condition_id}, a token_id here returns []. |
- ⚠
[]means "the chain resolved nothing", NOT "open". ~2,134 markets are closed by Polymarket with no on-chain resolution (cancellations, UMA voids), so an empty settlement is not "still trading" — which is whyactive/closedon/marketsread BOTH sources rather than this one alone. market_knownis false for a resolved market with no market-list row: itsslug,token_ids,outcomes,question,winning_token_idandwinning_outcomeare then empty, while the on-chain fields are still exact.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/settlements/0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/settlements/0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm settlement 0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846
# any MCP client; or from the terminal:
vike-data call polymarket_settlement --args '{"condition_id": "0x5db999fad322cea2914535aae5517060c3f80ad6d8c0231cde2124a434d16846"}'
On-chain settlements (paged)
On-chain settlements, oldest resolution first, ordered (resolved_ts_ms, condition_id).
- Route
GET /v1/polymarket/settlements/bulk- CLI
vike-data pm settlements- MCP
polymarket_settlements_bulk- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
from_ms | query | Resolution time, epoch ms, inclusive. Default: from the first resolution. |
to_ms | query | Resolution time, epoch ms, exclusive. Must be greater than from_ms. Default: no upper bound. |
slug_prefix | query | Only markets whose slug starts with this, e.g. btc-updown-5m-. Lowercase letters, digits and -, up to 100 characters; anything else is a 400. A market with no known slug never matches. |
cursor | query | Resume strictly after this resolved_ts_ms:condition_id, taken from a previous page's next_cursor. |
limit | query | Rows per page. Default and hard cap 5000. A full page typically takes 1 to 3 seconds and each page counts as one request, so ask for full pages. |
- Envelope
{settlements, has_more, next_cursor};next_cursoris a string only whilehas_moreis true andnullotherwise. Each row is exactly what/settlements/{condition_id}returns for that market. Walk every settled 5-minute BTC market with?slug_prefix=btc-updown-5m-&limit=5000and follownext_cursor: 5,000 markets per request instead of one. - Pages are rebuilt every 5 minutes: a market that has just resolved on-chain can take up to about 5 minutes to appear here, and a row with
market_knownfalse gets its market fields at the first rebuild after the market is added./settlements/{condition_id}is always current.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/settlements/bulk?slug_prefix=btc-updown-5m-&limit=100"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/settlements/bulk?slug_prefix=btc-updown-5m-&limit=100",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm settlements --slug-prefix btc-updown-5m- --limit 100
# any MCP client; or from the terminal:
vike-data call polymarket_settlements_bulk --args '{"slug_prefix": "btc-updown-5m-", "limit": 100}'
Order book, stats and candles
One token’s book at any moment since 2026-07-25, its tape summary and its candles.
Order-book snapshot
The nearest book snapshot at or before ts, flattened to levels.
- Route
GET /v1/polymarket/orderbook- CLI
vike-data pm orderbook- MCP
polymarket_orderbook- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
token_idrequired | query | The outcome token. |
ts | query | As-of epoch ms. The nearest snapshot at-or-before it is used; omit for the latest. |
depth | query | Levels per side. Default 25, hard cap 25. |
as_of_tsrepeats on every row and is the snapshot ACTUALLY used, not thetsrequested. Asks precede bids (sidesorts alphabetically), each ascending by level, in one array.- ⚠ A book can be one-sided, so
depth=5returning five rows rather than ten is correct. ⚠ And this endpoint can currently return every level SEVERAL TIMES.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/orderbook?token_id=1910830010387565971650098373488592514702818137344973088263643820608151819241&depth=10"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/orderbook?token_id=1910830010387565971650098373488592514702818137344973088263643820608151819241&depth=10",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm orderbook 1910830010387565971650098373488592514702818137344973088263643820608151819241 --depth 10
# any MCP client; or from the terminal:
vike-data call polymarket_orderbook --args '{"token_id": "1910830010387565971650098373488592514702818137344973088263643820608151819241", "depth": 10}'
One token's tape summary
Event counts, trade volume and first/last timestamps for one token.
- Route
GET /v1/polymarket/market_stats- CLI
vike-data pm market-stats- MCP
polymarket_market_stats- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
token_idrequired | query | The outcome token. Omitting it returns 400 {"error":"market_stats requires token_id"}. |
- ⚠ ALWAYS exactly one row. An unknown token returns one row of ZEROS, not
[].
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/market_stats?token_id=1910830010387565971650098373488592514702818137344973088263643820608151819241"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/market_stats?token_id=1910830010387565971650098373488592514702818137344973088263643820608151819241",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm market-stats 1910830010387565971650098373488592514702818137344973088263643820608151819241
# any MCP client; or from the terminal:
vike-data call polymarket_market_stats --args '{"token_id": "1910830010387565971650098373488592514702818137344973088263643820608151819241"}'
Polymarket candles
Bars at a fixed interval, OLDEST first. With to and no from, limit keeps the newest bars up to to; the rows still come back oldest first.
- Route
GET /v1/polymarket/ohlcv- CLI
vike-data candles polymarket- MCP
polymarket_ohlcv- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
token_idrequired | query | The outcome token. |
intervalrequired | query | One of 1m, 5m, 1h, 1d. Anything else returns 400 naming the four accepted values. |
from | query | Epoch MILLISECONDS, INCLUSIVE lower bound. |
to | query | Epoch MILLISECONDS, INCLUSIVE upper bound. |
limit | query | Max bars. Default 500, hard cap 1000. With to and no from, the limit bars nearest to (so to=X&limit=1 is the last bar at or before X); otherwise the oldest bars of the window. |
- ⚠
from/tohere are epoch-ms INTEGERS andtois INCLUSIVE — unlike/v1/polymarket/events, whose window is half-open, and unlike/v1/polymarket/archive/datasets, where the same two names areYYYY-MM-DDSTRINGS. A date-shaped value here does not parse as an integer and is therefore IGNORED, answering the request unfiltered. - ⚠ The two required args are checked in order —
token_idfirst, theninterval— so?interval=1halone reports the missing token, not the other way round.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/ohlcv?token_id=1910830010387565971650098373488592514702818137344973088263643820608151819241&interval=1h&limit=48"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/ohlcv?token_id=1910830010387565971650098373488592514702818137344973088263643820608151819241&interval=1h&limit=48",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data candles polymarket 1910830010387565971650098373488592514702818137344973088263643820608151819241 --interval 1h --limit 48
# any MCP client; or from the terminal:
vike-data call polymarket_ohlcv --args '{"token_id": "1910830010387565971650098373488592514702818137344973088263643820608151819241", "interval": "1h", "limit": 48}'
Event tape
Every recorded book snapshot, price change and trade, in order.
Raw event tape (paged)
The raw per-market event tape, ordered (local_ts, seq, token_id).
- Route
GET /v1/polymarket/events- CLI
vike-data pm tape- MCP
polymarket_tape- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
token_id | query | A single token — sugar for a one-element token_ids. |
token_ids | query | Comma-separated token ids, max 100 per request. Wins over token_id if both are given. |
from_msrequired | query | Window start, epoch ms, inclusive. |
to_msrequired | query | Window end, epoch ms, exclusive. Must be greater than from_ms. |
event_type | query | Filter to one of book, price_change, trade, tick_size_change, status. |
cursor | query | Resume from a previous page's next_cursor, passed back unchanged. It is local_ts:seq:token_id, with a ~n suffix when the page ended between two rows that share one local_ts:seq:token_id (two prints of a token in the same millisecond); no row is skipped or repeated. |
limit | query | Max rows per page. Default 5000, hard cap 50000. |
- ⚠ One of two routes whose body is an envelope (the other is
/settlements/bulk):{events, has_more, next_cursor}.bids/asksare strings carrying embedded JSON, andis_snapshotis a number, 0 or 1. - Bounded in every dimension on purpose: both window bounds required, the window itself capped at 24h,
token_idscapped at 100,limitcapped at 50,000.next_cursoris a string only whilehas_moreis true andnullotherwise.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/events?token_id=1910830010387565971650098373488592514702818137344973088263643820608151819241&from_ms=1793688535000&to_ms=1793689435000&event_type=trade"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/events?token_id=1910830010387565971650098373488592514702818137344973088263643820608151819241&from_ms=1793688535000&to_ms=1793689435000&event_type=trade",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm tape --token-id 1910830010387565971650098373488592514702818137344973088263643820608151819241 --from-ms 1793688535000 --to-ms 1793689435000 --event-type trade
# any MCP client; or from the terminal:
vike-data call polymarket_tape --args '{"token_id": "1910830010387565971650098373488592514702818137344973088263643820608151819241", "from_ms": 1793688535000, "to_ms": 1793689435000, "event_type": "trade"}'
Traders
Wallet profiles, smart money and the leaderboard, read from the on-chain trade tape.
One trader's profile
A Polymarket wallet's profile: lifetime volume and trade counts, realized P&L and hit rate over window (by RESOLUTION time), markets, recent trades, open positions and an equity curve. A wallet with no recorded trades answers 400 {"error": ...}, not zeros.
- Route
GET /v1/polymarket/wallets/{address}- CLI
vike-data pm wallet- MCP
polymarket_wallet- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
addressrequired | path | The wallet's 0x-prefixed proxy-wallet address (40 hex digits). Anything else is a 400. |
window | query | 24h, 7d, 30d or all. Default all. Any other value is a 400. See *What the window moves* for which fields it changes. |
- The headline
realized_pnl_usd,resolved_marketsandhit_ratecount only markets already settled in Polymarket's own daily resolution records, so a market that settled on chain in the last day can showrealized_pnlinmarkets[](which also reads the on-chain settlement) before the headline counts it. The headlinehit_rateis volume-weighted;category_breakdown[].hit_rateis a plain count (wins/resolved), andcategory_breakdown[].hit_rate_volume_weightedis the headline's definition per category. Eachopen_positionsrow carries itstoken_id. - The wallets that trade the same markets (
correlated) are not in this response: that scan is far heavier than the rest of the profile, so it is its own endpoint,GET /v1/polymarket/wallets/{address}/correlated. The key is absent, not an empty list, and every successful response says so in anX-Vike-Hintheader.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/wallets/0xa8f47125d4fe93f9079fdf999f950d71e4ec2ff2?window=30d"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/wallets/0xa8f47125d4fe93f9079fdf999f950d71e4ec2ff2?window=30d",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm wallet 0xa8f47125d4fe93f9079fdf999f950d71e4ec2ff2 --window 30d
# any MCP client; or from the terminal:
vike-data call polymarket_wallet --args '{"address": "0xa8f47125d4fe93f9079fdf999f950d71e4ec2ff2", "window": "30d"}'
Smart-money signals
flows: markets with the largest net inflow from the smart-money cohort over the window. mispricings: markets where that cohort bought meaningfully below the current mid. The cohort is wallets with $1k+ realized PnL over 5+ resolved markets and $5k+ volume. A flows row's buy_inflow, sell_outflow and net_inflow add both outcomes, so buying Up and buying Down both read as inflow; outcomes splits them per outcome, and a market's direction is the difference between its outcomes' net_inflow.
- Route
GET /v1/polymarket/smart_money- CLI
vike-data pm smart-money- MCP
polymarket_smart_money- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
mode | query | flows (default) — markets with the largest net cohort inflow in the window. mispricings — markets where the cohort bought meaningfully below the current mid. Any other value is a 400. |
hours | query | Lookback window in hours, an integer from 1 to 720. Default 24. Anything else is a 400. |
category | query | Restrict to one market category (at most 64 characters). Default: all. An unknown category returns [], not an error. |
limit | query | Maximum rows, a positive integer. Default 10; values above 20 are capped at 20. |
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/smart_money?mode=flows&hours=24&limit=10"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/smart_money?mode=flows&hours=24&limit=10",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm smart-money --mode flows --hours 24
# any MCP client; or from the terminal:
vike-data call polymarket_smart_money --args '{"mode": "flows", "hours": 24}'
Wallet leaderboard
The wallet leaderboard: Polymarket wallets ranked by realized P&L, hit rate, volume or trade count, as an ARRAY of rows (an empty array means no wallet passes the filters). The same board vike.io/polymarket shows, read from the hourly leaderboard table, not the tape.
- Route
GET /v1/polymarket/leaderboard- CLI
vike-data pm leaderboard- MCP
polymarket_leaderboard- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
category | query | all (default), Crypto, Sports, Politics, Pop-Culture or Tech. Every figure is recomputed on that category's markets only. Any other value is a 400. |
window | query | 24h, 7d, 30d or all. Default all. The period behind trades_window, volume_window_usd and realized_pnl_window_usd (P&L of the markets that RESOLVED inside it), and what the *_window sorts rank on; with no sort, 24h / 7d / 30d rank on realized_pnl_window_usd. 24h, 7d and 30d are rebuilt hourly and a market's resolution time can be corrected later, so they can be revised; all is not affected. Any other value is a 400. |
sort | query | Default realized_pnl_window when window is 24h, 7d or 30d, realized_pnl when it is all. realized_pnl (lifetime USD, largest first, whatever the window), realized_pnl_asc (biggest losers first), realized_pnl_window and realized_pnl_window_asc (P&L inside window), hit_rate (highest first, nulls last; the top is often wallets that buy near-certain outcomes just under $1, a 100% rate on a sliver of P&L, so set realized_pnl_usd against volume_all_usd before reading it as skill), resolved (most resolved markets), volume_24h and trades_24h (USD volume and trade count inside window; the names are historical), volume_all, trades_all, markets (lifetime), last_seen (most recent first). Ties break on the wallet address; hit_rate ties break on the most resolved markets first, then the largest lifetime volume, then the address. Any other value is a 400. |
min_resolved | query | Minimum resolved markets, a count from 0 to 4294967295. Default 20, so a hit_rate rests on a real sample; 0 includes wallets too thin for one. |
min_volume_usd | query | Minimum lifetime volume in USD (buys plus sells), a finite number of 0 or more. Default 0. |
min_avg_usd | query | Minimum average trade size in USD, a finite number of 0 or more. Default 0. 20 leaves out every wallet tagged bot. |
limit | query | Rows per page, a positive integer. Default 20; values above 50 are capped at 50. |
offset | query | Rows to skip, an integer from 0 to 1000. Default 0. The board is rebuilt hourly, so a page fetched after a rebuild can repeat or skip a wallet. Above 1000 is a 400: narrow with category or a min_* filter instead. |
hit_rateis a FRACTION (0.62 = 62%), volume-weighted, andnullwhere undefined, never 0;hit_rate_ciis the +/- half-width of its 95% Wilson interval, also a fraction.tagsarewhale,bot,hotandcold.first_seenis the first trade in the recorded tape (it starts 2026-01-01), so it does not show a wallet is new.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/leaderboard?window=30d&sort=realized_pnl&limit=20"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/leaderboard?window=30d&sort=realized_pnl&limit=20",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm leaderboard --window 30d --sort realized_pnl
# any MCP client; or from the terminal:
vike-data call polymarket_leaderboard --args '{"window": "30d", "sort": "realized_pnl"}'
Archive files
The daily Parquet files: list them, and resolve one to a download.
Archive file index
Every known archive Parquet file, filtered and paged, in MANIFEST ORDER.
- Route
GET /v1/polymarket/archive/datasets- CLI
vike-data pm archive- MCP
polymarket_archive- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
asset | query | Matches family-layout rows only, e.g. btc. Supplying it excludes every flat row. |
tenor | query | Matches family-layout rows only, e.g. 5m. |
stream | query | One of book_events, trades, l1_quotes. |
from | query | YYYY-MM-DD, inclusive lower date bound. |
to | query | YYYY-MM-DD, inclusive upper date bound. |
limit | query | Max rows. Default 200, hard cap 1000. |
offset | query | Skip this many matching rows. Default 0. |
{"datasets": [...], "limit": n, "offset": n, "total": n}—totalis the filtered count BEFORE paging, andlimit/offsetecho the EFFECTIVE values.- ⚠ TWO row shapes on one endpoint. A
familyrow carriesassetandtenor; aflatrow OMITS both keys entirely rather than emittingnull. Supplying either filter therefore excludes every flat row by construction. - ⚠
from/toare DATE STRINGS here and epoch-ms integers on/v1/polymarket/ohlcv. Same names, different endpoints, different types.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/archive/datasets?stream=trades&from=2026-11-02&to=2026-11-02"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/archive/datasets?stream=trades&from=2026-11-02&to=2026-11-02",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm archive --stream trades --date-from 2026-11-02 --date-to 2026-11-02
# any MCP client; or from the terminal:
vike-data call polymarket_archive --args '{"view": "datasets", "stream": "trades", "date_from": "2026-11-02", "date_to": "2026-11-02"}'
Resolve one archive file
Resolve exactly ONE dataset to a temporary download link.
- Route
GET /v1/polymarket/archive/url- CLI
vike-data pm archive- MCP
polymarket_archive- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
daterequired | query | YYYY-MM-DD. Omitting it returns 400 {"error":"archive/url requires date"}. |
streamrequired | query | One of book_events, trades, l1_quotes. |
asset | query | Supply both asset and tenor to resolve the family layout, or neither to resolve the flat layout. |
tenor | query | With asset, selects the family layout. |
redirect | query | true (or 1) returns 302 with the file in Location; the JSON body is still sent, so a client that does not follow redirects keeps the answer. |
- Returns a single dataset OBJECT — not an array, not an envelope. The only route in this family with that shape, the only one that can answer 302, and the only one that sets a header.
urlis a presigned link to that one file, valid forexpires_inseconds (7 days) untilexpires_at; it needs no API key, so do not share it. A file your account may not download is a 403.- ⚠
?redirect=1returns 302 with aLocationheader AND the full JSON body, so a client that does not follow redirects still gets the answer. - ⚠ "No such dataset" is a 400, not a 404: the manifest was read and the combination simply has no file. A 404 would claim the route does not exist.
- ⚠ Both
assetandtenor, or neither. Exactly one is rejected as ambiguous rather than guessed at.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/archive/url?date=2026-11-02&stream=trades"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/archive/url?date=2026-11-02&stream=trades",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
vike-data pm archive --view url --stream trades --date 2026-11-02
# any MCP client; or from the terminal:
vike-data call polymarket_archive --args '{"view": "url", "stream": "trades", "date": "2026-11-02"}'
Live stream catch-up
What the WebSocket sent while you were disconnected, from a cursor, with the same filters.
Stream catch-up
Stored events of one live-stream channel after a cursor, oldest first: the REST catch-up of wss://stream.vike.io/v1/stream/polymarket, for what a client missed while it was disconnected (events are kept 3 days). Pass the channel's filters as query parameters, as on the socket: a list as a comma list (wallet=0x..,0x..), a number as a number (min_usd=1000); an unknown filter, or one the channel does not take, is a 400 naming it.
- Route
GET /v1/polymarket/stream-events- CLI
- —
- MCP
stream_events- Cost
- 1 request
Parameters
| Parameter | In | Description |
|---|---|---|
channelrequired | query | pm.new_markets, pm.positions, pm.price_moves, pm.resolutions, pm.trades |
cursor | query | next_cursor of the previous page or the cursor of a stream frame. Empty: the oldest retained event |
limit | query | Default 200, clamped to 1-1000 |
- Channels and their filters:
- -
pm.new_markets:class-pm.positions:action,market,min_usd,wallet-pm.price_moves:market,min_move,move,token_id-pm.resolutions:market-pm.trades:market,min_usd,pnl_tier,role,side,volume_tier,wallet - Returns
{events: [{cursor, event}], next_cursor, has_more}.eventis exactly what the socket sent, so dedupe onevent.event_id. Passnext_cursorback ascursorwhilehas_moreis true. A window in which the channel was idle (nobody subscribed) comes as an item{cursor, gap}in its place. A cursor older than the retention is a 410 naming the oldest cursor accepted; omitcursorto start at the oldest stored event.
Call it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/stream-events?channel=pm.trades&limit=50"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/stream-events?channel=pm.trades&limit=50",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
)
print(r.json())
# any MCP client; or from the terminal:
vike-data call stream_events --args '{"channel": "pm.trades", "limit": 50}'
What comes back, field by field
The datasets behind these routes, with real rows and loading code.
Markets & settlements
Market search, one-market detail, and the on-chain resolution per condition.
- Route
GET /v1/polymarket/markets- Cadence
- Kept by the market recorder; state derived per request
- Coverage
- Recorded markets with derived state: closed when the chain resolved the condition OR Polymarket's own feed closed it. /markets filters by slug substring and active | closed (never both), newest end_date first, limit up to 100. `/markets/{condition_id}` answers a market outside the recorded set from Polymarket's full market catalog; its `source` says which list answered. `/settlements/{condition_id}` is the on-chain resolution — [] means the chain resolved nothing, not that the market is open; `/settlements/bulk` pages every settlement oldest resolution first (filter by resolution time and slug prefix, up to 5,000 a page, next_cursor + has_more) from pages rebuilt every 5 minutes, so a newly resolved market can appear there up to about 5 minutes after `/settlements/{condition_id}` shows it. Candles per token: /ohlcv.
- Rows
Fields
| Field | Type | Description |
|---|---|---|
condition_id | string | The market, 0x-prefixed |
slug | string | URL slug |
active | integer | 1 when not closed, else 0 |
closed | integer | 1 when resolved on chain or closed by the venue |
end_date | integer | Scheduled end, epoch milliseconds |
neg_risk | integer|null | 1 for a neg-risk market; null on a catalog row |
tick_size | number|null | Price increment; null on a catalog row |
token_ids | array | One outcome token id per outcome |
Real rows
Markets & settlements · rows
Load it
curl -H "X-API-KEY: $VIKE_API_KEY" \
"https://data.vike.io/v1/polymarket/markets?active=true&limit=25"
import os, requests
r = requests.get(
"https://data.vike.io/v1/polymarket/markets?active=true&limit=25",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
timeout=30,
)
r.raise_for_status()
body = r.json()
import os, requests
import polars as pl
body = requests.get(
"https://data.vike.io/v1/polymarket/markets?active=true&limit=25",
headers={"X-API-KEY": os.environ["VIKE_API_KEY"]},
timeout=30,
).json()
rows = body if isinstance(body, list) else next(v for v in body.values() if isinstance(v, list))
df = pl.DataFrame(rows)
print(df.head())
Every parameter of /v1/polymarket/markets → · back to the catalog
Trades
/markets/{condition_id}/trades and /events?event_type=trade: every field, real rows and code.
Order-book events and quotes
/events, /orderbook and the archive files book_events, l1_quotes.
Ids, coverage and the traps in the answers
/markets lists the markets Vike records. /markets/{condition_id} also answers from Polymarket’s full catalog; every row says which in source: recorded or catalog.X-Vike-Note.[] from /settlements/{condition_id} means the chain resolved nothing, never “still open”: Polymarket closes some markets with no on-chain resolution.bids and asks are JSON text, not arrays; a 0 in a field the event type does not fill means “not set”, not a price.side, level) can arrive several times; read a side’s depth as its max(level). A book can be one-sided.Status codes
Branch on the status code, not the body.
| Status | Meaning | Body |
|---|---|---|
| 400 | The input was rejected: an unknown market or dex, a malformed identifier, a cursor from another query. Polymarket routes also refuse unknown query parameters. | Hyperliquid / on-chain {"detail": "…"} · Polymarket {"error": "…"} |
| 401 | Missing, invalid or deactivated credential — on /v1 and the archive alike. | Turned away before the API runs, so the body can be an HTML page. Do not assume JSON. |
| 404 | No such route or archive file. An unknown condition_id on a Polymarket detail route is 200 [], not a 404. | {"detail": "Not Found"} |
| 422 | A parameter failed validation: wrong type, or outside the bounds the route declares. | {"detail": [ … ]} |
| 429 | Hourly limit exceeded, or on /v1 the per-minute cap (200 a minute per key). | Retry-After: 60 (hourly) or Retry-After: 1 (per-minute) header, JSON error body |
| 503 | The data store failed. Not your request — retry with back-off. | Hyperliquid / on-chain {"detail": "upstream unavailable"} · Polymarket {"error": "upstream unavailable"} |
- A failed query is never a 200 with an empty body. An empty list means there were no rows.
- The
/v1/onchainroutes and Hyperliquid/tradersand/positions/matchrun through a shared tool layer, which reports its own failures in-band — so an internal error there can arrive as a 400 carrying the error text. - MCP answers a bad credential with HTTP 401, a
WWW-Authenticate: Bearer resource_metadata=...header (where an OAuth client starts discovery) and a JSON-RPC error (-32000, Invalid or missing API key). A rate-limit trip there is a real 429. Check the body, not just the status.