curl --request GET \
--url https://api.polynode.dev/v1/markets/{id}/positions \
--header 'x-api-key: <api-key>'import requests
url = "https://api.polynode.dev/v1/markets/{id}/positions"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://api.polynode.dev/v1/markets/{id}/positions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.polynode.dev/v1/markets/{id}/positions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.polynode.dev/v1/markets/{id}/positions"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.polynode.dev/v1/markets/{id}/positions")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.polynode.dev/v1/markets/{id}/positions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"condition_id": "0x895e01dbf3e6a33cd9a44ca0f8cdb5df1bd2b0b6ebed5300d28f8da7145145e4",
"market_title": "Will Donald Trump win the 2028 Republican presidential nomination?",
"slug": "will-donald-trump-win-the-2028-republican-presidential-nomination",
"outcome_names": [
"Yes",
"No"
],
"outcomes": [
{
"token": "3039641309958397001906153616677074061284510636203465131156925998487819889437",
"positions": [
{
"proxyWallet": "0xa5ef39c3d3e10d0b270233af41cac69796b12966",
"name": "",
"outcome": "No",
"outcomeIndex": 1,
"size": 1735573.72,
"avgPrice": 0,
"currPrice": 0.9835,
"currentValue": 1706936.75,
"cashPnl": 1706936.75,
"realizedPnl": 0,
"totalPnl": 1706936.75
},
{
"proxyWallet": "0xc2e7800b5af46e6093872b177b7a5e7f0563be51",
"name": "beachboy4",
"outcome": "Yes",
"outcomeIndex": 0,
"size": 248651.26,
"avgPrice": 0.040276,
"currPrice": 0.0165,
"currentValue": 4102.75,
"cashPnl": -5911.93,
"realizedPnl": 0,
"totalPnl": -5911.93,
"firstTradeAt": 1753016057,
"lastTradeAt": 1765034355
}
]
}
]
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}Top Traders (Market)
Every wallet that holds (or has held) a position in a single market, grouped by outcome and sorted by P&L. The ‘who’s making money on this market’ feed.
curl --request GET \
--url https://api.polynode.dev/v1/markets/{id}/positions \
--header 'x-api-key: <api-key>'import requests
url = "https://api.polynode.dev/v1/markets/{id}/positions"
headers = {"x-api-key": "<api-key>"}
response = requests.get(url, headers=headers)
print(response.text)const options = {method: 'GET', headers: {'x-api-key': '<api-key>'}};
fetch('https://api.polynode.dev/v1/markets/{id}/positions', options)
.then(res => res.json())
.then(res => console.log(res))
.catch(err => console.error(err));<?php
$curl = curl_init();
curl_setopt_array($curl, [
CURLOPT_URL => "https://api.polynode.dev/v1/markets/{id}/positions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
CURLOPT_HTTPHEADER => [
"x-api-key: <api-key>"
],
]);
$response = curl_exec($curl);
$err = curl_error($curl);
curl_close($curl);
if ($err) {
echo "cURL Error #:" . $err;
} else {
echo $response;
}package main
import (
"fmt"
"net/http"
"io"
)
func main() {
url := "https://api.polynode.dev/v1/markets/{id}/positions"
req, _ := http.NewRequest("GET", url, nil)
req.Header.Add("x-api-key", "<api-key>")
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://api.polynode.dev/v1/markets/{id}/positions")
.header("x-api-key", "<api-key>")
.asString();require 'uri'
require 'net/http'
url = URI("https://api.polynode.dev/v1/markets/{id}/positions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
request["x-api-key"] = '<api-key>'
response = http.request(request)
puts response.read_body{
"condition_id": "0x895e01dbf3e6a33cd9a44ca0f8cdb5df1bd2b0b6ebed5300d28f8da7145145e4",
"market_title": "Will Donald Trump win the 2028 Republican presidential nomination?",
"slug": "will-donald-trump-win-the-2028-republican-presidential-nomination",
"outcome_names": [
"Yes",
"No"
],
"outcomes": [
{
"token": "3039641309958397001906153616677074061284510636203465131156925998487819889437",
"positions": [
{
"proxyWallet": "0xa5ef39c3d3e10d0b270233af41cac69796b12966",
"name": "",
"outcome": "No",
"outcomeIndex": 1,
"size": 1735573.72,
"avgPrice": 0,
"currPrice": 0.9835,
"currentValue": 1706936.75,
"cashPnl": 1706936.75,
"realizedPnl": 0,
"totalPnl": 1706936.75
},
{
"proxyWallet": "0xc2e7800b5af46e6093872b177b7a5e7f0563be51",
"name": "beachboy4",
"outcome": "Yes",
"outcomeIndex": 0,
"size": 248651.26,
"avgPrice": 0.040276,
"currPrice": 0.0165,
"currentValue": 4102.75,
"cashPnl": -5911.93,
"realizedPnl": 0,
"totalPnl": -5911.93,
"firstTradeAt": 1753016057,
"lastTradeAt": 1765034355
}
]
}
]
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}{
"error": "<string>"
}btc-updown-5m-1777179000) or the condition_id (e.g. 0xa7ae8a41...). It does not accept an outcome token id — pass the market identifier, not the side.
avgPrice, realizedPnl, etc.) — distinct from polynode’s V2 onchain endpoints which use snake_case. Different schemas, different use cases. Default cap is 50 rows per outcome; configurable up to 500.Request
GET /v1/markets/{slug-or-condition-id}/positions
| Parameter | Type | Location | Description |
|---|---|---|---|
id | string | path | Market slug or condition_id (0x…). Not an outcome token id. |
limit | integer | query | Max rows per outcome. Default 50, max 500. |
offset | integer | query | Skip first N rows. |
sortBy | string | query | TOTAL_PNL (default), REALIZED_PNL, CURRENT_VALUE, SIZE, INITIAL_VALUE. |
sortDirection | string | query | DESC (default) or ASC. |
status | string | query | OPEN, CLOSED, or ALL (default). |
min_size | number | query | Drop positions with size < min_size from each outcome. Use min_size=0.0001 to filter to current holders only (excludes historical participants who closed out to zero). |
includeTrades | boolean | query | When true, adds firstTradeAt / lastTradeAt (unix seconds) per row. Heavy — separate 20 req/min rate limit per key. |
user | string | query | Filter to a single wallet, or up to 20 wallets comma-separated, scoped to this market. |
TOTAL_PNL and REALIZED_PNL are sorted by Polymarket’s data-api directly. CURRENT_VALUE, SIZE, and INITIAL_VALUE are sorted in-process after the fetch (and after min_size if set), so they pair cleanly with the holder-filtering use case.Response
{
"condition_id": "0xa7ae8a4119fe00f231e693ed717339dd1e13da4617c79f3b1522ab1aee3965b6",
"market_title": "Bitcoin Up or Down - April 26, 12:50AM-12:55AM ET",
"slug": "btc-updown-5m-1777179000",
"image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png",
"outcome_names": ["Up", "Down"],
"outcomes": [
{
"token": "90684694382683135738693577247532961739938461809085935450142382916430459093778",
"positions": [
{
"proxyWallet": "0x9a1d392572d0e6bfefbf9302101b9e44c8ee86d6",
"name": "kq9000",
"profileImage": "",
"verified": false,
"asset": "90684694382683135738693577247532961739938461809085935450142382916430459093778",
"conditionId": "0xa7ae8a4119fe00f231e693ed717339dd1e13da4617c79f3b1522ab1aee3965b6",
"outcome": "Up",
"outcomeIndex": 0,
"size": 0,
"avgPrice": 0.4592,
"currPrice": 1,
"currentValue": 0,
"totalBought": 1633.1987,
"realizedPnl": 812.5754,
"cashPnl": 0,
"totalPnl": 812.5754,
"firstTradeAt": 1777179030,
"lastTradeAt": 1777179152
}
]
},
{ "token": "96824…", "positions": [ "…" ] }
]
}
Top-level fields
| Field | Type | Description |
|---|---|---|
condition_id | string | Market condition id |
market_title | string | Human-readable market question |
slug | string | Market slug |
image | string | Market image URL |
outcome_names | string[] | Ordered outcome labels (["Up","Down"], ["Yes","No"], etc.) |
outcomes[] | array | One entry per outcome — each holds a positions array |
outcomes[].token | string | Outcome token id |
outcomes[].positions[] | array | Traders with positions in this outcome, sorted by sortBy |
Per-trader fields (outcomes[].positions[])
| Field | Type | Description |
|---|---|---|
proxyWallet | string | Trader address (Gnosis Safe proxy) |
name | string | Display name (often empty or auto-generated) |
profileImage | string | Profile image URL (often empty) |
verified | boolean | Verified flag |
asset | string | Outcome token id (same as outcomes[].token) |
conditionId | string | Market condition id |
outcome | string | Outcome label ("Up", "No", etc.) |
outcomeIndex | number | 0-based position in outcome_names |
size | number | Current token balance. 0 = fully exited or never held. |
avgPrice | number | Volume-weighted average entry price |
currPrice | number | Current market price (or terminal 1.0/0.0 for resolved markets) |
currentValue | number | Mark-to-market value of remaining shares (size × currPrice) |
totalBought | number | Lifetime tokens acquired |
realizedPnl | number | Realized P&L from sells + redemptions, in USDC |
cashPnl | number | P&L from cash flows (separate accounting axis) |
totalPnl | number | Net portfolio P&L for this position |
firstTradeAt | number | Unix seconds — only with ?includeTrades=true |
lastTradeAt | number | Unix seconds — only with ?includeTrades=true |
Examples
Top 10 traders on a market by total P&L
curl "https://api.polynode.dev/v1/markets/btc-updown-5m-1777179000/positions?limit=10&sortBy=TOTAL_PNL&sortDirection=DESC" \
-H "x-api-key: YOUR_KEY"
By condition_id, with first/last trade timestamps
curl "https://api.polynode.dev/v1/markets/0xa7ae8a4119fe00f231e693ed717339dd1e13da4617c79f3b1522ab1aee3965b6/positions?includeTrades=true&limit=20" \
-H "x-api-key: YOUR_KEY"
Drill into specific wallets within a market
curl "https://api.polynode.dev/v1/markets/btc-updown-5m-1777179000/positions?user=0x9a1d392572d0e6bfefbf9302101b9e44c8ee86d6,0x44bd2993a69d8b569859ed8c0bf0b946f733f71a" \
-H "x-api-key: YOUR_KEY"
Closed positions only
curl "https://api.polynode.dev/v1/markets/btc-updown-5m-1777179000/positions?status=CLOSED&sortBy=REALIZED_PNL" \
-H "x-api-key: YOUR_KEY"
Notes
- Default cap of 50 rows per outcome unless
limitis set higher (max 500). On very large markets with deep tail traders, paginate withoffset. - Sort ties on equal P&L are non-deterministic — two wallets at the same
totalPnlmay swap positions between calls. UseproxyWalletas a stable secondary key on the client side if you need consistent ordering. size = 0is normal on closed positions, redeemed positions, or fully-exited positions. UserealizedPnlandtotalBoughtto detect history.firstTradeAt/lastTradeAtrequire?includeTrades=trueand trip a separate heavy-endpoint rate limit (20 req/min per key). Don’t request it on every refresh — fetch once and cache.
When to use this vs. other position endpoints
| If you want | Use |
|---|---|
| Top traders ranked on a single market | This endpoint |
| One wallet’s positions across all markets | GET /v2/wallets/{addr}/positions/onchain |
| All trades on a market (chronological fill feed) | GET /v2/onchain/markets/{tokenId}/trades |
Authorizations
Path Parameters
Condition ID (0x-prefixed) or market slug
Query Parameters
Maximum holders per outcome token (default 50, max 500)
1 <= x <= 500Pagination offset (default 0)
x >= 0Sort holders by field
TOKENS, CASH_PNL, REALIZED_PNL, TOTAL_PNL Sort direction
ASC, DESC Filter by position status
OPEN, CLOSED, ALL Enrich each position with firstTradeAt and lastTradeAt timestamps. Responses will be slower. Default false. Rate limited to 60 requests per minute per key (separate from your standard rate limit).
Filter to specific wallet address(es). Accepts a single address or multiple comma-separated addresses (max 20). Returns only those wallets' positions in this market.
Response
Market positions grouped by outcome token
Holder positions for a market, grouped by outcome token.
Market condition ID.
Array of outcome tokens, each containing a list of holder positions.
Show child attributes
Show child attributes
Market title (enriched by PolyNode).
Market slug (enriched by PolyNode).
Outcome names (e.g. ["Yes", "No"]).

