Markets
Markets
List and manage markets for the authenticated operator.
Path
GET /api/operator/markets
GET /api/operator/markets/trending
POST /api/operator/markets/{id}/visibility
Required scopes:
markets:readfor list and trending/widget cardsmarkets:writefor visibility mutation
Query params
All list query parameters are optional.
| Parameter | Type | Requirement | Values/default |
|---|---|---|---|
status | string | Optional | open, halted, in_review, resolved, or delisted. Without this filter, only open, halted, and resolved are returned. |
search | string | Optional | Case-insensitive match against slug or title. |
visibility | string | Optional | visible or hidden. |
sort | string | Optional | updated_at_desc (default) or trading_cutoff_asc. |
page | positive integer | Optional | Default 1, maximum 10,000. |
page_size | positive integer | Optional | Default 50, maximum 100. |
Default list includes open, halted, and resolved markets. Supplying status can also request in_review or delisted when available.
Invalid enum values return 400 INVALID_STATUS, 400 INVALID_VISIBILITY, 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.
Operator geo scope
All operator market APIs are scoped by the operator's configured market geo.
Results include only:
- international markets (
geo_tag: null) - markets tagged with the operator's configured geo, for example
ID
Markets explicitly tagged for a different geo are excluded even if they are globally trending, Admin-featured, allowlisted by ID, or requested through a stale region query. Locale is separate: default_locale controls language defaults, not market access.
Visibility modes
allallowlistblocklist
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 MarketStatus = 'open' | 'halted' | 'in_review' | 'resolved' | 'delisted'
type ResolvedOutcome = 'yes' | 'no'
type MarketGeoTag = 'ID' | 'KR' | 'VN' | 'JP' | 'TH' | 'MY' | 'IN' | 'BR' | 'MX' | 'CO' | 'AU'
interface Pagination {
page: number
page_size: number
total: number
total_pages: number
}
interface MarketListItem {
id: number
slug: string
title: string
geo_tag: MarketGeoTag | null
status: MarketStatus
yes_label: string | null
no_label: string | null
yes_percentage: number
visibility: 'visible' | 'hidden'
open_at: string | null
trading_cutoff_ts: string | null
resolve_deadline_ts: string | null
estimated_resolve_ts: string | null
resolved_outcome: ResolvedOutcome | null
volume_24h: number
updated_at: string
}
interface MarketListResponse {
items: MarketListItem[]
pagination: Pagination
}
MarketListItem
| Field | JSON type | Nullable | Definition |
|---|---|---|---|
id | integer | No | Stable Polynion market ID. |
slug | string | No | Stable market slug. |
title | string | No | Source market title. |
geo_tag | string enum | Yes | Eligible geo tag: ID, KR, VN, JP, TH, MY, IN, BR, MX, CO, or AU. null means international. |
status | string enum | No | open, halted, in_review, resolved, or delisted. |
yes_label | string | Yes | Custom YES-side label. null means no custom label is stored. |
no_label | string | Yes | Custom NO-side label. null means no custom label is stored. |
yes_percentage | number | No | Current YES percentage from AMM state, rounded to two decimal places. Returns 0 if total AMM state is not positive. |
visibility | string enum | No | Effective operator visibility: visible or hidden. |
open_at | ISO 8601 string | Yes | Scheduled/opened-at timestamp. |
trading_cutoff_ts | ISO 8601 string | Yes | Trading cutoff timestamp. |
resolve_deadline_ts | ISO 8601 string | Yes | Resolution deadline. |
estimated_resolve_ts | ISO 8601 string | Yes | Estimated resolution time. |
resolved_outcome | string enum | Yes | yes, no, or null when unresolved. |
volume_24h | integer | No | Operator-scoped processed trade volume over the preceding 24 hours, in Polynion Credits. Always present; 0 means no matching volume. |
updated_at | ISO 8601 string | No | Market update timestamp. |
Pagination
| Field | JSON type | Definition |
|---|---|---|
page | integer | Effective page number after defaulting/capping; always 1..10,000. |
page_size | integer | Effective page size after the default and maximum are applied. |
total | integer | Total matching records across all pages. |
total_pages | integer | max(1, ceil(total / page_size)); therefore an empty result reports 1. |
Example
{
"items": [
{
"id": 51,
"slug": "will-indonesia-qualify-for-the-2026-world-cup",
"title": "Will Indonesia qualify for the 2026 World Cup?",
"geo_tag": "ID",
"status": "open",
"yes_label": "Qualifies",
"no_label": "Does not qualify",
"yes_percentage": 51.29,
"visibility": "visible",
"open_at": "2026-07-09T00:00:00.000Z",
"trading_cutoff_ts": "2026-11-15T12:00:00.000Z",
"resolve_deadline_ts": "2026-11-16T12:00:00.000Z",
"estimated_resolve_ts": null,
"resolved_outcome": null,
"volume_24h": 123456,
"updated_at": "2026-07-10T08:30:00.000Z"
}
],
"pagination": {
"page": 1,
"page_size": 50,
"total": 1,
"total_pages": 1
}
}
Trending market cards
GET /api/operator/markets/trending
Required scope: markets:read.
Query parameters:
| Parameter | Type | Requirement | Values/default |
|---|---|---|---|
amount | positive integer | Optional | Default 5. Values above 20 are capped to 20; non-integers and values below 1 return 400 INVALID_AMOUNT. |
category | string | Optional | Active category slug. Category mode returns category 72h trending markets and does not inject global featured picks. |
Geo is not caller-supplied. The authenticated operator config controls the eligible geo set.
Default ordering:
- Admin featured picks for the operator geo
- Admin featured picks for international markets
- eligible picks from the global/all featured row
- eligible open markets ranked by processed 72h volume
Values above 20 are capped to 20. Invalid non-integers and values less than 1 return 400 INVALID_AMOUNT. An unknown or inactive category returns 400 INVALID_CATEGORY.
Content type: application/json.
type MarketTranslationLocale = 'en' | 'id' | 'zh-CN' | 'es'
interface CategoryOrTagSummary {
id: number
slug: string
name: string
}
interface MarketTranslation {
title: string
description: string
yesLabel: string | null
noLabel: string | null
}
type MarketTranslations = Partial<
Record<MarketTranslationLocale, MarketTranslation>
>
interface TrendingMarketCard {
rank: number
id: number
slug: string
title: string
description: string | null
image_url: string | null
status: 'open'
geo_tag: MarketGeoTag | null
yes_label: string
no_label: string
yes_price_bps: number
no_price_bps: number
yes_percentage: number
no_percentage: number
volume_credits: number
trending_volume_72h_credits: number
categories: CategoryOrTagSummary[]
tags: CategoryOrTagSummary[]
translations: MarketTranslations
open_at: string | null
trading_cutoff_ts: string | null
estimated_resolve_ts: string | null
resolve_deadline_ts: string | null
updated_at: string
market_path: string
}
interface TrendingMarketsResponse {
items: TrendingMarketCard[]
meta: {
source: 'operator_featured_trending' | 'operator_category_trending'
amount: number
category: string | null
operator_geo_tag: MarketGeoTag | null
included_geo_tags: Array<MarketGeoTag | null>
locale: string
generated_at: string
}
}
TrendingMarketCard
The trending card is a richer endpoint-specific market projection. It is not the same object as MarketListItem or the smaller market object returned by trade endpoints.
| Field | JSON type | Nullable | Definition |
|---|---|---|---|
rank | integer | No | One-based position in the returned items array. |
id | integer | No | Stable Polynion market ID. |
slug | string | No | Stable market slug. |
title | string | No | Source market title. |
description | string | Yes | Source market description. |
image_url | string | Yes | Market image URL. |
status | string literal | No | Always open; only open markets are eligible. |
geo_tag | string enum | Yes | Eligible MarketGeoTag. null means international. |
yes_label | string | No | Stored custom label, or YES when no custom label exists. |
no_label | string | No | Stored custom label, or NO when no custom label exists. |
yes_price_bps | integer | No | Current YES price in basis points; 10_000 represents 100%. |
no_price_bps | integer | No | Current NO price in basis points; 10_000 represents 100%. |
yes_percentage | number | No | yes_price_bps / 100. |
no_percentage | number | No | no_price_bps / 100. |
volume_credits | integer | No | Processed lifetime trade volume across all channels, in Polynion Credits. |
trending_volume_72h_credits | integer | No | Processed trade volume across all channels over the preceding 72 hours, in Polynion Credits. |
categories | array | No | Zero or more CategoryOrTagSummary objects. |
tags | array | No | Zero or more CategoryOrTagSummary objects. |
translations | object | No | Locale-keyed MarketTranslation map. Returns {} when no translations exist. |
open_at | ISO 8601 string | Yes | Scheduled/opened-at timestamp. |
trading_cutoff_ts | ISO 8601 string | Yes | Trading cutoff timestamp. |
estimated_resolve_ts | ISO 8601 string | Yes | Estimated resolution time. |
resolve_deadline_ts | ISO 8601 string | Yes | Resolution deadline. |
updated_at | ISO 8601 string | No | Market update timestamp. |
market_path | string | No | Relative Mini App path, always /markets/{slug}. |
Nested trending objects
| Object/field | JSON type | Nullable | Definition |
|---|---|---|---|
CategoryOrTagSummary.id | integer | No | Category or tag ID. |
CategoryOrTagSummary.slug | string | No | Category or tag slug. |
CategoryOrTagSummary.name | string | No | Category or tag display name. |
MarketTranslation.title | string | No | Translated market title. |
MarketTranslation.description | string | No | Translated market description. |
MarketTranslation.yesLabel | string | Yes | Translated YES-side label. |
MarketTranslation.noLabel | string | Yes | Translated NO-side label. |
Translation locale keys are limited to en, id, zh-CN, and es by the database enum. Property names inside each translation use camelCase exactly as shown.
meta
| Field | JSON type | Nullable | Definition |
|---|---|---|---|
source | string enum | No | operator_featured_trending without category; operator_category_trending with category. |
amount | integer | No | Effective requested item limit after defaulting/capping. The actual items.length may be smaller. |
category | string | Yes | Trimmed category slug, or null when category mode is not used. |
operator_geo_tag | string enum | Yes | Normalized MarketGeoTag, or null. |
included_geo_tags | array of string enum or null | No | [null] for an international-only operator, otherwise [null, operator_geo_tag]. |
locale | string | No | Authenticated operator's configured default locale. |
generated_at | ISO 8601 string | No | Response generation timestamp. |
Example
{
"items": [
{
"rank": 1,
"id": 51,
"slug": "will-indonesia-qualify-for-the-2026-world-cup",
"title": "Will Indonesia qualify for the 2026 World Cup?",
"description": "Resolves YES if Indonesia qualifies for the tournament.",
"image_url": "https://cdn.example.com/markets/51.webp",
"status": "open",
"geo_tag": "ID",
"yes_label": "Qualifies",
"no_label": "Does not qualify",
"yes_price_bps": 5129,
"no_price_bps": 4871,
"yes_percentage": 51.29,
"no_percentage": 48.71,
"volume_credits": 123456,
"trending_volume_72h_credits": 45678,
"categories": [
{ "id": 1, "slug": "sports", "name": "Sports" }
],
"tags": [
{ "id": 10, "slug": "football", "name": "Football" }
],
"translations": {
"id": {
"title": "Apakah Indonesia akan lolos ke Piala Dunia 2026?",
"description": "Diselesaikan YA jika Indonesia lolos ke turnamen.",
"yesLabel": "Lolos",
"noLabel": "Tidak lolos"
}
},
"open_at": "2026-07-09T00:00:00.000Z",
"trading_cutoff_ts": "2026-11-15T12:00:00.000Z",
"estimated_resolve_ts": null,
"resolve_deadline_ts": "2026-11-16T12:00:00.000Z",
"updated_at": "2026-07-10T08:30:00.000Z",
"market_path": "/markets/will-indonesia-qualify-for-the-2026-world-cup"
}
],
"meta": {
"source": "operator_featured_trending",
"amount": 5,
"category": null,
"operator_geo_tag": "ID",
"included_geo_tags": [null, "ID"],
"locale": "id",
"generated_at": "2026-07-10T08:31:00.000Z"
}
}
Visibility mutation
| Parameter | Location | Type | Requirement | Notes |
|---|---|---|---|---|
id | Path | positive integer | Required | Existing market ID within the operator geo scope. Market status and current visibility do not change eligibility for this mutation. |
visible | JSON body | boolean | Required | true shows the market; false hides it. |
Body:
{ "visible": false }
Content type: application/json.
interface MarketVisibilityResponse {
id: number
visibility: 'visible' | 'hidden'
market_visibility_mode: 'all' | 'allowlist' | 'blocklist'
}
Response example:
{
"id": 51,
"visibility": "hidden",
"market_visibility_mode": "blocklist"
}
| Field | JSON type | Definition |
|---|---|---|
id | integer | Market ID from the path. |
visibility | string enum | Effective requested state: visible or hidden. |
market_visibility_mode | string enum | Operator mode after the mutation: all, allowlist, or blocklist. Hiding a market while mode is all changes it to blocklist. |
Behavior:
allowlist: visible inserts/updatesallow; hidden deletes the allow rule.blocklist: hidden inserts/updatesblock; visible deletes the block rule.all: first hidden mutation switches the operator toblocklistand inserts a block rule.- out-of-geo market IDs return
MARKET_NOT_FOUND; operators cannot show/hide or allowlist markets outside their geo scope.
Errors:
400 INVALID_MARKET_ID: path value is not a positive integer.400 INVALID_VISIBILITY: JSON body is missingvisibleorvisibleis not a boolean.404 MARKET_NOT_FOUND: market does not exist or is outside the operator geo scope.
