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.

This JSON endpoint is unchanged by the HTML product selector: it remains the Polynion Market Portfolio contract below and does not gain Polyflight product or ticket pagination controls.

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 ${OPERATOR_API_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 prefers succeeded wallet-credit amounts for processed auto-redemptions, falling back to the stored item amount when no succeeded credit is found. Current or pending winning shares use projected payout rules: Credits flooring, current FX, and the operator FX fee. Stored processed amounts are not revalued at current FX, but their presence alone does not prove reconciliation. *_resolved_pnl_minor is resolution payout minus gross cost; a losing outcome's PnL is 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.
  • Explicit cost fields use historical processed-order money where available; positions/trades without matching historical order evidence fall back to current-FX conversions. Portfolio PnL is therefore a position-oriented view, not the confirmed-wallet-cash report from Market PnL.
  • 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 direction-signed operation amount: debit and credit_reversal are negative; credit and debit_reversal are positive. Check status: planned, failed, or unknown operations do not prove an actual balance change. 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.

Portfolio positions, Market trade history, cost totals, and metrics apply current operator market eligibility (ownership, geo, visibility, and resolution horizon). Hidden or otherwise ineligible markets can disappear from these views. Use the dedicated trade/report endpoints for historical reconciliation rather than treating a Portfolio total as an all-history cash ledger.

The JSON balance projection retains its legacy behavior: it does not accept a product filter and can include unlinked/Polyflight operations as operation_type: "unknown" with market: null. Do not infer Opinion-only cash from it. HTML views separate the two products; use Balance operations for explicit product-filtered JSON reporting.

HTML products and ticket accounting

Both HTML endpoints below embed a top-level Polynion Market / Polyflight selector. product selects the initial product; it is optional and defaults to opinion (Polynion Market), preserving the existing Market view. Product and section switching happens locally inside the returned HTML, without browser authentication requests or operator API fetches.

For product: "polyflight", the same tab values select Open Tickets (open), Resolved Tickets (closed), Ticket History (history), and Balance Mutation (balance):

  • Open Tickets shows admitted, active tickets. Failed or unconfirmed purchase attempts are not active tickets and do not inflate active stake.
  • Resolved Tickets separates the terminal game result (won, lost, or refunded) from wallet delivery state. A final win with an owed payout is still a resolved win; a loss acknowledgement can have a zero wallet amount.
  • Ticket History shows the user's admitted tickets only. Failed or unconfirmed purchase attempts do not appear in any ticket list or ticket count; their operations appear only in Balance Mutation.
  • Ticket tables show exact gross stake, fee, net stake, and potential payout. Potential payout is a ticket's stored possible outcome amount, not a confirmed credit.
  • The summary shows active stake, lifetime stake, confirmed credits, pending credits, and realized result PnL. Active stake is native IDR committed to active tickets, not a fictional resale value or Market portfolio valuation. Realized result PnL describes terminal ticket results separately from whether their wallet credits have been delivered.
  • Balance Mutation shows native-IDR wallet operations and distinguishes confirmed effects from pending or unknown effects. An unknown outcome is not proof that a debit or credit succeeded; neither a ticket result nor a pending obligation should be treated as a confirmed wallet balance change.

Polyflight money remains native IDR: minor-unit amounts are exact decimal integer strings, not floating-point JSON numbers. Preserve that precision, including values above JavaScript's safe integer range. Stake, fee, net stake, potential payout, and historical wallet amounts are stored historical amounts, not current-FX conversions or estimates of resale value.

Summary formulas are independent of the displayed page: active stake sums purchased ticket stakes; lifetime stake sums all admitted ticket stakes; confirmed credits sum paid win/refund obligations; pending credits sum win/refund obligations not yet paid. Realized result PnL sums a won ticket's authoritative payout minus its gross stake, subtracts lost stakes, and assigns zero to refunded tickets. A zero-amount loss acknowledgement contributes no credit. Market FX-provider unavailability is shown in the Market section without blocking native-IDR Polyflight history; database failures remain errors.

ticket_page and ticket_page_size independently page each of Open Tickets, Resolved Tickets, and Ticket History; balance_page and balance_page_size independently page each product's wallet list. Existing trade pagination and sorting continue to apply to Polynion Market.

Ticket pagination accepts only whole, safe positive integers: digit strings in GET, and numeric integers or digit strings in POST. Fractional values, malformed strings, booleans, and arrays return 400 INVALID_TICKET_PAGE or 400 INVALID_TICKET_PAGE_SIZE before user lookup, host resolution, or launch effects. Valid values above the configured page/page-size limit are capped at that limit.

Historical Polyflight data remains readable when the product is disabled. Disabled or otherwise unavailable products have no launch CTA. Both HTML views use user-facing projections: operator terms, share BPS, and private wallet or proof evidence are not exposed.

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 launch CTA links or buttons; the Market Open table omits the entire Action column
  • adds a distinct Market ID header and plain integer cell to Market Open, Resolved, Trade History, and Balance Mutation
  • keeps market titles, Market IDs, and Polyflight flight and ticket IDs as plain text, never launch links

Query params

ParameterTypeRequirementValues/default
external_user_idstringRequiredStable operator-side user ID owned by the authenticated operator.
productstringOptionalopinion (default, Polynion Market) or polyflight; selects the initial embedded product.
tabstringOptionalopen (default), closed, history, or balance.
trade_pagepositive integerOptionalDefault 1, maximum 10000.
trade_page_sizepositive integerOptionalDefault 20, maximum 100.
ticket_pagepositive integerOptionalDefault 1, maximum 10000; applies independently to each Polyflight ticket list.
ticket_page_sizepositive integerOptionalDefault 20, maximum 100; applies independently to each Polyflight ticket list.
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 "product=polyflight" \
  --data-urlencode "tab=history" \
  --data-urlencode "ticket_page=1" \
  --data-urlencode "ticket_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_PRODUCT, INVALID_TAB, INVALID_LOCALE, or INVALID_TICKET_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 Market Trade or Polyflight flight 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. It includes the embedded Polynion Market / Polyflight selector described above. The default Polynion Market view keeps 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.

The Polyflight sections use the native-IDR accounting described above. Flight and ticket IDs remain plain text. Flight launch links are generated only when Polyflight is enabled for the authenticated IDR operator; unavailable-product CTAs are absent, while historical tickets remain readable. Each Polyflight link is single-use, uses the same short TTL as POST /api/operator/launch, and is bound to the intended flight through launch metadata: product: "polyflight", the operator's enabledProducts, and the flight-specific redirectPath. Existing Market Trade links and sections retain their behavior.

Request body

{
  "launch_parameters": {
    "external_user_id": "operator-user-123",
    "display_name": "Rina",
    "locale": "id",
    "metadata": {}
  },
  "product": "opinion",
  "tab": "open",
  "trade_page": 1,
  "trade_page_size": 20,
  "ticket_page": 1,
  "ticket_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
productstringOptionalopinion (default, Polynion Market) or polyflight; selects the initial embedded product.
tabstringOptionalopen (default), closed, history, or balance.
trade_pagepositive integerOptionalDefault 1, maximum 10000.
trade_page_sizepositive integerOptionalDefault 20, maximum 100.
ticket_pagepositive integerOptionalDefault 1, maximum 10000; applies independently to each Polyflight ticket list.
ticket_page_sizepositive integerOptionalDefault 20, maximum 100; applies independently to each Polyflight ticket list.
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": {}
    },
    "product": "polyflight",
    "tab": "history",
    "ticket_page": 1,
    "ticket_page_size": 20
  }' \
  "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.
  • Market Trade links and enabled-IDR Polyflight flight 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. Product switching itself is local and does not make browser authentication or operator API requests.
  • 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 available Market Trade and Polyflight flight links only as normal new tabs. The embedded product selector works locally within that frame. This preview is an operator troubleshooting aid; backend integrations must continue to call the endpoint server-side.

Copyright © 2026