Trades
Trades
List Mini App order and trade records.
Path
GET /api/operator/trades
Required scope: trades:read
Query params
All list query parameters are optional.
| Parameter | Type | Requirement | Values/default |
|---|---|---|---|
status | string | Optional | One of the order statuses below. |
external_user_id | string | Optional | Exact operator-side user ID. |
market_id | positive integer | Optional | Exact market ID. The current handler applies base-10 parseInt before validation, so integrations should send canonical decimal digits only. |
created_from | datetime | Optional | Inclusive order creation-time lower bound. |
created_to | datetime | Optional | Inclusive order creation-time upper bound. |
sort | string | Optional | created_at_desc (default) or updated_at_desc. |
page | positive integer | Optional | Default 1, maximum 10,000. |
page_size | positive integer | Optional | Default 50, maximum 100. |
Invalid filters return 400 INVALID_STATUS, 400 INVALID_MARKET_ID, 400 INVALID_CREATED_FROM, 400 INVALID_CREATED_TO, or 400 INVALID_SORT. Missing page/page_size values, or values that do not parse to a positive base-10 integer, fall back to their defaults; values above their maxima are capped.
Status values
queuedprocessingoperator_debit_pendingoperator_debitedoperator_credit_pendingprocessedfailedcompensation_pendingcompensatedmanual_review
List response
Content type: application/json.
The examples below are illustrative. Field names, nesting, JSON types, nullability, and enum values match the handler contract.
type OrderStatus =
| 'queued'
| 'processing'
| 'operator_debit_pending'
| 'operator_debited'
| 'operator_credit_pending'
| 'processed'
| 'failed'
| 'compensation_pending'
| 'compensated'
| 'manual_review'
type TradeSide = 'buy' | 'sell'
type TradeOutcome = 'yes' | 'no'
type MarketStatus = 'open' | 'halted' | 'in_review' | 'resolved' | 'delisted'
interface Pagination {
page: number
page_size: number
total: number
total_pages: number
}
interface FeeBreakdown {
fxFeeBps: number
operatorFeeBps: number
totalFeeBps: number
}
type JsonValue =
| null
| boolean
| number
| string
| JsonValue[]
| { [key: string]: JsonValue }
interface TradeListOperatorUser {
id: number
external_user_id: string
display_name: string | null
}
interface TradeMarketSummary {
id: number
slug: string
title: string
status: MarketStatus
}
interface TradeListItem {
id: number
idempotency_key: string
status: OrderStatus
side: TradeSide
outcome: TradeOutcome
requested_external_amount_minor: number | null
requested_shares_micro: number | null
quoted_credits_equivalent: number | null
quoted_external_amount_minor: number | null
executed_trade_id: number | null
executed_credits_delta: number | null
executed_shares_delta: number | null
executed_external_amount_minor: number | null
fx_rate_snapshot_id: number | null
fee_breakdown: JsonValue
fee_credits_total: number
operator_fee_bps: number
operator_fee_credits: number
gross_external_amount_minor: number
fee_external_amount_minor: number
net_external_amount_minor: number
operator_fee_external_amount_minor: number
currency_code: string
failure_reason: string | null
created_at: string
updated_at: string
processed_at: string | null
operator_user: TradeListOperatorUser
market: TradeMarketSummary
}
interface TradeListSummary {
fee_credits_total: number
operator_fee_bps: number
operator_fee_credits_total: number
fee_external_amount_minor_total: number
operator_fee_external_amount_minor_total: number
currency_code: string
}
interface TradeListResponse {
summary: TradeListSummary
items: TradeListItem[]
pagination: Pagination
}
TradeListSummary
Summary values use the same filters as the list and aggregate all matching records, not only the current page.
| Field | JSON type | Definition |
|---|---|---|
fee_credits_total | integer | Sum of linked executed trades' total trade fees, in Polynion Credits. Orders without a linked trade contribute 0. |
operator_fee_bps | integer | Authenticated operator's configured share of trade fees, in basis points. 10_000 represents 100%. |
operator_fee_credits_total | integer | Sum of floor(fee_credits_total × operator_fee_bps / 10_000) per linked trade, in Credits. |
fee_external_amount_minor_total | integer | Sum of historical operator-currency equivalents of linked trade fees. |
operator_fee_external_amount_minor_total | integer | Sum of the operator's share of those historical fee equivalents. |
currency_code | string | Authenticated operator's configured currency code. |
TradeListItem
id identifies a Mini App order. It is not the executed trade ID. Use executed_trade_id with the trade-detail endpoint.
| Field | JSON type | Nullable | Definition |
|---|---|---|---|
id | integer | No | Mini App order ID. |
idempotency_key | string | No | Operator-supplied order idempotency key. |
status | string enum | No | Current OrderStatus. |
side | string enum | No | buy or sell. |
outcome | string enum | No | yes or no. |
requested_external_amount_minor | integer | Yes | Stored requested operator-currency amount. Normally populated for buys; may be null for sells or records without an amount request. |
requested_shares_micro | integer | Yes | Stored requested/quoted share quantity in micro-shares. 1_000_000 equals one share. |
quoted_credits_equivalent | integer | Yes | Credits equivalent recorded at quote time. |
quoted_external_amount_minor | integer | Yes | Operator-currency amount recorded at quote time. |
executed_trade_id | integer | Yes | Linked Polynion trade ID after execution. Use this value for GET /api/operator/trades/{tradeId}. |
executed_credits_delta | integer | Yes | Signed Credits balance delta: normally negative for buys and positive for sells. |
executed_shares_delta | integer | Yes | Executed quantity in micro-shares. |
executed_external_amount_minor | integer | Yes | Historical operator-wallet debit/credit amount recorded at execution. |
fx_rate_snapshot_id | integer | Yes | FX snapshot used by the quote/execution. |
fee_breakdown | JSON value | Yes | Stored quote fee configuration. Current producers write FeeBreakdown; legacy/unprocessed records can be null. The route returns the stored JSON without runtime shape validation. |
fee_credits_total | integer | No | Linked trade's total fee in Credits, or 0 before/no execution. |
operator_fee_bps | integer | No | Current authenticated operator fee-share configuration in basis points. |
operator_fee_credits | integer | No | floor(fee_credits_total × operator_fee_bps / 10_000). |
gross_external_amount_minor | integer | No | Signed gross user cash flow before the fee: negative for buys and positive for sells. Sell gross reconstructs the pre-fee amount as the net wallet credit plus the fee. |
fee_external_amount_minor | integer | No | Historical operator-currency equivalent of fee_credits_total, derived proportionally from this order's executed Credits and external amount. Returns 0 when execution data is unavailable or zero. |
net_external_amount_minor | integer | No | Signed net user cash flow after the fee: negative for buys and positive for sells. Buy net is the gross wallet debit less the fee; sell net is the wallet credit recorded at execution. |
operator_fee_external_amount_minor | integer | No | floor(fee_external_amount_minor × operator_fee_bps / 10_000). |
currency_code | string | No | Authenticated operator's configured currency code. |
failure_reason | string | Yes | Stored order failure reason. |
created_at | ISO 8601 string | No | Order creation timestamp. |
updated_at | ISO 8601 string | No | Order update timestamp. |
processed_at | ISO 8601 string | Yes | Successful/final processing timestamp when recorded. |
operator_user | object | No | TradeListOperatorUser. |
market | object | No | TradeMarketSummary. This endpoint-specific projection is smaller than the objects in the Markets API. |
External amount fields are zero-decimal integer amounts in currency_code; do not apply an ISO currency exponent. The *_external_amount_minor name is retained for API consistency. Gross and net amounts use user cash-flow signs (buy < 0, sell > 0), while fee amounts remain non-negative reporting values.
Nested list objects
| Object/field | JSON type | Nullable | Definition |
|---|---|---|---|
FeeBreakdown.fxFeeBps | integer | No | FX conversion fee configured at quote time, in basis points. |
FeeBreakdown.operatorFeeBps | integer | No | Operator fee component captured by the quote writer. The current trade quote path writes 0; it is distinct from top-level operator_fee_bps, which is the operator's revenue share of trade fees. |
FeeBreakdown.totalFeeBps | integer | No | Sum of the fee-breakdown components. |
TradeListOperatorUser.id | integer | No | Internal operator-user mapping ID. |
TradeListOperatorUser.external_user_id | string | No | Exact operator-side user ID. |
TradeListOperatorUser.display_name | string | Yes | Display-name snapshot. |
TradeMarketSummary.id | integer | No | Stable Polynion market ID. |
TradeMarketSummary.slug | string | No | Stable market slug. |
TradeMarketSummary.title | string | No | Source market title. |
TradeMarketSummary.status | string enum | No | Current MarketStatus. |
Pagination
| Field | JSON type | Definition |
|---|---|---|
page | integer | Effective page number after defaulting/capping; always 1..10,000. |
page_size | integer | Effective page size after defaulting/capping. |
total | integer | Total matching orders across all pages. |
total_pages | integer | max(1, ceil(total / page_size)); an empty result reports 1. |
Example
{
"summary": {
"fee_credits_total": 500,
"operator_fee_bps": 1000,
"operator_fee_credits_total": 50,
"fee_external_amount_minor_total": 810,
"operator_fee_external_amount_minor_total": 81,
"currency_code": "IDR"
},
"items": [
{
"id": 9001,
"idempotency_key": "operator-order-123",
"status": "processed",
"side": "buy",
"outcome": "yes",
"requested_external_amount_minor": 81000,
"requested_shares_micro": 12500000,
"quoted_credits_equivalent": 50000,
"quoted_external_amount_minor": 81000,
"executed_trade_id": 501,
"executed_credits_delta": -50000,
"executed_shares_delta": 12500000,
"executed_external_amount_minor": 81000,
"fx_rate_snapshot_id": 77,
"fee_breakdown": {
"fxFeeBps": 0,
"operatorFeeBps": 0,
"totalFeeBps": 0
},
"fee_credits_total": 500,
"operator_fee_bps": 1000,
"operator_fee_credits": 50,
"gross_external_amount_minor": -81000,
"fee_external_amount_minor": 810,
"net_external_amount_minor": -80190,
"operator_fee_external_amount_minor": 81,
"currency_code": "IDR",
"failure_reason": null,
"created_at": "2026-07-10T12:00:00.000Z",
"updated_at": "2026-07-10T12:00:01.000Z",
"processed_at": "2026-07-10T12:00:01.000Z",
"operator_user": {
"id": 42,
"external_user_id": "operator-user-123",
"display_name": "Rina"
},
"market": {
"id": 51,
"slug": "will-indonesia-qualify-for-the-2026-world-cup",
"title": "Will Indonesia qualify for the 2026 World Cup?",
"status": "open"
}
}
],
"pagination": {
"page": 1,
"page_size": 50,
"total": 1,
"total_pages": 1
}
}
Trade detail
GET /api/operator/trades/{tradeId}
Required scope: trades:read
Use the actual executed trade id from the list response's executed_trade_id. Do not pass the Mini App order id; they identify different records.
tradeId is a required positive integer path parameter. The endpoint has no query parameters or request body.
The route is operator-scoped. It returns only a Mini App trade whose trade row and linked order both belong to the authenticated operator. An unknown trade or a trade owned by another operator returns the same 404 TRADE_NOT_FOUND response.
Example
curl -sS \
-H "Authorization: Bearer ***" \
"https://your-beacon-origin.example.com/api/operator/trades/501"
Response
Content type: application/json. Response headers include Cache-Control: no-store and Vary: Authorization.
type TradeStatus = 'queued' | 'processed' | 'failed'
type OperatorUserStatus = 'active' | 'suspended' | 'disabled'
type ResolvedOutcome = 'yes' | 'no'
interface TradeDetailOperatorUser {
id: number
external_user_id: string
polynion_user_id: number
display_name: string | null
status: OperatorUserStatus
}
interface TradeDetailMarket {
id: number
slug: string
title: string
status: MarketStatus
resolved_outcome: ResolvedOutcome | null
}
interface TradeDetailOrder {
id: number
idempotency_key: string
status: OrderStatus
side: TradeSide
outcome: TradeOutcome
requested_external_amount_minor: number | null
requested_shares_micro: number | null
quoted_credits_equivalent: number | null
quoted_external_amount_minor: number | null
executed_trade_id: number
executed_credits_delta: number | null
executed_shares_delta: number | null
executed_external_amount_minor: number | null
fx_rate_snapshot_id: number | null
fee_breakdown: JsonValue
attempt_count: number
failure_reason: string | null
created_at: string
updated_at: string
processed_at: string | null
}
interface TradeDetailResponse {
trade_id: number
order_id: number
idempotency_key: string
status: TradeStatus
side: TradeSide
outcome: TradeOutcome
shares_delta: number
credits_delta: number
avg_price_bps: number
fee_credits_total: number
operator_fee_bps: number
operator_fee_credits: number
source_channel: 'mini_app'
external_currency_code: string | null
external_amount_minor: number | null
fx_rate_snapshot_id: number | null
failure_reason: string | null
created_at: string
processed_at: string | null
operator_user: TradeDetailOperatorUser
market: TradeDetailMarket
order: TradeDetailOrder
}
Response example:
{
"trade_id": 501,
"order_id": 9001,
"idempotency_key": "mini_app_order:9001:trade",
"status": "processed",
"side": "buy",
"outcome": "yes",
"shares_delta": 12500000,
"credits_delta": -50000,
"avg_price_bps": 4000,
"fee_credits_total": 500,
"operator_fee_bps": 1000,
"operator_fee_credits": 50,
"source_channel": "mini_app",
"external_currency_code": "IDR",
"external_amount_minor": 81000,
"fx_rate_snapshot_id": 77,
"failure_reason": null,
"created_at": "2026-07-10T12:00:00.000Z",
"processed_at": "2026-07-10T12:00:01.000Z",
"operator_user": {
"id": 42,
"external_user_id": "operator-user-123",
"polynion_user_id": 987,
"display_name": "Rina",
"status": "active"
},
"market": {
"id": 51,
"slug": "will-indonesia-qualify-for-the-2026-world-cup",
"title": "Will Indonesia qualify for the 2026 World Cup?",
"status": "open",
"resolved_outcome": null
},
"order": {
"id": 9001,
"idempotency_key": "operator-order-123",
"status": "processed",
"side": "buy",
"outcome": "yes",
"requested_external_amount_minor": 81000,
"requested_shares_micro": 12500000,
"quoted_credits_equivalent": 50000,
"quoted_external_amount_minor": 81000,
"executed_trade_id": 501,
"executed_credits_delta": -50000,
"executed_shares_delta": 12500000,
"executed_external_amount_minor": 81000,
"fx_rate_snapshot_id": 77,
"fee_breakdown": {
"fxFeeBps": 0,
"operatorFeeBps": 0,
"totalFeeBps": 0
},
"attempt_count": 1,
"failure_reason": null,
"created_at": "2026-07-10T12:00:00.000Z",
"updated_at": "2026-07-10T12:00:01.000Z",
"processed_at": "2026-07-10T12:00:01.000Z"
}
}
TradeDetailResponse
| Field | JSON type | Nullable | Definition |
|---|---|---|---|
trade_id | integer | No | Executed Polynion trade ID. |
order_id | integer | No | Linked Mini App order ID. |
idempotency_key | string | No | Trade-row idempotency key. This is separate from order.idempotency_key. |
status | string enum | No | Trade status: queued, processed, or failed. |
side | string enum | No | buy or sell. |
outcome | string enum | No | yes or no. |
shares_delta | integer | No | Executed quantity in micro-shares. 1_000_000 equals one share. |
credits_delta | integer | No | Signed Credits delta: normally negative for buys and positive for sells. |
avg_price_bps | integer | No | Average execution price in basis points; 10_000 represents 100%. |
fee_credits_total | integer | No | Total trade fee in Polynion Credits. |
operator_fee_bps | integer | No | Authenticated operator's current fee-share configuration in basis points. |
operator_fee_credits | integer | No | floor(fee_credits_total × operator_fee_bps / 10_000). |
source_channel | string literal | No | Always mini_app; the loader excludes other channels. |
external_currency_code | string | Yes | Currency code recorded on the trade. |
external_amount_minor | integer | Yes | Historical external-wallet debit/credit amount recorded on the trade. |
fx_rate_snapshot_id | integer | Yes | FX snapshot recorded on the trade. |
failure_reason | string | Yes | Trade-row failure reason. |
created_at | ISO 8601 string | No | Trade creation timestamp. |
processed_at | ISO 8601 string | Yes | Trade processing timestamp. |
operator_user | object | No | TradeDetailOperatorUser. |
market | object | No | TradeDetailMarket. |
order | object | No | Full linked TradeDetailOrder. |
TradeDetailOperatorUser
| Field | JSON type | Nullable | Definition |
|---|---|---|---|
id | integer | No | Internal operator-user mapping ID. |
external_user_id | string | No | Exact operator-side user ID. |
polynion_user_id | integer | No | Internal Polynion user ID linked to this operator user. |
display_name | string | Yes | Display-name snapshot. |
status | string enum | No | active, suspended, or disabled. |
TradeDetailMarket
This is the canonical market projection for trade detail and trade-history identity. It intentionally contains fewer fields than Markets API objects.
| Field | JSON type | Nullable | Definition |
|---|---|---|---|
id | integer | No | Stable Polynion market ID. |
slug | string | No | Stable market slug. |
title | string | No | Source market title. |
status | string enum | No | Current MarketStatus. |
resolved_outcome | string enum | Yes | yes, no, or null when unresolved. |
TradeDetailOrder
| Field | JSON type | Nullable | Definition |
|---|---|---|---|
id | integer | No | Mini App order ID; equal to top-level order_id. |
idempotency_key | string | No | Operator-supplied order idempotency key. |
status | string enum | No | Current OrderStatus. |
side | string enum | No | buy or sell. |
outcome | string enum | No | yes or no. |
requested_external_amount_minor | integer | Yes | Stored requested operator-currency amount. |
requested_shares_micro | integer | Yes | Stored requested/quoted quantity in micro-shares. |
quoted_credits_equivalent | integer | Yes | Credits equivalent recorded at quote time. |
quoted_external_amount_minor | integer | Yes | External amount recorded at quote time. |
executed_trade_id | integer | No | Linked executed trade ID. The detail loader requires it to equal top-level trade_id. |
executed_credits_delta | integer | Yes | Signed executed Credits delta. |
executed_shares_delta | integer | Yes | Executed quantity in micro-shares. |
executed_external_amount_minor | integer | Yes | External wallet amount recorded at execution. |
fx_rate_snapshot_id | integer | Yes | FX snapshot recorded on the order. |
fee_breakdown | JSON value | Yes | Stored quote-time data. Current producers write FeeBreakdown; the route returns the stored JSON without runtime shape validation. |
attempt_count | integer | No | Number of processing attempts recorded on the order. |
failure_reason | string | Yes | Order processing failure reason. |
created_at | ISO 8601 string | No | Order creation timestamp. |
updated_at | ISO 8601 string | No | Order update timestamp. |
processed_at | ISO 8601 string | Yes | Order processing timestamp. |
credits_delta, fee_credits_total, operator_fee_credits, order.quoted_credits_equivalent, and order.executed_credits_delta are Polynion Credits. External amount fields are zero-decimal integer amounts labeled by external_currency_code; do not apply an ISO currency exponent.
The detail response does not include fee_external_amount_minor or operator_fee_external_amount_minor. Those historical fee-equivalent fields are available on the list response.
Rendered HTML alternative
GET /api/operator/trades/{tradeId}/view
Required scope: trades:read
This route uses the same operator-scoped loader as the JSON detail endpoint and renders its data as a responsive read-only receipt. It is designed for operator backends and the Dashboard API Playground's isolated HTML Preview.
Unlike the Portfolio HTML view, this endpoint does not create or update users, mint launch tokens, or require launch:write. It is a read-only GET.
curl -sS \
-H "Authorization: Bearer ***" \
"https://your-beacon-origin.example.com/api/operator/trades/501/view" \
--output trade-501.html
The HTML response includes:
- execution summary with side, outcome, external amount, shares, average price, Credits delta, and total fee
- operator user and market identity
- linked order quote and settlement values
- idempotency keys, FX snapshot, timestamps, attempts, and failure reason when present
- escaped fee-breakdown JSON
- responsive mobile and print layouts
Response type: HTML document string, not JSON.
| Response property | Value |
|---|---|
| Status | 200 |
Content-Type | text/html; charset=utf-8 |
Cache-Control | no-store |
Content-Security-Policy | Restricts all sources, framing, forms, images, and styles to the receipt's declared policy. |
X-Robots-Tag | noindex, nofollow |
X-Content-Type-Options | nosniff |
Vary | Authorization |
Both detail routes use the same tenant-scoped loader and return the same validation and not-found errors.
Trade detail errors:
400 INVALID_TRADE_ID:tradeIdis non-positive, malformed, or outside the safe-integer range.404 TRADE_NOT_FOUND: no operator-owned Mini App trade and linked order match.
Market/user trade history HTML
GET /api/operator/trades/{marketId}/{externalUserId}
Required scope: trades:read
This read-only route renders the authenticated operator's processed Mini App trades for one exact operator user and one exact market. It remains available for historical operator-owned trades when that market is no longer currently visible to the operator.
Path parameters:
| Parameter | Type | Requirement |
|---|---|---|
marketId | positive integer | Required |
externalUserId | string, 1–255 characters | Required; URL-encode the value |
Optional pagination uses page (default 1, maximum 10,000) and page_size (default 50, maximum 100). Results are ordered by newest trade first. A valid operator user and market with no matching trades returns 200 with a clear empty-history view.
The endpoint is intended for authenticated operator backends and the Dashboard API Playground. The HTML table includes market, UTC created_at time, side, outcome, human-readable shares converted from micro-shares, and signed zero-decimal external-currency amount. processed_at is loaded but is not rendered as the table time. All dynamic values are escaped. The route does not mutate users, markets, orders, or launch tokens.
Response type: HTML document string, not JSON. Buy amounts render as negative wallet movement; sell amounts render as positive wallet movement. The document also renders the exact operator-user identity, market title/slug/status, and pagination values.
| Response property | Value |
|---|---|
| Status | 200 |
Content-Type | text/html; charset=utf-8 |
Cache-Control | no-store |
Content-Security-Policy | Restrictive inline-document policy. |
X-Robots-Tag | noindex, nofollow |
X-Content-Type-Options | nosniff |
Vary | Authorization |
Errors:
400 INVALID_MARKET_ID400 INVALID_EXTERNAL_USER_ID404 OPERATOR_USER_NOT_FOUND404 MARKET_NOT_FOUND
