Skip to main content
GET
/
v2
/
onchain
/
positions
Positions
curl --request GET \
  --url https://api.polynode.dev/v2/onchain/positions

Documentation Index

Fetch the complete documentation index at: https://polynode.mintlify.app/llms.txt

Use this file to discover all available pages before exploring further.

Search and filter all Polymarket positions across every wallet. Each position is enriched with market metadata and includes realized P&L, average entry price, and current size. Sourced directly from onchain settlement data.

Request

GET /v2/onchain/positions

Query parameters

ParameterTypeRequiredDescription
walletstringNoFilter by wallet address
market_slugstringNoFilter by market slug (e.g. will-zohran-mamdani-win-the-2025-nyc-mayoral-election)
condition_idstringNoFilter by condition ID (0x-prefixed, 64 hex chars)
token_idstringNoFilter by outcome token ID
statusstringNoopen (size > 0), closed (size = 0), or all (default)
min_sizenumberNoMinimum position size in shares
limitintegerNoResults per page (1-500, default 100)
orderstringNoSort direction: desc (default, most recent first) or asc. When wallet is set, results are ordered by the wallet’s most recent on-chain activity per position. Otherwise, ordered by position ID.
pagination_keystringNoCursor from a previous response to fetch the next page. Not used for wallet queries (see Pagination).

Identifying markets

  • market_slug — human-readable URL slug from Polymarket. Returns positions for all outcomes.
  • condition_id — unique condition identifier. Returns positions for all outcomes.
  • token_id — specific outcome token. Returns positions for only that outcome.

Response

{
  "count": 2,
  "pagination": {
    "limit": 2,
    "has_more": true,
    "pagination_key": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639-99881503238784655670984673100655147508393351942279611133016622634826369070119"
  },
  "positions": [
    {
      "wallet": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639",
      "token_id": "99949662063403569895321097192043694236016212986254553767097648841549016857591",
      "size": 0.012785,
      "avg_price": 0.859999,
      "realized_pnl": 0.16,
      "unrealized_pnl": 0.02,
      "current_price": 0.88,
      "market_status": "live",
      "total_bought": 1.16,
      "market": "Will FC Bayern Munchen win on 2026-04-07?",
      "market_slug": "ucl-rma1-bay1-2026-04-07-bay1",
      "outcome": "Yes",
      "condition_id": "0x5f5c8d8fa28d77b5552562f32393ac199fb6b92ed1c1e2239e29c09f7e4eb3f5",
      "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/champions-league-pic-QIUFsL8vaDdq.png"
    },
    {
      "wallet": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639",
      "token_id": "99881503238784655670984673100655147508393351942279611133016622634826369070119",
      "size": 0,
      "avg_price": 0.98,
      "realized_pnl": 0.1,
      "unrealized_pnl": 0,
      "current_price": null,
      "market_status": "closed",
      "total_bought": 5,
      "market": "Solana Up or Down - December 30, 10:30PM-10:45PM ET",
      "market_slug": "sol-updown-15m-1767151800",
      "outcome": "Down",
      "condition_id": "0xa7f50f1bd65e8fd1790117b3b162dc03489df7b89cd35f4b1093792691b8a327",
      "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/SOL+fullsize.png"
    }
  ]
}

Position fields

