Skip to content

GuidesCookbook

Positions live on venues, not on Predictefy. Reading them across venues means asking each one what it will actually serve before asking it for anything.

getAccountCapabilities is the call that comes before the others. It reports, per resource, what this venue can do:

Terminal window
curl -s "$PREDICTEFY_API_URL/v1/accounts/polymarket/capabilities" \
-H "Authorization: Bearer pk_live_YOUR_KEY"

It returns four resources — balances, positions, openOrders, fills — each with a state, a served flag and notes. state is one of:

StateMeaning
publicReadable without the account holder’s credentials
owner_auth_requiredNeeds the account owner’s own authentication
derivedReconstructed by Predictefy rather than served by the venue
not_supportedThe venue does not expose it at all

Branch on this. not_supported is a correct answer about the world — do not retry it, and do not substitute another venue’s number. derived is real but is our reconstruction, not the venue’s statement, and should be labelled as such wherever a user sees it.

Terminal window
curl -s "$PREDICTEFY_API_URL/v1/accounts/polymarket/ACCOUNT_ID/positions?limit=100" \
-H "Authorization: Bearer pk_live_YOUR_KEY"

The path carries both the venue and the account id — /v1/accounts/{venue}/{accountId}/…. The same shape serves /balances, /positions, /open-orders and /fills. All four take limit, but only positions, open orders, and fills take cursor. Balances are not paginated; sending a cursor returns 400 VALIDATION_ERROR.

A position row:

{
"venue": "polymarket",
"accountId": "0xabc…",
"positionId": "pos_18f2",
"marketId": "0x7d3f…",
"canonicalMarketId": "polymarket:0x7d3f…",
"outcomeId": "0x91aa…",
"side": "yes",
"size": 250,
"avgEntryPrice": 0.41,
"markPrice": 0.44,
"unrealizedPnl": 7.5,
"realizedPnl": 0,
"status": "open",
"asOf": "2026-08-12T14:31:04.882Z"
}

Everything above raw is required and normalized identically across venues, so one renderer works everywhere. raw carries the venue’s own payload when you need something the normalized shape does not cover.

Note both id forms: marketId is the venue’s, canonicalMarketId is {venue}:{marketId} and is what you use as a cross-venue key. See Identifiers.

getPortfolio rolls this up for one address:

Terminal window
curl -s "$PREDICTEFY_API_URL/v1/portfolio?address=0xabc…&venues=polymarket,limitless" \
-H "Authorization: Bearer pk_live_YOUR_KEY"

It takes address and an optional venues filter, and returns venues keyed by venue — each with its own balances and positions — plus totals carrying markValueUsd and a byVenue breakdown, and an asOf for the whole snapshot.

The filter is limited to the venues that serve public balances or positions: hyperliquid, limitless, myriad, opinion, polymarket, predictfun, rain, and sxbet. Omit venues for all eight. Naming any other venue — kalshi, for one — returns 400 VALIDATION_ERROR rather than a silently short answer.

Use totals.markValueUsd for a headline number and totals.byVenue when you need to show where the value sits. The top-level asOf covers the aggregate; individual rows carry their own.

This is the mistake worth avoiding.

  • /v1/accounts/{venue}/… is the hosted account surface: what a venue reports about an account, read-only.
  • /v1/exec/{venue}/positions is the execution service surface: native positions when the lane implements them, otherwise positions derived from orders placed through Predictefy. Derived rows carry venue, marketId, outcomeId, and netSize, with meta.derivation: fills.

The fills-derived execution view sees only orders placed through Predictefy and can differ from venue-native account state. Label which source your UI shows. /v1/exec/{venue}/balance returns a native balance when the lane supports it, otherwise 501 NOT_SUPPORTED.

For caller-authenticated reads, use POST /v1/exec/{venue}/positions or POST /v1/exec/{venue}/balance on https://exec.predictefy.com with a trade-scoped platform key. PRED takes { owner, signer, accessToken } in the JSON body; balance also accepts optional marketId and parentMarketId. These read-only POSTs need no Idempotency-Key, and venue credentials are used once without logging or persistence. The venue lane must be armed.

async function venueSnapshot(venue, accountId) {
const caps = await api(`/v1/accounts/${venue}/capabilities`);
if (caps.data.positions.state === 'not_supported') {
// Honest gap. Render it as unavailable, not as zero positions.
return { venue, positions: null, reason: 'not_supported' };
}
const positions = await api(`/v1/accounts/${venue}/${accountId}/positions`, { limit: 100 });
return {
venue,
positions: positions.data,
derived: caps.data.positions.state === 'derived',
};
}

An empty array means “ran and found nothing”. not_supported means “cannot run here”. Rendering the second as the first tells the user they hold no positions on a venue where we simply cannot look — see Capability-honest data.

Account reads are per venue per resource, so a portfolio across five venues is at least five calls before pagination. getPortfolio is one call for the aggregate and is cheaper than assembling it yourself when you only need totals. Current weights are in Credits & billing.