OpenPoly logo
Operator Api

Market PnL

Read resolved-market user cash PnL and historical quoted fees.

Market PnL

GET /api/operator/market-pnl

Requires both trades:read and redemptions:read, or *. Operator API keys and staff sessions are accepted. The operator is derived from authentication; operatorId and operator_id query parameters return 400 OPERATOR_SELECTOR_NOT_ALLOWED.

This read-only Opinion report groups actual wallet cash by operator user and resolved market, including users who sold their entire position before resolution. It is not position valuation, unpaid winning-share entitlement, operator profit, or monthly settlement. Public markets and the operator's own private markets are eligible; other operators' private markets are excluded.

Filters and pagination

All parameters are optional. There is no default date cutoff.

ParameterValues/default
market_idExact positive integer market ID, maximum 2147483647; decimal digits only.
external_user_idExact operator external user ID; URL-encode it. Leading/trailing query whitespace is trimmed.
searchCase-insensitive literal substring of market title or slug.
resolved_from_dateInclusive YYYY-MM-DD lower bound in the operator timezone.
resolved_to_dateInclusive YYYY-MM-DD upper bound; implemented as exclusive next-day local midnight.
sortresolved_at_desc (default), resolved_at_asc, pnl_desc, pnl_asc, spent_desc, returned_desc.
pageDefault 1, maximum 10000.
page_sizeDefault 25, maximum 100.

Dates select markets, not individual wallet movements. All lifetime qualifying cash for a selected market contributes. resolved_at currently uses the market update timestamp, not a dedicated immutable resolution timestamp; metadata edits can change the date selection. Reversed date bounds return 400 INVALID_RESOLVED_DATE_RANGE. Invalid dates, ID, or sort return their INVALID_* code. Pagination uses the standard defaulting/capping rules.

curl -sS -G "$OPERATOR_API_BASE_URL/api/operator/market-pnl" \
  -H "Authorization: Bearer $OPERATOR_API_KEY" \
  --data-urlencode "market_id=51" \
  --data-urlencode "external_user_id=operator-user-123" \
  --data-urlencode "page_size=25"

Response

The envelope is { items, summary, pagination }. pagination contains numeric page, page_size, total, and total_pages (at least 1). total counts user/market groups, not trades.

Each item contains:

  • operator_user: numeric id, external_user_id, nullable display_name;
  • market: numeric id, slug, title;
  • resolved_at: timestamp or null; outcome: resolved outcome string;
  • currency_code (currently IDR for Opinion wallet cash);
  • the amounts and warning counts below.
FieldJSON typeMeaning
spent_minorinteger stringSucceeded debits minus succeeded debit reversals, including buy fees.
sales_returned_minorinteger stringSucceeded order credits minus succeeded order credit reversals.
redemption_returned_minorinteger stringSucceeded redemption credits minus succeeded redemption credit reversals.
returned_minorinteger stringSale returns plus redemption returns.
pnl_minorsigned integer stringUser cash PnL: returned minus spent. Positive means the user received more than they spent.
quoted_fee_creditsinteger stringAccumulated linked executed-trade fees in Polynion Credits, once per order.
quoted_fee_minorinteger stringThe same fees converted per order using historical external/credit amounts and flooring, then accumulated.
pending_operation_countintegerUnresolved wallet operations or source-level work awaiting reconciliation.
failed_payout_countintegerSource records with a failed credit/refund and a remaining positive unpaid amount; not the count of individual wallet attempts.
failed_payout_minorinteger stringRemaining failed/unpaid credit or refund amount.

All *_minor values are exact decimal integer strings in currency_code; IDR is zero-decimal. Preserve precision with integer/string arithmetic. quoted_fee_credits is a different unit (10000 Credits = 1 USDT) and must not be added to currency amounts.

summary repeats every amount, warning count, and currency_code across all filtered groups, plus numeric user_market_count, user_count, and market_count, and string timezone. It is not limited to the current page. Responses use Cache-Control: private, no-store.

Cash versus quoted fees

Only succeeded wallet operations enter cash amounts. Retries that reuse one operation do not create another movement, but distinct successful operations are each counted, even if they represent an accidental duplicate payment. Source status does not erase confirmed cash. Reversals offset the corresponding cash bucket, including compensated buys.

Both quoted-fee columns are reference-only. They come from executed trades independently of wallet-operation count or reconciliation state; they are not additional wallet debits and must not be subtracted again from pnl_minor. Historical fee conversion uses the order's executed external amount, falling back to quoted/requested amount, divided by absolute executed Credits (or quoted Credits when execution Credits are unavailable), with per-order flooring. It never uses today's FX rate. Missing fee evidence contributes zero rather than proving that no fee was charged.

Pending or ambiguous work is not cash until confirmed. A failed/unpaid amount is not automatically a debt write-off or an applied payout. A zero pending count does not prove that every winning holding has been redeemed. PnL can change after resolution as wallet operations and reversals complete.

Use cash-flow triage for warning sources and redacted evidence. It additionally requires balance_operations:read. Use Resolutions for market exposure and Opinion Settlement Reports for monthly operator accounting; these reports answer different questions.

Copyright © 2026