FieldTypeDescription
walletstringWallet address holding the position
token_idstringOutcome token ID
sizenumberCurrent position size in shares. 0 means the position is fully exited.
avg_pricenumberAverage entry price (0 to 1)
realized_pnlnumberRealized profit/loss in USD. Reflects gains/losses from closed portions of the position and from onchain redemptions of resolved markets. Stays 0 for resolved-but-not-yet-redeemed positions; check redeemable to detect those.
unrealized_pnlnumberUnrealized profit/loss in USD on the remaining open shares. For resolved markets, uses the final settlement price (1.0 for winners, 0.0 for losers). For live markets, uses the current market price. Returns 0 when size = 0. Byte-identical to Polymarket’s cashPnl field for any position covered by both.
current_pricenumber | nullPrice used to compute unrealized_pnl. 1.0 or 0.0 for resolved markets (derived deterministically from on-chain payoutNumerators), the live market price for active markets, and null when size = 0 or no price is available.
market_statusstringOne of: "live" (market still trading), "resolved-win" (market resolved, this outcome won), "resolved-loss" (market resolved, this outcome lost), or "closed" (position fully exited, size = 0). A fifth value "resolved-unknown" may appear briefly for very old markets while resolution data is catching up. Never "live" when resolved_at is set.
wonboolean | undefinedPresent only on resolved-win / resolved-loss rows. true when this outcome won, false when it lost. Derived from on-chain payouts.
winning_outcome_indexnumber | undefinedPresent only on resolved rows. Numeric index of the outcome that won (0 or 1 for binary markets). Pair with outcome_index to know whether this row is the winning side.
outcome_indexnumber | undefinedNumeric index of this row’s outcome within the market’s outcomes array (0 or 1 for binary markets). Stable across the API regardless of how the UI labels the outcome (Yes/No, team names, etc.). Use this for cross-row joins instead of parsing outcome strings.
total_boughtnumberTotal amount bought in USD over the lifetime of the position.
initial_valuenumber | undefinedCost basis in USD of the currently held shares (size × cost_per_share). The exact value Polymarket uses internally to compute cashPnl. Use this if you need the basis number that matches what users see in the Polymarket UI.
redeemableboolean | undefinedtrue when the market has resolved and the user can call redeem on the CTF contract to claim payout (or accept loss). Useful for detecting “resolved-but-not-redeemed” positions: filter market_status = "resolved-loss" AND redeemable = true for unclaimed losses, or market_status = "resolved-win" AND redeemable = true for unclaimed wins.
opposite_assetstring | undefinedToken ID of the OPPOSITE outcome on the same market (the binary counterpart). Useful for fetching the matching position on the other side without re-resolving the condition.
marketstringMarket question text
market_slugstringMarket URL slug
outcomestringOutcome label (e.g. “Yes”, “No”)
condition_idstringMarket condition ID
imagestring | nullMarket image URL. null for some delisted or niche markets.
event_slugstring | nullParent event slug, distinct from the per-row market slug. For multi-market events (NBA games with several lines, election markets with several candidates, FIFA World Cup with one market per team), this is the parent the markets share. For single-market events, equals the market slug. null when event metadata is not yet known.
last_activitynumber | undefinedUnix timestamp of the wallet’s most recent fill on this position. Present only when querying by wallet. Positions with no fill history (e.g. acquired purely via split/merge/redemption) omit this field and sort to the end.
last_trade_atnumber | nullUnix seconds. Latest fill on this token across V1 + V2 exchanges. Same data as last_activity but always present (null instead of omitted) for consistent shape. Use either; last_trade_at is preferred for new integrations.
closed_atnumber | nullUnix seconds. Latest moment this wallet redeemed any outcome of the market for collateral. null when the wallet has not redeemed (open positions, positions sold to zero pre-resolution, or wins held but not yet redeemed).
resolved_atnumber | nullUnix seconds. Moment the market was resolved on-chain (when payouts became redeemable). null for markets that resolved before polynode began tracking, or for markets that have not yet resolved. Recent markets are fully covered.

Pagination fields

FieldTypeDescription
countnumberNumber of positions in this response
pagination.limitnumberRequested page size
pagination.has_morebooleantrue if more results exist beyond this page
pagination.pagination_keystringPass this as pagination_key in the next request to get the next page

Examples

All positions for a wallet

curl "https://api.polynode.dev/v2/onchain/positions?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&limit=100" \
  -H "x-api-key: YOUR_KEY"

Open positions only

Use status=open to get only positions with a non-zero size.
curl "https://api.polynode.dev/v2/onchain/positions?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&status=open&limit=100" \
  -H "x-api-key: YOUR_KEY"
{
  "count": 2,
  "pagination": {
    "limit": 2,
    "has_more": true,
    "pagination_key": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639-99504617..."
  },
  "positions": [
    {
      "wallet": "0xa973ae12882a1c24a75da7a3b52cb01500b2f639",
      "token_id": "99949662063403569895321097192043694236016212986254553767097648841549016857591",
      "size": 0.012785,
      "avg_price": 0.859999,
      "realized_pnl": 0.16,
      "unrealized_pnl": 0.02,
      "current_price": 0.88,
      "market_status": "live",
      "total_bought": 1.16,
      "market": "Will FC Bayern Munchen win on 2026-04-07?",
      "market_slug": "ucl-rma1-bay1-2026-04-07-bay1",
      "outcome": "Yes",
      "condition_id": "0x5f5c8d8fa28d77b5552562f32393ac199fb6b92ed1c1e2239e29c09f7e4eb3f5",
      "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/champions-league-pic-QIUFsL8vaDdq.png"
    }
  ]
}

