Users
Users
List operator users and retrieve a user's Portfolio as JSON, read-only inspection HTML, or launch-capable HTML.
Path
GET /api/operator/users
Required scope: users:read
Query params
All list query parameters are optional.
| Parameter | Type | Requirement | Values/default |
|---|---|---|---|
status | string | Optional | active, suspended, or disabled. |
search | string | Optional | Case-insensitive match against external user ID or display name. |
external_user_id | string | Optional | Exact external user ID. |
created_from | datetime | Optional | Inclusive lower bound; any JavaScript-parseable date/time. |
created_to | datetime | Optional | Inclusive upper bound; any JavaScript-parseable date/time. |
sort | string | Optional | created_at_desc (default), created_at_asc, or external_user_id_asc. |
page | positive integer | Optional | Default 1. |
page_size | positive integer | Optional | Default 50, maximum 100. |
Response fields
idexternal_user_idpolynion_user_iddisplay_namestatusmetadatalast_seen_atcreated_atupdated_at
Portfolio JSON data
GET /api/operator/users/portfolio
Required scope: users:read
Use this read-only endpoint when your backend needs the Portfolio data without HTML or launch links. It does not create or update the operator user, and it does not create launch tokens.
Query params
| Parameter | Type | Requirement | Values/default |
|---|---|---|---|
external_user_id | string | Required | Stable operator-side user ID. |
trade_page | positive integer | Optional | Default 1, maximum 10000. |
trade_page_size | positive integer | Optional | Default 20, maximum 100. |
balance_page | positive integer | Optional | Default 1, maximum 10000. |
balance_page_size | positive integer | Optional | Default 20, maximum 100. |
trade_sort_by | string | Optional | createdAt (default), sharesDelta, or creditsDelta. |
trade_sort_direction | string | Optional | desc (default) or asc. |
Example
curl -sS \
-H "Authorization: Bearer ${OPER...KEY}" \
"https://your-beacon-origin.example.com/api/operator/users/portfolio?external_user_id=operator-user-123&trade_page=1&trade_page_size=20&balance_page=1&balance_page_size=20"
Response
{
"operator_user": {
"id": 42,
"external_user_id": "operator-user-123",
"polynion_user_id": 987,
"display_name": "Rina",
"status": "active"
},
"generated_at": "2026-07-10T12:00:00.000Z",
"currency_code": "IDR",
"wallet": {
"provider": "rest_v1",
"currency_code": "IDR",
"balance_minor": 250000,
"available": true,
"refreshed_at": "2026-07-10T12:00:00.000Z"
},
"portfolio_value": {
"positions_count": 0,
"total_value_minor": 0,
"realized_pnl_minor": 0,
"unrealized_pnl_minor": 0
},
"metrics": {
"markets_predicted": 0,
"win_rate_pct": 0,
"lifetime_volume_minor": 0
},
"positions": [],
"open_positions": [],
"resolved_positions": [],
"trade_history": {
"items": [],
"pagination": {
"page": 1,
"page_size": 20,
"total": 0,
"total_pages": 1
},
"sort": {
"by": "createdAt",
"direction": "desc"
}
},
"balance_mutations": {
"items": [
{
"id": 4321,
"market": {
"id": 123,
"slug": "example-market",
"title": "Will this market resolve Yes?",
"translations": {}
},
"operation_type": "trade",
"direction": "debit",
"status": "succeeded",
"amount_minor": -50000,
"currency_code": "IDR",
"created_at": "2026-07-10T11:59:00.000Z"
}
],
"pagination": {
"page": 1,
"page_size": 20,
"total": 1,
"total_pages": 1
}
}
}
Response details:
positionscontains every operator-visible position used for Portfolio totals.open_positionsandresolved_positionsare convenience subsets of the same objects.- Position objects include market identity/status, labels/translations, shares, redeemed shares, BPS prices, raw
yes_net_credits/no_net_credits, and operator-currency cost/value/PnL fields. - Use the explicit per-outcome cost fields:
*_gross_cost_minor,*_fee_minor, and*_net_cost_minor. Gross cost is processed Mini App buy gross debits minus gross sell proceeds before fee. Fee is always non-negative. Net cost aggregates each trade after its fee: buy net is gross debit minus fee, while sell net is gross proceeds minus fee and remains negative in cost accounting. Because buy and sell fees move the signed net in opposite directions, mixed-flow aggregate net cost is not simply aggregate gross cost minus total fee. A negative gross/net cost means cumulative sell proceeds exceed buy costs. metrics.lifetime_volume_minoris the absolute sum of historical gross costs before fee across the operator user's processed Mini App trades. It uses stored operator-currency amounts and does not reconvert Credits at the current FX rate.*_resolution_payout_minoruses the actual operator-currency amount credited for processed auto-redemptions. Current or pending winning shares use the same projected payout rules as the redemption worker: Credits flooring, current FX, and the operator FX fee. Processed payouts are never revalued at current FX.*_resolved_pnl_minoris resolution payout minus gross cost; therefore a losing outcome's PnL is exactly the negative gross cost. Open-only and resolved-only PnL fields arenulloutside their applicable market state.yes_cost_minor/no_cost_minorremain backward-compatible legacy current-FX conversions. New integrations should use the explicit gross/fee/net fields.- Trade items include market context, side/outcome, decimal
shares_delta, legacy convertedamount_minor,gross_cost_minor,fee_minor,net_cost_minor, rawfee_credits_total, status, and timestamp. Trade cost fields use the same sign convention as positions: buys are positive costs; sells are negative proceeds. For sells,gross_cost_minoris proceeds before fee andnet_cost_minoris the smaller proceeds amount actually credited after fee. balance_mutationsis a bounded, latest-first (created_at DESC, then operation ID descending) projection of balance operations for this operator user. Items expose only operation ID, market identity/title/translations, derivedoperation_type, direction, status, signedamount_minor, currency, and timestamp.operation_typeistradewhen the operation has an order andresolutionwhen it has a redemption item. No separate persisted type is required.balance_mutations.items[].amount_minoris the actual user balance effect:debitandcredit_reversalare negative;creditanddebit_reversalare positive. Currency comes fromcurrency_code.- All
*_minorfields usecurrency_code. Share fields use decimal shares. BPS fields use basis points. Fields ending in_creditsremain Polynion Credits. wallet.available: falsemeans the balance could not be refreshed. Checkwallet.unavailable_reason; do not treatbalance_minor: nullas zero.- Responses include
Cache-Control: no-storeandVary: Authorization. 404 OPERATOR_USER_NOT_FOUNDmeans the external user id does not exist for the authenticated operator. This endpoint never creates it.
Internal Portfolio HTML inspection
GET /api/operator/users/portfolio-view
Required scope: users:read
Use this operator-internal endpoint when a backend needs ready-to-render HTML for inspecting an existing operator user's Portfolio. This is not a public or user-side launch flow. It is strictly read-only:
- identifies an existing operator user by required
external_user_id - never creates or updates an operator user
- never resolves a Mini App launch host
- never creates launch tokens or launch URLs
- renders no CTA links or buttons; the Open table omits the entire
Actioncolumn - adds a distinct
Market IDheader and plain integer cell to Open, Resolved, Trade History, and Balance Mutation - keeps market titles and Market IDs as plain text, never links
Query params
| Parameter | Type | Requirement | Values/default |
|---|---|---|---|
external_user_id | string | Required | Stable operator-side user ID owned by the authenticated operator. |
tab | string | Optional | open (default), closed, history, or balance. |
trade_page | positive integer | Optional | Default 1, maximum 10000. |
trade_page_size | positive integer | Optional | Default 20, maximum 100. |
balance_page | positive integer | Optional | Default 1, maximum 10000. |
balance_page_size | positive integer | Optional | Default 20, maximum 100. |
trade_sort_by | string | Optional | createdAt (default), sharesDelta, or creditsDelta. |
trade_sort_direction | string | Optional | desc (default) or asc. |
locale | string | Optional | en, id, es, or zh-CN; defaults to the operator locale. |
Example
curl -sS -G \
-H "Authorization: Bearer ${OPERATOR_API_KEY}" \
--data-urlencode "external_user_id=operator-user-123" \
--data-urlencode "tab=open" \
--data-urlencode "trade_page=1" \
--data-urlencode "trade_page_size=20" \
"https://your-beacon-origin.example.com/api/operator/users/portfolio-view" \
-o portfolio-inspection.html
Response headers:
Content-Type: text/html; charset=utf-8
Cache-Control: no-store
X-Robots-Tag: noindex, nofollow
Vary: Authorization
Errors and integration rules
400 INVALID_EXTERNAL_USER_IDmeansexternal_user_idis missing, blank, or invalid.404 OPERATOR_USER_NOT_FOUNDmeans that external user does not exist for the authenticated operator. The endpoint does not create it.- Invalid enum or pagination controls return
400 INVALID_<QUERY_PARAMETER>, for exampleINVALID_TAB,INVALID_LOCALE, orINVALID_TRADE_PAGE_SIZE. - Call this endpoint from a trusted backend only. Never expose an operator API key in browser code.
- Do not cache the returned HTML. Stream it to an internal operator surface or render it in a sandboxed frame.
- Do not use this endpoint to launch the Mini App. Use the launch-capable POST endpoint below only when short-lived Trade launch links are required.
Launch-capable Portfolio HTML view
POST /api/operator/users/portfolio-view
Required scopes: users:read and launch:write
This endpoint returns text/html, not JSON. Use it when your backend needs a ready-to-render, view-only Portfolio view for one operator user with the same main sections as the Mini App Portfolio page:
OpenResolvedTrade HistoryBalance Mutation- balance, portfolio value, and portfolio metrics
The rendered view is intentionally not a general navigation surface:
- market titles are plain text, not links
- trade-history market titles are plain text, not links
- open-position
Tradebuttons are single-use Mini App launch links that redirect to the matching market after launch - resolved positions show both the user's bought outcome and the market's winning
Resolution; redemption is automatic, so the Resolved table has noActioncolumn - Open, Resolved, and Trade History show gross cost, fee, and net cost in the authenticated operator's configured currency; resolved PnL uses gross cost
- Balance Mutation shows Timestamp, Market, Type, Status, and Amount. Amount is the signed user balance effect in operator-currency minor units: debits and credit reversals are red/negative; credits and debit reversals are green/positive.
- Balance Mutation timestamps retain their ISO instant in
<time datetime>markup. The inline client script rendersDD Month YYYY, HH:mm:ssin the viewing browser's local timezone; the initial no-script fallback is deterministic UTC text and is not presented as browser-local.
Request body
{
"launch_parameters": {
"external_user_id": "operator-user-123",
"display_name": "Rina",
"locale": "id",
"metadata": {}
},
"tab": "open",
"trade_page": 1,
"trade_page_size": 20,
"balance_page": 1,
"balance_page_size": 20,
"trade_sort_by": "createdAt",
"trade_sort_direction": "desc"
}
launch_parameters is required and uses the same fields as POST /api/operator/launch:
launch_parameters field | Type | Requirement | Notes |
|---|---|---|---|
external_user_id | string | Required | Stable operator-side user ID. The endpoint upserts this operator user before rendering. |
display_name | string | Optional | Latest display-name snapshot. |
locale | string | Optional | Default Portfolio locale. Use en, id, es, or zh-CN. |
host | string | Optional | Must be active and allowed for the operator. Omit unless the Provider assigned one. |
metadata | object | Optional | Merged into operator-user metadata. |
View options:
| Field | Type | Requirement | Values/default |
|---|---|---|---|
tab | string | Optional | open (default), closed, history, or balance. |
trade_page | positive integer | Optional | Default 1, maximum 10000. |
trade_page_size | positive integer | Optional | Default 20, maximum 100. |
balance_page | positive integer | Optional | Default 1, maximum 10000. |
balance_page_size | positive integer | Optional | Default 20, maximum 100. |
trade_sort_by | string | Optional | createdAt (default), sharesDelta, or creditsDelta. |
trade_sort_direction | string | Optional | desc (default) or asc. |
locale | string | Optional | Overrides launch_parameters.locale for the rendered document only. |
Example
curl -sS \
-X POST \
-H "Authorization: Bearer ${OPERATOR_API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"launch_parameters": {
"external_user_id": "operator-user-123",
"display_name": "Rina",
"locale": "id",
"metadata": {}
},
"tab": "balance"
}' \
"https://your-beacon-origin.example.com/api/operator/users/portfolio-view" \
-o portfolio.html
Response headers include:
Content-Type: text/html; charset=utf-8
Cache-Control: no-store
X-Robots-Tag: noindex, nofollow
Vary: Authorization
Integration rules
- Call this endpoint from your backend only. Never expose an operator API key in browser code.
- Stream or inject the returned HTML into your server-rendered page.
- Do not cache the returned HTML; balances, positions, and embedded launch links are user-specific and time-sensitive.
Tradelinks contain single-use launch tokens with the same short TTL asPOST /api/operator/launch; refresh the Portfolio HTML when the page is stale.400 INVALID_LAUNCH_PARAMETERSmeans the requiredlaunch_parametersobject is missing or invalid.400 INVALID_OPERATOR_HOSTmeanslaunch_parameters.hostdoes not belong to the operator.
Dashboard Playground behavior
The Operator Dashboard API Playground keeps the exact response available in Raw and renders Preview only inside a sandboxed iframe srcdoc. Preview injects a restrictive CSP, does not grant allow-same-origin, cannot access dashboard cookies or the parent DOM, and permits Trade links only as normal new tabs. This preview is an operator troubleshooting aid; backend integrations must continue to call the endpoint server-side.
