GET /positions: Positions Across All Wallets
curl --request GET \
--url https://partner-api.bravadotrade.com/trader-analytics/positionsimport requests
url = "https://partner-api.bravadotrade.com/trader-analytics/positions"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://partner-api.bravadotrade.com/trader-analytics/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://partner-api.bravadotrade.com/trader-analytics/positions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$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://partner-api.bravadotrade.com/trader-analytics/positions"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://partner-api.bravadotrade.com/trader-analytics/positions")
.asString();require 'uri'
require 'net/http'
url = URI("https://partner-api.bravadotrade.com/trader-analytics/positions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodyTrader Data API
GET /positions: Positions Across All Wallets
Position state across every indexed wallet: one wallet, all holders of a market or token, or an incremental state sync. Live marks, honest unknowns, cursor pagination.
GET
/
trader-analytics
/
positions
GET /positions: Positions Across All Wallets
curl --request GET \
--url https://partner-api.bravadotrade.com/trader-analytics/positionsimport requests
url = "https://partner-api.bravadotrade.com/trader-analytics/positions"
response = requests.get(url)
print(response.text)const options = {method: 'GET'};
fetch('https://partner-api.bravadotrade.com/trader-analytics/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://partner-api.bravadotrade.com/trader-analytics/positions",
CURLOPT_RETURNTRANSFER => true,
CURLOPT_ENCODING => "",
CURLOPT_MAXREDIRS => 10,
CURLOPT_TIMEOUT => 30,
CURLOPT_HTTP_VERSION => CURL_HTTP_VERSION_1_1,
CURLOPT_CUSTOMREQUEST => "GET",
]);
$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://partner-api.bravadotrade.com/trader-analytics/positions"
req, _ := http.NewRequest("GET", url, nil)
res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
body, _ := io.ReadAll(res.Body)
fmt.Println(string(body))
}HttpResponse<String> response = Unirest.get("https://partner-api.bravadotrade.com/trader-analytics/positions")
.asString();require 'uri'
require 'net/http'
url = URI("https://partner-api.bravadotrade.com/trader-analytics/positions")
http = Net::HTTP.new(url.host, url.port)
http.use_ssl = true
request = Net::HTTP::Get.new(url)
response = http.request(request)
puts response.read_bodySign each request with a fresh HMAC signature. The cURL and HTTP header values below are placeholders, not reusable credentials. For runnable signing code, use request signing examples. Python examples use the signed session from that page; Node.js examples use its
bravadoFetch helper.- What does this wallet hold? Pass
wallet. - Who holds this market or token? Pass
market_id,condition_idortoken_id. - What changed since I last looked? Pass
updated_afterand follow the cursor.
GET https://partner-api.bravadotrade.com/trader-analytics/positions
null cost, never an estimate), and marks are resolution-aware. The per-wallet /traders/{address}/positions endpoints use a different calculation path (floating-point arithmetic, a fills-based cost fallback, and a separate treatment of some accounting-cohort wallets), so their figures can legitimately differ from this endpoint’s. Do not expect the two to match to the last digit; use this endpoint as the source of record.
A request must be bounded. Send at least one of
wallet, token_id, condition_id, market_id or updated_after. A request with none of them (for example status=open alone) returns 400 with "code": "unbounded_scan".Query parameters
string
default:"polymarket"
polymarket or predictfun. Selects one venue per request; results are never merged across venues. kalshi returns 501 and any other value returns 400, both with "code": "venue_not_captured". See venue semantics.string
Wallet address:
0x followed by 40 hex characters.string
Venue market id. Returns every holder of every outcome token of the market.
string
Condition id:
0x followed by 64 hex characters.string
Outcome token id as a decimal integer string (a 256-bit value; never send it as a JSON number).
string
open or closed. With status=open and no updated_after, open positions are listed only if their live value is at least $1 or their mark is unknown, matching the active_positions count on the trader profile. Unpriced positions are never dropped as worthless.integer
Unix milliseconds. Only positions whose
last_updated_at is at or after this time.integer
Unix milliseconds. Only positions whose
last_updated_at is at or before this time.integer
Only positions whose latest on-chain activity is at or after this block.
integer
Only positions whose latest on-chain activity is at or before this block.
string
USDC decimal with at most 6 decimal places (for example
"25" or "12.5"). Applied to the live current_value. Positions whose mark is unknown cannot pass a value floor and are excluded; they are counted in unpriced_positions. Replaces the $1 status=open admission.integer
Unix milliseconds, exclusive. Returns positions whose state changed after this time. See state sync.
integer
default:"100"
Page size, 1 to 1000. Values outside the range return
400.string
The opaque
next_cursor from the previous page. Send it unchanged with the same filters. A cursor replayed with different filters, or against a different ordering, returns 400 with "code": "cursor_mismatch".min_timestamp, max_timestamp, min_block and max_block all describe the position’s latest activity: its most recent economic event (a fill, split, merge, redemption or settlement). Unknown query parameters and repeated parameters return 400.
Response
Response
{
"data": [
{
"venue": "polymarket",
"wallet": "0x1111111111111111111111111111111111111111",
"market_id": "540816",
"condition_id": "0x2222222222222222222222222222222222222222222222222222222222222222",
"token_id": "1606938044258990275541962092341162602522202993782792835301377",
"outcome": "Yes",
"shares": "250.000000",
"avg_entry_price": "0.420000",
"cost_basis": "105.000000",
"current_price": "0.610000",
"current_value": "152.500000",
"realized_pnl": "-3.250000",
"unrealized_pnl": "47.500000",
"status": "open",
"last_updated_at": 1780001000000,
"mark_source": "quote",
"mark_ts": 1780000990,
"open_value_source": "book"
}
],
"next_cursor": "AQEBq83vEjRWeJD_nNuTfHcEQw",
"served_at": 1780001001234,
"unpriced_positions": 0
}
| Field | Type | Description |
|---|---|---|
data | array | Position rows, in the ordering of the query (see pagination). |
next_cursor | string or null | Pass as cursor for the next page. null on the last page. |
served_at | integer | Unix milliseconds at which the live marks were read. |
unpriced_positions | integer | Open positions with an unknown mark among the rows this response covered, including rows a min_position_value floor excluded. |
Position fields
Money and share fields are exact decimal strings with 6 decimal places, computed from integer micro-units. Parse them with a decimal type. See numeric conventions.| Field | Type | Description |
|---|---|---|
venue | string | polymarket or predictfun. |
wallet | string | Lowercase 0x address. |
market_id | string or null | null when the token is not yet mapped to a market. |
condition_id | string or null | 0x condition id; null when unknown. |
token_id | string | Decimal outcome token id. |
outcome | string or null | Outcome label (for example Yes); null when unlabelled. |
shares | string or null | Shares held now; "0.000000" for closed positions. null when open_value_source is unknown_book_unseeded. |
avg_entry_price | string or null | Remaining cost basis per covered share, in USDC. null when cost is unknown. |
cost_basis | string or null | Remaining cost basis in USDC. null when cost is unknown. Never "0.000000" as a stand-in for unknown. |
current_price | string or null | Live mark in USDC per share, 0 to 1. null when mark_source is unknown. |
current_value | string or null | shares times current_price. null when the mark is unknown. |
realized_pnl | string | Realized PnL in USDC (signed). See venue semantics. |
unrealized_pnl | string or null | On covered shares only: min(shares, covered) × (current_price − cost per covered share). null when the mark or cost is unknown. |
status | string | open or closed. |
last_updated_at | integer | Unix milliseconds of the position’s latest economic event. |
mark_source | string | resolution, quote, trade or unknown. |
mark_ts | integer or null | Unix seconds of the quote or trade the mark rests on. null for resolution and unknown. |
open_value_source | string | book, fifo_lot or unknown_book_unseeded. |
Marks and unknown values
Marks are computed live on every request (responses are not cached), in this order: the market’s resolution payout, then a fresh quote, then the last real trade, then unknown. They are never stored on the position, so a price tick does not changelast_updated_at.
- When no defensible mark exists,
mark_sourceisunknownandcurrent_price,current_valueandunrealized_pnlare JSONnull. The keys are always present. Unknown is never reported as0. - When the cost basis cannot be determined,
cost_basisandavg_entry_pricearenull. - When
open_value_sourceisunknown_book_unseeded(a Polymarket wallet whose on-chain balances have not been indexed yet),shares,cost_basis,avg_entry_price,current_valueandunrealized_pnlare allnull. The row’sstatusis a placeholder in that case; rely onopen_value_source. - A position with
sharesof"0.000000"has acurrent_valueandunrealized_pnlof exactly"0.000000": nothing held is worth nothing, which is known.
Pagination
Results are paged with an opaque keysetcursor. Do not parse or construct it.
- Follow
next_cursoruntil it isnull. A page can hold fewer thanlimitrows whilenext_cursoris still non-null, for example when a value floor discards many candidates. - Send the same filters on every page. Changing
limitbetween pages is allowed. - Row order is the query’s key order, not value order: by token then wallet for market and token queries, by token for a wallet query, and by
last_updated_atforupdated_after. Sort client-side if you need ranking by value.
Sync position state with updated_after
Use updated_after to keep a local copy of position state current.
- Start from
updated_after=0, or from the time you last finished a sync. - Page with
cursoruntilnext_cursorisnull. Each position appears once, at its latest state, even if it changed several times since your last sync. - Remember the largest
last_updated_atyou received. For the next sync, use that value minus a safety margin (for example 15 minutes) asupdated_after, and apply rows idempotently by(wallet, token_id), keeping the row with the greaterlast_updated_at.
last_updated_at is the on-chain time of the position’s latest event, not the time the row became visible to the API. A row for a slightly older block can therefore appear after you have already seen a newer one; the overlap makes that harmless, and the idempotent apply makes the re-read free.
updated_after syncs state, not prices. A position row changes only when the wallet’s position changes on-chain. current_price, current_value and unrealized_pnl are always as of served_at; to re-price positions you already hold, query them again with token_id (all holders of that token) or wallet. The change feed does not drop positions that decay to dust, so status=open admission is not applied on this axis.
Venue semantics
| Polymarket | predict.fun (venue=predictfun) | |
|---|---|---|
shares source | On-chain balanceOf book (open_value_source is book) | FIFO lot state (open_value_source is fifo_lot); there is no balanceOf book |
unknown_book_unseeded | Possible | Never |
mark_source | resolution, quote, trade, unknown | resolution, quote, unknown (no trade tier) |
realized_pnl | As the PnL engine books it for the wallet | Net of fees |
open_shares and resolved_unredeemed_shares, which decompose a balanceOf balance on the per-wallet endpoints, are not part of this response on either venue.
Partner keys need the venue enabled for the request: venue=predictfun requires predictfun in your enabled venues (otherwise 403 VENUE_NOT_ENABLED). The same data is available on the predict.fun mount, GET /trader-analytics/predict/positions, where venue defaults to predictfun.
Errors
Errors return{"error": "...", "code": "..."}.
| Status | code | Meaning |
|---|---|---|
| 400 | unbounded_scan | None of wallet, token_id, condition_id, market_id, updated_after was sent. |
| 400 | invalid_wallet, invalid_token_id, invalid_condition_id, invalid_market_id, invalid_status, invalid_limit, invalid_min_position_value, invalid_range | A parameter failed validation. |
| 400 | unknown_parameter, duplicate_parameter | An unsupported or repeated parameter. |
| 400 | invalid_cursor, cursor_mismatch | The cursor is malformed, or was issued for different filters or ordering. |
| 400 / 501 | venue_not_captured | Unknown venue (400) or kalshi (501). |
Example
curl "https://partner-api.bravadotrade.com/trader-analytics/positions?market_id=540816&status=open&limit=500" \
-H "X-BRAVADO-API-KEY: $BRAVADO_API_KEY" \
-H "X-BRAVADO-TIMESTAMP: <unix-ms>" \
-H "X-BRAVADO-SIGNATURE: <signature-for-this-request>"
curl "https://partner-api.bravadotrade.com/trader-analytics/positions?wallet=0x1111111111111111111111111111111111111111" \
-H "X-BRAVADO-API-KEY: $BRAVADO_API_KEY" \
-H "X-BRAVADO-TIMESTAMP: <unix-ms>" \
-H "X-BRAVADO-SIGNATURE: <signature-for-this-request>"
curl "https://partner-api.bravadotrade.com/trader-analytics/positions?updated_after=1780000000000&limit=1000" \
-H "X-BRAVADO-API-KEY: $BRAVADO_API_KEY" \
-H "X-BRAVADO-TIMESTAMP: <unix-ms>" \
-H "X-BRAVADO-SIGNATURE: <signature-for-this-request>"