Who holds a market

Use market_slug to see all wallets with positions on a specific market.
curl "https://api.polynode.dev/v2/onchain/positions?market_slug=will-zohran-mamdani-win-the-2025-nyc-mayoral-election&status=open&limit=50" \
  -H "x-api-key: YOUR_KEY"
{
  "count": 2,
  "pagination": {
    "limit": 2,
    "has_more": true,
    "pagination_key": "0xfffe254008792df0c325325a75a3c6e7aaed436a-10583236..."
  },
  "positions": [
    {
      "wallet": "0xffff3840fbf40fd2e193c01cc299cdf1262cffaf",
      "token_id": "33945469250963963541781051637999677727672635213493648594066577298999471399137",
      "size": 0.009534,
      "avg_price": 0.947999,
      "realized_pnl": -0.54,
      "unrealized_pnl": -5.07,
      "current_price": 0.416,
      "market_status": "live",
      "total_bought": 539.03,
      "market": "Will Zohran Mamdani win the 2025 NYC mayoral election?",
      "market_slug": "will-zohran-mamdani-win-the-2025-nyc-mayoral-election",
      "outcome": "Yes",
      "condition_id": "0xebddfcf7b4401dade8b4031770a1ab942b01854f3bed453d5df9425cd9f211a9",
      "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/will-zohran-mamdani-win-the-2025-nyc-mayoral-election-EscSJQTT6hWg.jpg"
    }
  ]
}

Large positions

Use min_size to find positions above a threshold.
curl "https://api.polynode.dev/v2/onchain/positions?min_size=100&limit=20" \
  -H "x-api-key: YOUR_KEY"
{
  "count": 2,
  "pagination": {
    "limit": 2,
    "has_more": true,
    "pagination_key": "0xffffffe1e093aacd21e4e281e66d543fb0b23455-98495912..."
  },
  "positions": [
    {
      "wallet": "0xffffffe1e093aacd21e4e281e66d543fb0b23455",
      "token_id": "98813479054803844837498343855179110721772056323529222930302029314504656450267",
      "size": 1100,
      "avg_price": 0.003636,
      "realized_pnl": 0,
      "unrealized_pnl": -3.96,
      "current_price": 0,
      "market_status": "resolved-loss",
      "total_bought": 1100,
      "market": "Bitcoin Up or Down - February 5, 9:45AM-10:00AM ET",
      "market_slug": "btc-updown-15m-1770302700",
      "outcome": "Up",
      "condition_id": "0x3ec09263f6fb247e65635d52c2787dc0f46806f153124e138f42ad03411198b4",
      "image": "https://polymarket-upload.s3.us-east-2.amazonaws.com/BTC+fullsize.png"
    }
  ]
}

Combined filters

Filter by wallet, market, and status at once.
curl "https://api.polynode.dev/v2/onchain/positions?wallet=0xa973ae12882a1c24a75da7a3b52cb01500b2f639&market_slug=ucl-rma1-bay1-2026-04-07-bay1&status=open&limit=10" \
  -H "x-api-key: YOUR_KEY"

Pagination

Wallet queries return every position for the wallet in a single response (up to 500), sorted by most recent on-chain activity. No pagination needed — request limit=500 and read all results. has_more is always false and no cursor is returned. Wallets with more than 500 lifetime positions are capped at 500. Non-wallet queries (filtering by market_slug, condition_id, token_id, or min_size alone) use cursor-based pagination for iterating through large result sets.
# First page
curl "https://api.polynode.dev/v2/onchain/positions?min_size=100&limit=100" \
  -H "x-api-key: YOUR_KEY"

# Next page
curl "https://api.polynode.dev/v2/onchain/positions?min_size=100&limit=100&pagination_key=CURSOR_FROM_PREVIOUS" \
  -H "x-api-key: YOUR_KEY"

Error responses

StatusResponseCondition
400{"error": "market_slug not found"}Invalid or unknown market_slug
400{"error": "condition_id not found"}Invalid or unknown condition_id
401{"error": "API key required. Pass via ?key= or x-api-key header."}Missing API key
403{"error": "V2 endpoints require a paid plan. See polynode.dev/pricing for details."}Free tier key
429{"error": "Rate limited. N req/s for your tier.", "retryAfterMs": ...}Rate limited

