OpenPoly logo
Operator Api

Markets

Query eligible markets and manage operator visibility.

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:read for list and trending/widget cards
  • markets:write for visibility mutation

Query params

All list query parameters are optional.

ParameterTypeRequirementValues/default
statusstringOptionalopen, halted, in_review, resolved, or delisted. Without this filter, only open, halted, and resolved are returned.
searchstringOptionalCase-insensitive match against slug or title.
visibilitystringOptionalvisible or hidden.
sortstringOptionalupdated_at_desc (default) or trading_cutoff_asc.
pagepositive integerOptionalDefault 1, maximum 10,000.
page_sizepositive integerOptionalDefault 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

  • all
  • allowlist
  • blocklist

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

FieldJSON typeNullableDefinition
idintegerNoStable Polynion market ID.
slugstringNoStable market slug.
titlestringNoSource market title.
geo_tagstring enumYesEligible geo tag: ID, KR, VN, JP, TH, MY, IN, BR, MX, CO, or AU. null means international.
statusstring enumNoopen, halted, in_review, resolved, or delisted.
yes_labelstringYesCustom YES-side label. null means no custom label is stored.
no_labelstringYesCustom NO-side label. null means no custom label is stored.
yes_percentagenumberNoCurrent YES percentage from AMM state, rounded to two decimal places. Returns 0 if total AMM state is not positive.
visibilitystring enumNoEffective operator visibility: visible or hidden.
open_atISO 8601 stringYesScheduled/opened-at timestamp.
trading_cutoff_tsISO 8601 stringYesTrading cutoff timestamp.
resolve_deadline_tsISO 8601 stringYesResolution deadline.
estimated_resolve_tsISO 8601 stringYesEstimated resolution time.
resolved_outcomestring enumYesyes, no, or null when unresolved.
volume_24hintegerNoOperator-scoped processed trade volume over the preceding 24 hours, in Polynion Credits. Always present; 0 means no matching volume.
updated_atISO 8601 stringNoMarket update timestamp.

Pagination

FieldJSON typeDefinition
pageintegerEffective page number after defaulting/capping; always 1..10,000.
page_sizeintegerEffective page size after the default and maximum are applied.
totalintegerTotal matching records across all pages.
total_pagesintegermax(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
  }
}
GET /api/operator/markets/trending

Required scope: markets:read.

Query parameters:

ParameterTypeRequirementValues/default
amountpositive integerOptionalDefault 5. Values above 20 are capped to 20; non-integers and values below 1 return 400 INVALID_AMOUNT.
categorystringOptionalActive 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:

  1. Admin featured picks for the operator geo
  2. Admin featured picks for international markets
  3. eligible picks from the global/all featured row
  4. 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.

FieldJSON typeNullableDefinition
rankintegerNoOne-based position in the returned items array.
idintegerNoStable Polynion market ID.
slugstringNoStable market slug.
titlestringNoSource market title.
descriptionstringYesSource market description.
image_urlstringYesMarket image URL.
statusstring literalNoAlways open; only open markets are eligible.
geo_tagstring enumYesEligible MarketGeoTag. null means international.
yes_labelstringNoStored custom label, or YES when no custom label exists.
no_labelstringNoStored custom label, or NO when no custom label exists.
yes_price_bpsintegerNoCurrent YES price in basis points; 10_000 represents 100%.
no_price_bpsintegerNoCurrent NO price in basis points; 10_000 represents 100%.
yes_percentagenumberNoyes_price_bps / 100.
no_percentagenumberNono_price_bps / 100.
volume_creditsintegerNoProcessed lifetime trade volume across all channels, in Polynion Credits.
trending_volume_72h_creditsintegerNoProcessed trade volume across all channels over the preceding 72 hours, in Polynion Credits.
categoriesarrayNoZero or more CategoryOrTagSummary objects.
tagsarrayNoZero or more CategoryOrTagSummary objects.
translationsobjectNoLocale-keyed MarketTranslation map. Returns {} when no translations exist.
open_atISO 8601 stringYesScheduled/opened-at timestamp.
trading_cutoff_tsISO 8601 stringYesTrading cutoff timestamp.
estimated_resolve_tsISO 8601 stringYesEstimated resolution time.
resolve_deadline_tsISO 8601 stringYesResolution deadline.
updated_atISO 8601 stringNoMarket update timestamp.
market_pathstringNoRelative Mini App path, always /markets/{slug}.
Object/fieldJSON typeNullableDefinition
CategoryOrTagSummary.idintegerNoCategory or tag ID.
CategoryOrTagSummary.slugstringNoCategory or tag slug.
CategoryOrTagSummary.namestringNoCategory or tag display name.
MarketTranslation.titlestringNoTranslated market title.
MarketTranslation.descriptionstringNoTranslated market description.
MarketTranslation.yesLabelstringYesTranslated YES-side label.
MarketTranslation.noLabelstringYesTranslated 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

FieldJSON typeNullableDefinition
sourcestring enumNooperator_featured_trending without category; operator_category_trending with category.
amountintegerNoEffective requested item limit after defaulting/capping. The actual items.length may be smaller.
categorystringYesTrimmed category slug, or null when category mode is not used.
operator_geo_tagstring enumYesNormalized MarketGeoTag, or null.
included_geo_tagsarray of string enum or nullNo[null] for an international-only operator, otherwise [null, operator_geo_tag].
localestringNoAuthenticated operator's configured default locale.
generated_atISO 8601 stringNoResponse 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

ParameterLocationTypeRequirementNotes
idPathpositive integerRequiredExisting market ID within the operator geo scope. Market status and current visibility do not change eligibility for this mutation.
visibleJSON bodybooleanRequiredtrue 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"
}
FieldJSON typeDefinition
idintegerMarket ID from the path.
visibilitystring enumEffective requested state: visible or hidden.
market_visibility_modestring enumOperator mode after the mutation: all, allowlist, or blocklist. Hiding a market while mode is all changes it to blocklist.

Behavior:

  • allowlist: visible inserts/updates allow; hidden deletes the allow rule.
  • blocklist: hidden inserts/updates block; visible deletes the block rule.
  • all: first hidden mutation switches the operator to blocklist and 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 missing visible or visible is not a boolean.
  • 404 MARKET_NOT_FOUND: market does not exist or is outside the operator geo scope.
Copyright © 2026