OpenPoly logo
Operator Api

Users

Query operator users created through the Mini App integration.

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.

ParameterTypeRequirementValues/default
statusstringOptionalactive, suspended, or disabled.
searchstringOptionalCase-insensitive match against external user ID or display name.
external_user_idstringOptionalExact external user ID.
created_fromdatetimeOptionalInclusive lower bound; any JavaScript-parseable date/time.
created_todatetimeOptionalInclusive upper bound; any JavaScript-parseable date/time.
sortstringOptionalcreated_at_desc (default), created_at_asc, or external_user_id_asc.
pagepositive integerOptionalDefault 1.
page_sizepositive integerOptionalDefault 50, maximum 100.

Response fields

  • id
  • external_user_id
  • polynion_user_id
  • display_name
  • status
  • metadata
  • last_seen_at
  • created_at
  • updated_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

ParameterTypeRequirementValues/default
external_user_idstringRequiredStable operator-side user ID.
trade_pagepositive integerOptionalDefault 1, maximum 10000.
trade_page_sizepositive integerOptionalDefault 20, maximum 100.
balance_pagepositive integerOptionalDefault 1, maximum 10000.
balance_page_sizepositive integerOptionalDefault 20, maximum 100.
trade_sort_bystringOptionalcreatedAt (default), sharesDelta, or creditsDelta.
trade_sort_directionstringOptionaldesc (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:

  • positions contains every operator-visible position used for Portfolio totals. open_positions and resolved_positions are 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_minor is 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_minor uses 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_minor is resolution payout minus gross cost; therefore a losing outcome's PnL is exactly the negative gross cost. Open-only and resolved-only PnL fields are null outside their applicable market state.
  • yes_cost_minor / no_cost_minor remain 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 converted amount_minor, gross_cost_minor, fee_minor, net_cost_minor, raw fee_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_minor is proceeds before fee and net_cost_minor is the smaller proceeds amount actually credited after fee.
  • balance_mutations is 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, derived operation_type, direction, status, signed amount_minor, currency, and timestamp.
  • operation_type is trade when the operation has an order and resolution when it has a redemption item. No separate persisted type is required.
  • balance_mutations.items[].amount_minor is the actual user balance effect: debit and credit_reversal are negative; credit and debit_reversal are positive. Currency comes from currency_code.
  • All *_minor fields use currency_code. Share fields use decimal shares. BPS fields use basis points. Fields ending in _credits remain Polynion Credits.
  • wallet.available: false means the balance could not be refreshed. Check wallet.unavailable_reason; do not treat balance_minor: null as zero.
  • Responses include Cache-Control: no-store and Vary: Authorization.
  • 404 OPERATOR_USER_NOT_FOUND means 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 Action column
  • adds a distinct Market ID header 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

ParameterTypeRequirementValues/default
external_user_idstringRequiredStable operator-side user ID owned by the authenticated operator.
tabstringOptionalopen (default), closed, history, or balance.
trade_pagepositive integerOptionalDefault 1, maximum 10000.
trade_page_sizepositive integerOptionalDefault 20, maximum 100.
balance_pagepositive integerOptionalDefault 1, maximum 10000.
balance_page_sizepositive integerOptionalDefault 20, maximum 100.
trade_sort_bystringOptionalcreatedAt (default), sharesDelta, or creditsDelta.
trade_sort_directionstringOptionaldesc (default) or asc.
localestringOptionalen, 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_ID means external_user_id is missing, blank, or invalid.
  • 404 OPERATOR_USER_NOT_FOUND means 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 example INVALID_TAB, INVALID_LOCALE, or INVALID_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:

  • Open
  • Resolved
  • Trade History
  • Balance 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 Trade buttons 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 no Action column
  • 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 renders DD Month YYYY, HH:mm:ss in 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 fieldTypeRequirementNotes
external_user_idstringRequiredStable operator-side user ID. The endpoint upserts this operator user before rendering.
display_namestringOptionalLatest display-name snapshot.
localestringOptionalDefault Portfolio locale. Use en, id, es, or zh-CN.
hoststringOptionalMust be active and allowed for the operator. Omit unless the Provider assigned one.
metadataobjectOptionalMerged into operator-user metadata.

View options:

FieldTypeRequirementValues/default
tabstringOptionalopen (default), closed, history, or balance.
trade_pagepositive integerOptionalDefault 1, maximum 10000.
trade_page_sizepositive integerOptionalDefault 20, maximum 100.
balance_pagepositive integerOptionalDefault 1, maximum 10000.
balance_page_sizepositive integerOptionalDefault 20, maximum 100.
trade_sort_bystringOptionalcreatedAt (default), sharesDelta, or creditsDelta.
trade_sort_directionstringOptionaldesc (default) or asc.
localestringOptionalOverrides 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.
  • Trade links contain single-use launch tokens with the same short TTL as POST /api/operator/launch; refresh the Portfolio HTML when the page is stale.
  • 400 INVALID_LAUNCH_PARAMETERS means the required launch_parameters object is missing or invalid.
  • 400 INVALID_OPERATOR_HOST means launch_parameters.host does 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.

Copyright © 2026