Skip to content
Get an API key

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:

State Meaning
public Readable without the account holder’s credentials
owner_auth_required Needs the account owner’s own authentication
derived Reconstructed by Predictefy rather than served by the venue
not_supported The 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, each taking limit and cursor.

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,kalshi" \
-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.

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: positions derived from orders you placed through Predictefy. Its rows are ExecDerivedPositionvenue, marketId, outcomeId, netSize — and nothing else. It is deliberately narrow.

They answer different questions and will legitimately disagree: the account surface sees everything on the venue, the execution surface sees only what came through us. Comparing them and calling the difference a bug is a misreading. Pick the one that matches the question you are answering, and say which one your UI is showing.

/v1/exec/{venue}/balance is likewise the execution service’s view, not the venue’s account balance.

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.