Notes

  • Position data is sourced from onchain settlement records. Every position that was ever opened on Polymarket is included.
  • The realized_pnl field reflects actual profit/loss from closed portions of the position, including onchain redemptions of resolved markets. For open positions, it reflects any partial closes.
  • The unrealized_pnl field shows the paper profit/loss on the remaining open shares. For resolved markets, this uses the final settlement price (1forwinners,1 for winners, 0 for losers). For active markets, the current market price is used.
  • market_status lets clients distinguish live-tradable positions from positions on resolved markets that the wallet never redeemed. Resolved-and-never-redeemed positions still have size > 0 and will show the correct terminal unrealized_pnl.
  • Market metadata (market, market_slug, outcome, condition_id, image) is enriched from our index. Very old or delisted markets may not have metadata.
  • When querying by wallet, the sort key is the timestamp of the most recent fill for each position, not the position’s close date. A position bought months ago and held to resolution (closed only via redemption) will sort by its original buy date.

Verifying parity with Polymarket

Per-position fields on this endpoint match Polymarket’s data-api.polymarket.com/positions byte-for-byte. Anyone can verify directly with the standalone Node script below — no internal access required.

Field map

polynode fieldPolymarket field
token_idasset
condition_idconditionId
sizesize
avg_priceavgPrice
realized_pnlrealizedPnl
unrealized_pnlcashPnl
current_pricecurPrice
outcomeoutcome

Self-test script (Node 18+, no dependencies)

// Save as parity_demo.mjs and run with:
//   POLYNODE_KEY=pn_live_xxx node parity_demo.mjs <wallet>

const KEY = process.env.POLYNODE_KEY;
const WALLET = process.argv[2];

const [pm, us] = await Promise.all([
  fetch(`https://data-api.polymarket.com/positions?user=${WALLET}&sizeThreshold=0&limit=2000`,
    { headers: { 'User-Agent': 'Mozilla/5.0' } }).then(r => r.json()),
  fetch(`https://api.polynode.dev/v2/onchain/positions?wallet=${WALLET}&status=all&limit=2000`,
    { headers: { 'x-api-key': KEY } }).then(r => r.json()).then(j => Array.isArray(j) ? j : (j.positions ?? [])),
]);

const pmByPid = new Map(pm.map(p => [String(p.asset), p]));
const usByPid = new Map(us.map(p => [String(p.token_id), p]));
const shared = [...pmByPid.keys()].filter(k => usByPid.has(k));

let exact = 0, sub_dollar = 0;
for (const pid of shared) {
  const p = pmByPid.get(pid), u = usByPid.get(pid);
  const avgDiff = Math.abs(Number(p.avgPrice) - Number(u.avg_price));
  const pnlDiff = Math.abs(Number(p.realizedPnl) - Number(u.realized_pnl));
  if (avgDiff < 0.005 && pnlDiff < 0.01) exact++;
  if (pnlDiff < 1.0) sub_dollar++;
  console.log(`  ${pid.slice(0,8)}…  PM realPnl=${Number(p.realizedPnl).toFixed(2).padStart(10)}  Us realPnl=${Number(u.realized_pnl).toFixed(2).padStart(10)}  Δ=${pnlDiff.toFixed(4)}`);
}
console.log(`\n  byte-perfect: ${exact}/${shared.length}    sub-$1: ${sub_dollar}/${shared.length}`);
Run against any wallet to confirm. Validated 2026-04-30 across diverse wallets at 100 % byte-perfect match on every shared open position.

What this proves

If you trust Polymarket’s positions page, you can trust ours — they’re computing the same thing from the same on-chain events. Use this script as ongoing regression coverage in your own integration if PnL accuracy is critical to your product.

Query Parameters

wallet
string

Filter by wallet address

market_slug
string

Filter by market slug

condition_id
string

Filter by condition ID

token_id
string

Filter by outcome token ID

status
enum<string>
default:all

Filter by position status

Available options:
open,
closed,
all
min_size
number

Minimum position size in shares

limit
integer
default:100

Results per page (1-500, default 100)

Required range: 1 <= x <= 500
order
enum<string>
default:desc

Sort direction

Available options:
desc,
asc
pagination_key
string

Cursor from a previous response to fetch the next page

Response

Position results with pagination