GuidesCookbook
Monitor a multi-venue portfolio
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.
Ask what is available first
Section titled “Ask what is available first”getAccountCapabilities is the call that comes before the others. It reports, per resource,
what this venue can do:
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.
Read positions and balances
Section titled “Read positions and balances”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.
The aggregate view
Section titled “The aggregate view”getPortfolio rolls this up for one address:
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.
Two surfaces, and they are not the same
Section titled “Two surfaces, and they are not the same”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}/positionsis the execution service surface: native positions when the lane implements them, otherwise positions derived from orders placed through Predictefy. Derived rows carryvenue,marketId,outcomeId, andnetSize, withmeta.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.
Building the monitor
Section titled “Building the monitor”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.
Related
Section titled “Related”- Accounts & funding — the account surface in full
- Trading & execution — the execution service and its own position view
- Capability-honest data — why
not_supportedis an answer