NAV
δΈ­ζ–‡
HTTP Python

Concept Overview

This section introduces the core business concepts you need to understand before integrating the Outcomes API, including xp (the base asset), events, markets, outcomes, prices, Split / Merge, the mirror order book, the order book, positions, settlement, and the key IDs.

Once you understand these concepts, you will be able to reason more clearly about:

1. Prediction Market

A prediction market is a market for trading on the outcomes of future events.

Each market usually corresponds to a clear question, for example:

Will Germany win a given match?

Users trade the different outcomes of that question based on their own judgment. The most common outcomes are YES and NO.

Example:

Object Example
Market Will Germany win?
YES Germany wins
NO Germany does not win

In simple terms:

Direction Meaning
Buy YES The user believes the outcome will happen
Buy NO The user believes the outcome will not happen

2. Core Object Model

The core object relationships in the Outcomes API are as follows:

In simple terms:

Object Meaning
Event A real-world event
Market A specific tradable question under an Event
Outcome A tradable result under a Market, e.g. YES / NO
assetId The tradable asset ID of a specific outcome

When integrating, you typically follow this path:

  1. Query an Event
  2. Get the Markets under that Event
  3. Choose the YES or NO outcome under a Market
  4. Trade using the assetId of that outcome

3. Event and Market

An Event is the upper-level organizational unit of the prediction market, representing a real-world event.

Example:

Object Example
Event Germany vs. Curacao

An Event can contain multiple Markets:

Market Question
Market 1 Will Germany win?
Market 2 Will the two teams draw?
Market 3 Will Curacao win?

What users actually trade is not the Event itself, but the YES or NO outcome under a specific Market.

4. Outcome

An Outcome is the tradable object under a Market.

In a binary market, each Market usually has two outcomes:

Outcome Meaning
YES The Market's result will happen
NO The Market's result will not happen

Each outcome has its own assetId. When placing an order, what you ultimately use is the assetId of the target outcome.

Example:

To buy YES, use yesOutcome.assetId. To buy NO, use noOutcome.assetId.

5. Price and Probability

A price in the prediction market is a decimal between 0 and 1. It can be understood as the market's real-time estimate of the probability that an outcome happens.

For example:

Outcome Current price Interpretation
YES 0.65 The market currently estimates the probability of YES at about 65%
NO 0.35 The market currently estimates the probability of NO at about 35%

Why price β‰ˆ probability: a winning outcome settles at 1 xp and a losing one at 0 xp, so the expected value of holding an outcome = 1 xp Γ— the probability that it occurs. In an efficient market the price converges to this expected value, so the price can be read as the market's estimate of that outcome's probability.

The price is formed by market trading and does not represent the final result. The final result is determined by market settlement.

6. Complementary Relationship of YES / NO

YES and NO are the two sides of the same Binary Market. When one outcome happens, the other does not.

Ideally, the sum of their prices is close to 1.

Outcome Price
YES 0.65
NO 0.35
YES + NO 1.00

This relationship can be summarized as:

YES + NO β‰ˆ 1

You do not need to handle the YES / NO price conversion manually. When placing an order, simply choose the target outcome and use its assetId.

7. Split / Merge Mechanism

Split / Merge is the underlying mechanism behind the YES / NO complementary relationship.

For a Binary Market, YES and NO can be seen as a pair of conditional outcomes. The system can convert between xp (the base asset of this prediction market) and the YES / NO outcomes via Split and Merge.

7.1 Split

Split splits one unit of xp into equal amounts of YES and NO outcomes.

1 xp β†’ 1 YES + 1 NO

This can be understood as: the user splits one complete market entitlement into the market's two complementary outcomes.

Before After
1 xp 1 YES + 1 NO

7.2 Merge

Merge is the inverse of Split. The user can merge equal amounts of YES and NO outcomes back into xp.

1 YES + 1 NO β†’ 1 xp

Merge is only possible when the YES and NO amounts are equal.

Before After
1 YES + 1 NO 1 xp

This mechanism guarantees the complementary relationship of YES and NO:

YES + NO β‰ˆ 1

In other words, within the same Binary Market, YES and NO are not fully independent assets, but the two sides of the same set of conditional outcomes.

8. Mirror Order Book

Based on the YES / NO complementary relationship, the system can use a mirror order book to handle YES and NO trading in a unified way.

For the same Binary Market:

User action Economically equivalent to
Buy YES @ 0.60 Sell NO @ 0.40
Buy NO @ 0.30 Sell YES @ 0.70

The reason is:

YES price + NO price β‰ˆ 1

Therefore, the system can support both YES and NO trading on a single unified order book at the underlying level.

You do not need to split or convert mirror orders manually. When placing an order, simply choose the outcome the user wants to trade and use its assetId. The system handles the YES / NO equivalence at the order book level.

9. Order Book

Outcomes uses a central limit order book (CLOB).

CLOB stands for Central Limit Order Book. Market prices are not set directly by the platform, but are formed by users' orders and trades.

The order book has two sides:

Side Meaning
Bid The price a buyer is willing to buy at
Ask The price a seller is willing to sell at

Example:

Side Price Size
Ask 0.67 100
Ask 0.66 80
Bid 0.65 120
Bid 0.64 200

When the bid and ask prices match, the orders are matched and traded.

10. Maker and Taker

When an order is filled, the user may be a Maker or a Taker.

Role Meaning
Maker Posts an order to the order book, providing liquidity
Taker Actively fills an existing order in the book, consuming liquidity

Example:

The current best ask is 0.66.

User action Result Role
Buy at 0.66 Fills immediately Taker
Post a buy at 0.60 Cannot fill immediately; enters the book and waits Maker

Order matching generally follows:

Priority Description
Price priority Better prices are matched first
Time priority At the same price, earlier orders in the book are matched first

11. Position

Open orders that are not yet filled occupy the available balance. After an order is filled, the user obtains a position in the corresponding outcome.

Example:

Action Result
User buys 10 YES After the fill, the user holds 10 YES outcomes

Positions change with the following operations:

Operation Effect on position
Fill Increases or decreases the corresponding outcome position
Close Decreases an existing outcome position
Split Generates YES / NO outcomes
Merge Merges YES / NO outcomes
Settlement Updates positions and balances based on the final result

12. Closing a Position

A user does not have to wait for market settlement to exit a position. If the market is still tradable, the user can close a position by selling an existing outcome.

Example:

Step Action
Step 1 User buys YES at 0.40
Step 2 YES price later rises to 0.70
Step 3 User sells YES to close the position
Step 4 The user's position and balance are updated based on the fill

Closing a position is essentially exiting an existing position via a reverse trade.

13. Settlement

Once an event's result is determined, the Market enters the settlement process.

After settlement:

Outcome Settlement result
Winning outcome Settled at 1 xp
Losing outcome Settled at 0 xp

Example:

Market: Will Germany win?

If Germany ultimately wins:

Outcome Result
YES Wins
NO Loses

If Germany does not win:

Outcome Result
YES Loses
NO Wins

After the market settles, open orders are closed, and the user's positions and balances are updated based on the final result.

14. Binary Market

A Binary Market is the most common market structure, representing a yes / no question.

Example:

Market Will Germany win?
YES Germany wins
NO Germany does not win

A Binary Market usually has only two outcomes:

You only need to choose the assetId of YES or NO to trade.

15. NegRisk Market

A NegRisk Market is used for scenarios with multiple mutually exclusive outcomes. An Event can contain multiple related Markets, but usually only one outcome ultimately wins.

Example:

Event: Who will win the championship?

Market Question
Market 1 Will Germany win the title?
Market 2 Will France win the title?
Market 3 Will Brazil win the title?
Market 4 Will another team win the title?

If Germany ultimately wins the title:

Market YES result
Will Germany win the title? Wins
Will France win the title? Loses
Will Brazil win the title? Loses
Will another team win the title? Loses

For developers, a NegRisk Market does not change the basic trading method. You still choose the YES or NO outcome under a specific Market and trade with its assetId.

Market type Characteristics Developer trading method
Binary Market A single yes / no question Choose the assetId of YES or NO
NegRisk Market Multiple mutually exclusive outcomes Still choose the assetId of YES or NO under a specific Market

16. Market Status

A Market goes through different states during its lifecycle.

Common states:

Status Meaning Generally tradable
active Market is trading normally Yes
paused Market trading is paused No
settling Market is settling No
resolved Market has been settled No

Check the Market status before placing an order. If the Market is not active, the order may be rejected.

17. Key IDs

When integrating the API, the various IDs are the most easily confused.

Keep these in mind:

ID Meaning Common use
eventId Event ID Query Event details and its Markets
marketId Market ID Identify a specific tradable question
assetId An outcome's asset ID Place order, cancel, market data, order book, positions, and other trading operations
orderId Order ID Query orders, cancel, track order status

The most important rule is:

Order placement, cancellation, market data, and order book operations generally use assetId.

If the API returns assetId as null, the outcome is temporarily not tradable. Wait until assetId returns a valid value before performing trading operations.

18. Summary

You can understand the core model of Outcomes with the following chain:

Event β†’ Market β†’ Outcome β†’ assetId β†’ Order / Position

The mapping is:

Stage Description
Event Find a real-world event
Market Find a specific question under that event
Outcome Choose YES or NO
assetId Get the tradable asset ID
Order Place an order using assetId
Position A position is formed after the fill

For API consumers, the most critical points are:

Key point Description
Do not trade the Event The Event is only an organizational unit
Do not trade the Market directly The Market is a specific question
Actually trade the Outcome The Outcome is represented by assetId
YES / NO are complementary YES + NO β‰ˆ 1
The mirror order book is handled by the system No manual conversion needed
Check status before ordering The Market must be tradable
assetId must not be null assetId = null means temporarily not tradable

Quick Start

This guide walks you through the minimal integration flow of the Outcomes API and submitting your first order.

Prerequisites

Before you start, make sure you have the following:

Requirement Description
OKX account Prediction market capability enabled
Available balance For submitting orders
OKX App For creating an API Key and authorizing an Agent
Dev environment Examples in this doc use the Rust SDK
Agent private key For signing write operations such as place / cancel order

Step 1: Set Up an API Key

Before signing any request, you must first create an API Key via the OKX API management page or the App. After creation, you receive the following 3 credentials, which must be kept safe:

Credential Description
APIKey Public identifier
SecretKey Used to sign requests
Passphrase An extra security credential you set yourself

APIKey and SecretKey are randomly generated and provided by the platform; the Passphrase is set by you to strengthen the security of API access.

APIKey Permissions

For the prediction market, select all of the following permissions:

Permission Description
Read Read-only operations such as querying bills and history
Trade Write operations such as place order, cancel, transfer, and config changes

Creation Steps

Step 1: Tap "View" on the App prediction market page Step 2: Tap the top-right menu
Tap View on the App prediction market page Tap the top-right menu
Step 3: Open the API Key generation page Step 4: Generate the API Key
Open the API Key generation page Generate the API Key
Step 5: View details after generation Step 6: Details page
View details after generation Details page

All REST endpoints require OKX API credentials. Construct the client with with_credentials (see Step 5).

Step 2: Set Up an Agent

Keep your main key safe β€” create an Agent key that signs trades on your behalf.

  1. Generate a new Ethereum key pair (any key-generation tool). Save the Agent private key and the Agent address.
  2. Authorize the Agent in the OKX App β€” Agent authorization must be done via the App, not the API.

App Agent Authorization Steps

Step 1: Enter the Agent address to authorize Step 2: Authorization succeeded
Enter the Agent address to authorize Authorization succeeded

After authorization, all subsequent write API calls use the Agent private key; read API calls do not require the private key.

Step 3: Add the SDK

Installation

Add this SDK and the Tokio runtime to your Cargo.toml:

Cargo.toml dependencies

[dependencies]
okx-outcomes-sdk = { git = "https://github.com/okx/outcomes-sdk.git", features = ["signing", "websocket"] }
tokio = { version = "1", features = ["full"] }

That is the complete dependency list. tokio-tungstenite, k256, sha3, futures-util, etc. are pulled in transitively via the SDK's features. Do not add these dependencies yourself, or you may end up with versions inconsistent with what the SDK was built against.

Feature Flags

Feature What it enables When to enable
default REST client only (events, markets, orders, positions, balance, trades, prices, sports data). Read-only integration.
signing EIP-712 + ECDSA action signing helpers. Any write operation: place_order, cancel_order, cancel_all, split, merge, redeem, heartbeat.
websocket The tokio-tungstenite-based PredictionsWsClient and the typed WsMessage parser. Real-time prices, trades, order book, and user order / position / balance streams.

Step 4: Configure Environment Variables

We recommend storing credentials in environment variables:

Environment variable configuration

export PREDICTIONS_API_KEY="your-api-key"
export PREDICTIONS_API_SECRET="your-secret-key"
export PREDICTIONS_API_PASSPHRASE="your-passphrase"
export PREDICTIONS_AGENT_PRIVATE_KEY="0x..."

Step 5: Initialize the Client

Initialize the SDK Client with the API Key, Secret Key, and Passphrase:

Client initialization example

use okx_outcomes_sdk::{ApiCredentials, OutcomesSdkClient};

let creds = ApiCredentials {
    api_key:    std::env::var("PREDICTIONS_API_KEY")?,
    secret_key: std::env::var("PREDICTIONS_API_SECRET")?,
    passphrase: std::env::var("PREDICTIONS_API_PASSPHRASE")?,
};

let client = OutcomesSdkClient::with_credentials(creds);

The SDK signs REST requests locally and automatically writes the required authentication headers.

Step 6: Query Tradable Markets

get_events returns a paginated list of events. All filter parameters are optional:

Query events example

let page = client
    .get_events(
        Some("active"),      // status: "active" | "resolved"
        None,                // tag
        None,                // league_id
        Some("volume_24h"),  // sort: "volume" | "volume_24h" | "ending_soon" | "newest"
        None,                // cursor (from the previous page)
        Some(20),            // page_size (max 50)
    )
    .await?;

for event in &page.events {
    println!("{} - {} markets ({} points volume)",
        event.event_title, event.total_markets_count, event.volume);
}

// Pagination: pass `page.pagination.next_cursor` back as `cursor` on the next call.

Other read methods

Method Purpose
client.search(keyword, cursor, page_size) Search events and markets by keyword.
client.get_event(event_id) A single event and its full market list.
client.get_event_markets(event_id) All markets under a single event (no pagination).
client.get_market(market_id) Get a single market by market ID.
client.get_ticker(inst_id) Latest ticker for a single market instrument.
client.get_candles(...) Candlestick history.
client.get_trades(...) Recent public trade history.

Find a market with status: "active" and record:

Step 7: Place a Limit Order

Buy 10 YES tokens at 0.55 xp on market 100049000. This requires two layers of authentication: the API Key headers + the SDK-signed request body.

Full limit order example

use okx_outcomes_sdk::{ApiCredentials, OutcomesSdkClient};
use okx_outcomes_sdk::models::order::{
    LimitOrderType, OrderItem, PlaceOrderAction, PlaceOrderRequest, SigningOrderSide, SizeType,
};
use okx_outcomes_sdk::signing::{
    action_place_order, generate_client_order_id_default, now_millis, parse_private_key,
    sign_to_wrapper, LimitTif, OrderRequest, OrderType,
};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let creds = ApiCredentials {
        api_key:    std::env::var("PREDICTIONS_API_KEY")?,
        secret_key: std::env::var("PREDICTIONS_API_SECRET")?,
        passphrase: std::env::var("PREDICTIONS_API_PASSPHRASE")?,
    };
    let client = OutcomesSdkClient::with_credentials(creds);

    // 1. Load the on-chain signing key (hex, with or without 0x prefix).
    let key = parse_private_key(&std::env::var("PREDICTIONS_AGENT_PRIVATE_KEY")?)?;

    // 2. Build the typed order. `asset_id` is the outcome asset ID from Step 6.
    let order_request = OrderRequest {
        asset_id:         "100049000".into(),
        side:             SigningOrderSide::Buy,
        market_type:      "prediction".into(),
        client_order_id:  Some(generate_client_order_id_default()?),
        price:            "0.55".into(),
        reduce_only:      false,
        size:             "10".into(),
        size_type:        SizeType::Base, // omitted on the wire (default)
        order_type:       OrderType::Limit(LimitOrderType { tif: LimitTif::Gtc }),
    };

    // 3. Derive the wire-format OrderItem from the same OrderRequest so the
    //    JSON body cannot drift from the signed msgpack bytes.
    let order_item = OrderItem::try_from(&order_request)?;
    let action = action_place_order(vec![order_request]);

    // 4. Sign and assemble the SignatureWrapper expected by the wire format.
    let nonce = now_millis();
    let signature = sign_to_wrapper(&action, nonce, None, &key)?;

    // 5. Submit.
    let req = PlaceOrderRequest {
        action: PlaceOrderAction {
            action_type: "placeOrder".into(),
            grouping:    "na".into(),
            orders:      vec![order_item],
        },
        nonce: nonce as i64,
        signature,
    };
    let resp = client.place_order(&req).await?;
    println!("tx_hash: {}", resp.tx_hash);
    Ok(())
}

Place order response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0x7a8b9c0d1e2f3a4b5c6d7e8f9a0b1c2d3e4f5a6b7c8d9e0f1a2b3c4d5e6f7a8b"
  }
}

Step 8: View Orders

When status = Some("open"), list_orders returns all non-terminal orders on the account, page by page. Pass the next_cursor from the previous response back via the cursor parameter until has_next is false:

Query order list example

use okx_outcomes_sdk::models::order::OrderRecord;

let mut cursor: Option<String> = None;
let mut all_open: Vec<OrderRecord> = Vec::new();

loop {
    let page = client
        .list_orders(
            None,             // market_id: None = all markets, Some(123) = a specific market
            Some("open"),     // "open" = PENDING_PLACE / ACTIVE / PENDING_CANCEL; "closed" = FILLED / PARTIALLY_FILLED / CANCELLED / EXPIRED / FAILED
            cursor.as_deref(),
            Some(50),         // max items per page
        )
        .await?;
    all_open.extend(page.list);
    if !page.has_next {
        break;
    }
    cursor = page.next_cursor;
}

println!("{} open orders", all_open.len());
for o in &all_open {
    println!(
        "#{} [{}] {} {}/{} @ {}  market={} asset={} cloid={:?}",
        o.id, o.status, o.side, o.filled_size, o.size, o.price,
        o.market_id, o.asset_id, o.client_order_id,
    );
}

Each OrderRecord exposes the order parameters (side, size, price, order_type, size_type, expiration), order metadata (market_id, asset_id, client_order_id, tx_hash), and the current lifecycle status (status, filled_size, filled_amount).

Tips:

Step 9: Cancel an Order

The cancel flow is the same as place_order, also in three steps: build a typed CancelRequest, derive the corresponding wire-format CancelItem from it, sign the action, and finally submit the wire request. You can locate the target order by the order's server oid (the OrderRecord.id value) or by cloid (the client_order_id passed when placing the order).

The snippet below assumes client and key are already constructed as in the order placement section.

Cancel order example

use okx_outcomes_sdk::models::order::{CancelItem, CancelOrderAction, CancelOrderRequest};
use okx_outcomes_sdk::signing::{
    action_cancel, now_millis, sign_to_wrapper, CancelRequest, CancelTarget,
};

// 1. CancelTarget must be one of:
//    Oid   = server-assigned order ID (OrderRecord.id), a decimal string.
//    Cloid = client-assigned order ID, a hex string with the 0x prefix.
let cancel_request = CancelRequest {
    asset_id:    "100049000".into(),
    market_type: "prediction".into(),
    target:      CancelTarget::Oid("578840".into()),
    // target:   CancelTarget::Cloid("0xabc...".into()),
};

// 2. Derive the wire item from the same request so the signed bytes do not diverge from the JSON body.
let cancel_item = CancelItem::from(&cancel_request);
let action      = action_cancel(vec![cancel_request]);

// 3. Sign and submit.
let nonce     = now_millis();
let signature = sign_to_wrapper(&action, nonce, None, &key)?;

let resp = client
    .cancel_order(&CancelOrderRequest {
        action: CancelOrderAction {
            action_type: "cancel".into(),
            cancels:     vec![cancel_item],
        },
        nonce:     nonce as i64,
        signature,
    })
    .await?;
println!("cancel tx_hash: {}", resp.tx_hash);

Step 10: View Balance, Orders, and Positions

Query balance and positions example

// Balance
let entries = client.get_balance().await?; // Vec<BalanceEntry>
for entry in &entries {
    println!("[{}] available={} total={}", entry.odds_type, entry.available, entry.balance);
}

// Positions
let positions = client.get_positions(
    Some("open"), // "open" | "closed" | "settled"
    None,         // market_id
    None,         // cursor
    Some(50),     // limit
).await?;
println!("{} positions", positions.list.len());

For order queries, see Step 8. get_positions is also paginated via cursor + limit.

Full Flow Recap

The minimal integration flow is:

  1. Create an API Key in the App / web
  2. Authorize an Agent in the App
  3. Install the SDK
  4. Configure environment variables
  5. Initialize the Client
  6. Query Events and Markets
  7. Get the assetId of the YES / NO outcome and submit an order with it
  8. Query order status
  9. Cancel the order if needed
  10. Query positions and balance after the fill

Notes

Topic Description
Heartbeat If you run a bot, set up a heartbeat to auto-cancel all orders when the bot disconnects. Send a pre-signed cancel-all every < 5 minutes; see the SDK docs for details.
Price tick See the table below.
Nonce Use the current millisecond timestamp. Each nonce can be used only once.
Timestamp skew OK-ACCESS-TIMESTAMP must be within 30 seconds of the server time, otherwise error code 50102 is returned.

Price Tick Rules

Price range Min precision Example
0.04 - 0.96 0.01 0.04, 0.55, 0.96
< 0.04 or > 0.96 0.001 0.039, 0.962

FAQ

Issue Possible cause Resolution
assetId is null The market is not yet registered Wait until assetId returns a valid value before placing orders
Order fails Market not tradable, insufficient balance, signature error, or invalid parameters Check the error code and the order failure reason
Signature fails The Agent private key is incorrect, or the signed content does not match the request body Confirm the Agent is authorized and use the SDK to build the signature
Order not found Wrong order ID, or the order has already ended Use the order list endpoint to confirm the order status
Order still not cancelled after cancel The cancellation is being processed asynchronously Wait for the status to update from PENDING_CANCEL to CANCELLED

REST API

The REST API lets you query events, markets, tickers, order books, orders, trades, positions, and balances, and submit write operations such as place order, cancel order, Split, Merge, and Redeem. Developers can complete the full trading loop through the REST API: discover markets, obtain the assetId, submit orders, query order status, manage positions, and handle settlement-related operations. The base URL for all endpoints is https://www.okx.com.

1. Events & Markets API

Data Structures

OutcomeResp β€” Outcome option

Field Type Description
tokenId string / null Conditional token contract address; null before deployment
assetId string / null TradeZone asset ID (used when placing orders); null before deployment
name string Outcome name, e.g. "Yes", "No"
price string Current price (string in [0, 1], e.g. "0.82")
bgColor string Button background color (Hex); for a sports moneyline Yes outcome this is the home-team theme color; otherwise null

MarketResp β€” Market object

Field Type Description
id string Globally unique market ID
marketId string Market's unique ID in TradeZone (used when placing orders)
negRisk boolean Whether this is a mutually exclusive (negRisk) market
status string Market status: active / paused / settling / resolved
settleStage int Settlement stage: 0=not started, 1=first-round publication, 2=first-round dispute, 3=second-round publication, 4=second-round dispute, 5=settled
question string Full market question
shortQuestion string / null Short market question
description string Market description
marketIcon string / null Market icon URL
bestBid string / null Best bid price (0–1); null if there are no bids
bestAsk string / null Best ask price (0–1); null if there are no asks
lastTradePrice string / null Last trade price (0–1); null if never traded
volume string Total volume (xp)
probability string / null Market Yes probability (decimal in [0, 1]); null before deployment
resolutionSources string[] List of resolution data source URLs
yesOutcome OutcomeObject Yes outcome option
noOutcome OutcomeObject No outcome option
startTime string Expected market start time (Unix ms)
endTime string Expected market end time (Unix ms)
resolveStartAt string First time the market entered the start_resolve state (Unix ms)
resolveAt string First time the market entered the resolved state (Unix ms)

EventResp β€” Event object

Field Type Description
id string Globally unique event ID
eventId string Event's unique ID in TradeZone
negRisk boolean Whether this is a negRisk event
status string Event status: active / paused / resolved
eventTitle string Event title
description string Event description
eventIcon string / null Event icon URL
volume string Sum of volume across all markets in the event (xp)
startTime string / null Trading start timestamp (ms)
endTime string / null Trading stop timestamp (ms)
createdAt string Event creation timestamp (ms)
totalMarketsCount int Total number of markets under the event
finalOutcomesMarketId string / null Winning market ID after settlement; null before settlement
markets array<MarketResp> Market list (the list endpoint returns at most the first 2; get the full list via GET /api/v5/predictions/events/{eventId}/markets)

PaginationResp β€” Cursor pagination

Field Type Description
nextCursor string / null Next-page cursor; null means this is the last page
hasMore boolean Whether more data is available
pageSize int Number of items returned this time

1.1 Get events

Get the prediction market event list, with multi-dimensional filtering and sorting by status, etc.

HTTP Request

GET /api/v5/predictions/events

Request Parameters (Query)

Parameter Type Required Default Description
status string No active Event status filter: active / resolved
sort string No volume_24h Sort order: volume / volume_24h / ending_soon / newest
tag string No - Sports tag ID filter (from GET /api/v5/predictions/sports/tags)
leagueId string No - League ID filter (from GET /api/v5/predictions/sports/tags/{tagId}/leagues)
cursor string No - Pagination cursor; omit on the first request
pageSize int No 10 Items per page (max 50)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "events": [
      {
        "id": "100",
        "eventId": "100",
        "negRisk": false,
        "status": "active",
        "eventTitle": "Will BTC exceed $100k by end of 2026?",
        "description": "This event tracks whether Bitcoin will cross $100,000.",
        "eventIcon": "https://cdn.example.com/events/100.png",
        "volume": "1250000.00",
        "startTime": 1700000000000,
        "endTime": 1798761600000,
        "createdAt": 1710000000000,
        "totalMarketsCount": 1,
        "finalOutcomesMarketId": null,
        "markets": [
          {
            "id": "1",
            "marketId": "1",
            "oddsType": "points",
            "negRisk": false,
            "status": "active",
            "settleStage": 0,
            "question": "Will BTC exceed $100k by end of 2026?",
            "shortQuestion": "BTC > $100k?",
            "description": "Resolves YES if BTC price exceeds $100,000 before Jan 1, 2027.",
            "marketIcon": null,
            "bestBid": "0.64",
            "bestAsk": "0.66",
            "lastTradePrice": "0.65",
            "volume": "1250000.00",
            "probability": "0.65",
            "resolutionSources": ["https://coinmarketcap.com"],
            "yesOutcome": {
              "tokenId": "0xabc...001",
              "assetId": "1",
              "name": "Yes",
              "price": "0.65",
              "finalResult": null
            },
            "noOutcome": {
              "tokenId": "0xabc...002",
              "assetId": "2",
              "name": "No",
              "price": "0.35",
              "finalResult": null
            }
          }
        ]
      }
    ],
    "pagination": {
      "nextCursor": "eyJpZCI6MTAwfQ",
      "hasMore": true,
      "pageSize": 10
    }
  }
}
Field Type Description
events array<EventResp> Event list; each event's markets returns at most the first 2 markets
pagination PaginationResp Cursor pagination info

1.2 Get a single event

Get the full information for a specified event. The markets field returns all markets under the event (not truncated).

HTTP Request

GET /api/v5/predictions/events/{eventId}

Path Parameters

Parameter Type Required Description
eventId string Yes Event ID

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "id": "100",
    "eventId": "100",
    "negRisk": false,
    "status": "active",
    "eventTitle": "NBA Finals 2026 - Lakers vs Celtics",
    "description": "Who will win the 2026 NBA Championship?",
    "eventIcon": "https://cdn.example.com/events/100.png",
    "volume": "3200000.00",
    "startTime": 1748000000000,
    "endTime": 1750000000000,
    "createdAt": 1710000000000,
    "totalMarketsCount": 3,
    "finalOutcomesMarketId": null,
    "markets": [ ]
  }
}

Returns the full EventResp, with markets containing all markets under the event.

1.3 Get markets

Get all markets under a specified event, without pagination.

HTTP Request

GET /api/v5/predictions/events/{eventId}/markets

Path Parameters

Parameter Type Required Description
eventId string Yes Event ID

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "markets": [
      {
        "id": "1",
        "marketId": "1",
        "negRisk": false,
        "status": "active",
        "settleStage": 0,
        "question": "Will BTC exceed $100k by end of 2026?",
        "shortQuestion": "BTC > $100k?",
        "description": "Resolves YES if BTC price exceeds $100,000 before Jan 1, 2027.",
        "marketIcon": null,
        "bestBid": "0.64",
        "bestAsk": "0.66",
        "lastTradePrice": "0.65",
        "volume": "1250000.00",
        "probability": "0.65",
        "resolutionSources": ["https://coinmarketcap.com"],
        "yesOutcome": {
          "tokenId": "0xabc...001",
          "assetId": "1",
          "name": "Yes",
          "price": "0.65",
          "finalResult": null
        },
        "noOutcome": {
          "tokenId": "0xabc...002",
          "assetId": "2",
          "name": "No",
          "price": "0.35",
          "finalResult": null
        }
      }
    ]
  }
}
Field Type Description
markets array<MarketResp> All markets under the event

1.4 Get a single market

Get the detailed information for a specified market.

HTTP Request

GET /api/v5/predictions/markets/{marketId}

Path Parameters

Parameter Type Required Description
marketId string Yes Market TradeZone ID

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "id": "1",
    "marketId": "1",
    "negRisk": false,
    "status": "resolved",
    "settleStage": 5,
    "question": "Will BTC exceed $100k by end of 2026?",
    "shortQuestion": "BTC > $100k?",
    "description": "Resolves YES if BTC price exceeds $100,000 before Jan 1, 2027.",
    "marketIcon": null,
    "bestBid": null,
    "bestAsk": null,
    "lastTradePrice": "0.98",
    "volume": "1250000.00",
    "probability": null,
    "resolutionSources": ["https://coinmarketcap.com"],
    "yesOutcome": {
      "tokenId": "0xabc...001",
      "assetId": "1",
      "name": "Yes",
      "price": "1.00",
      "finalResult": true
    },
    "noOutcome": {
      "tokenId": "0xabc...002",
      "assetId": "2",
      "name": "No",
      "price": "0.00",
      "finalResult": false
    }
  }
}

Returns the full MarketResp.

1.5 Search events

Full-text search of events by keyword. Returns the matching event list, with cursor pagination.

HTTP Request

GET /api/v5/predictions/events/search

Request Parameters (Query)

Parameter Type Required Default Description
keyword string Yes - Search keyword, matched against event title and description
cursor string No - Pagination cursor; omit on the first request
pageSize int No 10 Items per page (max 50)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "events": [
      {
        "id": "100",
        "eventId": "100",
        "negRisk": false,
        "status": "active",
        "eventTitle": "Will BTC exceed $100k by end of 2026?",
        "description": "This event tracks whether Bitcoin will cross $100,000.",
        "eventIcon": "https://cdn.example.com/events/100.png",
        "volume": "1250000.00",
        "startTime": 1700000000000,
        "endTime": 1798761600000,
        "createdAt": 1710000000000,
        "totalMarketsCount": 1,
        "finalOutcomesMarketId": null,
        "markets": [
          {
            "id": "1",
            "marketId": "1",
            "oddsType": "points",
            "negRisk": false,
            "status": "active",
            "settleStage": 0,
            "question": "Will BTC exceed $100k by end of 2026?",
            "shortQuestion": "BTC > $100k?",
            "description": "Resolves YES if BTC price exceeds $100,000 before Jan 1, 2027.",
            "marketIcon": null,
            "bestBid": "0.64",
            "bestAsk": "0.66",
            "lastTradePrice": "0.65",
            "volume": "1250000.00",
            "probability": "0.65",
            "resolutionSources": ["https://coinmarketcap.com"],
            "yesOutcome": {
              "tokenId": "0xabc...001",
              "assetId": "1",
              "name": "Yes",
              "price": "0.65",
              "finalResult": null
            },
            "noOutcome": {
              "tokenId": "0xabc...002",
              "assetId": "2",
              "name": "No",
              "price": "0.35",
              "finalResult": null
            }
          }
        ]
      }
    ],
    "pagination": {
      "nextCursor": "eyJpZCI6MTAwfQ",
      "hasMore": true,
      "pageSize": 10
    }
  }
}
Field Type Description
events array<EventResp> Event list
pagination PaginationResp Cursor pagination info

2. Price API

2.1 Get ticker

Get the latest ticker for a single prediction market instrument, used to display the last trade price in the order book.

Request Parameters

Parameter Type Required Description
instId String Yes Prediction market yesAssetId

HTTP Request

GET /api/v5/market/ticker?instId={yesAssetId}

Response Parameters

Response example

{
  "code": "0",
  "msg": "",
  "data": [{
    "instType": "SPOT",
    "instId": "{yesAssetId}",
    "last": "0.65",
    "lastSz": "100",
    "askPx": "0.66",
    "askSz": "500",
    "bidPx": "0.64",
    "bidSz": "300",
    "open24h": "0.60",
    "high24h": "0.70",
    "low24h": "0.55",
    "vol24h": "10000",
    "volCcy24h": "6500",
    "sodUtc0": "0.62",
    "sodUtc8": "0.61",
    "ts": "1711900800000"
  }]
}
Field Type Description
instType String Instrument type
instId String Instrument ID
last String Last trade price
lastSz String Last trade size
askPx String Best ask price
askSz String Size at the best ask
bidPx String Best bid price
bidSz String Size at the best bid
open24h String 24h open price
high24h String 24h high price
low24h String 24h low price
vol24h String 24h volume (in contracts)
volCcy24h String 24h volume (in quote currency)
sodUtc0 String Open price at UTC 0
sodUtc8 String Open price at UTC+8
ts String Data update time (Unix ms timestamp)

2.2 Get candlesticks

Query historical candlestick data for a prediction market instrument. Candlesticks are grouped and returned by the requested bar size; at most 1,440 candles are available per bar size.

Request Parameters

Parameter Type Required Description
instId String Yes Prediction market yesAssetId
bar String No Bar size, default 1m. Allowed values: 1m 3m 5m 15m 30m 1H 2H 4H 6H 12H 1D 1W 1M
after String No Pagination cursor; return data before this timestamp (Unix ms)
before String No Pagination cursor; return data after this timestamp (Unix ms)
limit String No Items per page, max 100, default 100

HTTP Request

GET /api/v5/market/candles?instId={yesAssetId}&bar=1m&limit=100

Response Data

(data is a 2D array; each record's field order is as follows)

Response example

{
  "code": "0",
  "msg": "",
  "data": [
    ["1711900800000", "0.65", "0.67", "0.63", "0.66", "2000", "1300", "1300", "1"],
    ["1711900740000", "0.63", "0.66", "0.62", "0.65", "1800", "1170", "1170", "1"]
  ]
}
Index Field Type Description
0 ts String Candle open time (Unix ms timestamp)
1 o String Open price
2 h String High price
3 l String Low price
4 c String Close price
5 vol String Volume (in contracts)
6 volCcy String Volume (in quote currency)
7 volCcyQuote String Volume (in quote currency)
8 confirm String Candle state: 0 not confirmed, 1 confirmed

2.3 Get order book

Query a bid/ask depth snapshot for a prediction market instrument.

Request Parameters

Parameter Type Required Description
instId String Yes Prediction market yesAssetId
sz String No Number of depth levels; max 400 per side, i.e. up to 800 across both sides. If omitted, defaults to 1 level.

HTTP Request

GET /api/v5/market/pm-books?instId={yesAssetId}&sz=400

Response Parameters

Response example

{
  "code": "0",
  "msg": "",
  "data": [
    {
      "asks": [
        ["67364.1","0.45478048","5"]
      ],
      "bids": [
        ["67364","1.72315936", "17"]
      ],
      "ts": "1774943488756",
      "seqId": 74487243135
    }
  ]
}

Field Type Description
asks Array Ask levels, sorted by price low to high
bids Array Bid levels, sorted by price high to low
ts String Depth snapshot time (Unix ms timestamp)
seqId Number Order book version number; not relevant for prediction markets

Each record in the asks / bids arrays has the format: [price, size, order count]

3. Orders API

3.1 Place order

Submit a signed order request. The developer constructs the calldata and signs it with their own wallet private key (ECDSA); after the server validates it, the order is written to the database and forwarded directly to TradeZone (it does not go through the AA Wallet).

HTTP Request

POST /api/v5/predictions/orders

Request Body

Request example

{
  "action": {
    "type": "placeOrder",
    "grouping": "na",
    "orders": [{
      "assetId": "1",
      "marketType": "prediction",
      "side": "buy",
      "price": "0.65",
      "size": "100",
      "reduceOnly": false,
      "clientOrderId": "0x0197a98c91312671ca83f15ccbd5186f",
      "orderType": { "limit": { "tif": "gtc" } }
    }]
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
Field Type Required Description
action object Yes Order action
action.type string Yes Fixed "placeOrder"
action.grouping string Yes Fixed "na"
action.orders array Yes Order list (one order per request)
action.orders[].assetId string Yes TradeZone asset ID (e.g. "1")
action.orders[].marketType string Yes Fixed "prediction"
action.orders[].side string Yes "buy" / "sell"
action.orders[].price string Yes Order price (e.g. "0.65")
action.orders[].size string Yes Order size (e.g. "100")
action.orders[].clientOrderId string Yes Client order ID; used to distinguish region
action.orders[].reduceOnly boolean No Whether reduce-only, default false
action.orders[].sizeType string No "base" (default) / "quote"
action.orders[].orderType object Yes Order type
action.orders[].orderType.limit.tif string Yes "gtc" / "gtd" / "ioc" / "fok" / "alo"
nonce long Yes Request timestamp (ms), anti-replay
signature object Yes Signature object
signature.Ecdsa object Yes ECDSA signature components
signature.Ecdsa.r string Yes Signature r value ("0x...")
signature.Ecdsa.s string Yes Signature s value ("0x...")
signature.Ecdsa.v int Yes Signature v value (0 or 1)

TIF (Time In Force)

JSON value Trade structure Description
"gtc" "tif": "gtc" Good-Til-Cancel; the order stays until filled or cancelled
"gtd" "tif": { "gtd": { "expiresAfter": 1700000005000 } }, Good-Til-Date; the order stays until it expires at expiresAfter, or is filled/cancelled
"ioc" "tif": "ioc" Immediate-Or-Cancel (i.e. FAK); fills as much as possible immediately, then cancels the remainder. For simulated market orders only; must be paired with a price protection upper/lower bound (price)
"fok" "tif": "fok" Fill-Or-Kill; fully filled or fully cancelled. For simulated market orders only; must be paired with a price protection upper/lower bound (price)
"alo" "tif": "alo" Add-Liquidity-Only (Post-Only); maker-only order, rejected if it would fill immediately

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...789"
  }
}
Field Type Description
txHash string TradeZone transaction hash

3.2 Cancel a single order

Cancel an active order. The developer constructs the cancel calldata and signs it; after validating the order's ownership and status, the server submits it to TradeZone.

HTTP Request

POST /api/v5/predictions/orders/cancel

Request Body

Request example

{
  "action": {
    "type": "cancel",
    "cancels": [{
      "assetId": "1",
      "marketType": "prediction",
      "oid": "12345",
      "clientOrderId": "0x"
    }]
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
Field Type Required Description
action object Yes Cancel action
action.type string Yes Fixed "cancel"
action.cancels array Yes Cancel list (one per request)
action.cancels[].assetId string Yes TradeZone asset ID (e.g. "1")
action.cancels[].marketType string Yes Fixed "prediction"
action.cancels[].oid string No Order ID; exactly one of oid and clientOrderId is required
action.cancels[].clientOrderId string No Client order ID; exactly one of oid and clientOrderId is required
nonce long Yes Request timestamp (ms)
signature object Yes Signature object
signature.Ecdsa object Yes ECDSA signature components
signature.Ecdsa.r string Yes Signature r value ("0x...")
signature.Ecdsa.s string Yes Signature s value ("0x...")
signature.Ecdsa.v int Yes Signature v value (0 or 1)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
Field Type Description
txHash string TradeZone transaction hash

3.3 Get a single order

Get the full details of a specified order (active or historical).

HTTP Request

GET /api/v5/predictions/orders/{orderId}

Path Parameters

Parameter Type Required Description
orderId string Yes Order ID

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "id": "1",
    "oid": "1",
    "clientOrderId": "1",
    "marketId": "1",
    "assetId": "1",
    "side": "BUY",
    "orderType": "GTC",
    "sizeType": "BASE",
    "size": "100",
    "price": "0.65",
    "expiration": null,
    "txHash": "0xdef...789",
    "status": "ACTIVE",
    "filledSize": "40",
    "filledAmount": "26",
    "failReason": null,
    "cancelReason": null,
    "oddsType": "points",
    "createdAt": "1710000000000",
    "updatedAt": "1710000000000"
  }
}
Field Type Description
id string Order ID
oid string Order oid
clientOrderId string Order clientOrderId
marketId string Market ID
tokenId string YES/NO token address
assetId string TradeZone asset ID
side string BUY / SELL
orderType string GTC / GTD / FOK / IOC / POST_ONLY
sizeType string BASE / QUOTE
size string Original order size
price string Order price
expiration string GTD expiry timestamp (ms); null for non-GTD
txHash string Transaction hash for the current stage
status string Order status (see table below)
filledSize string Filled token amount
filledAmount string Filled xp amount
failReason string Failure reason (present only when status = FAILED)
cancelReason string Cancel reason (system-initiated cancellations)
oddsType string Odds type: points = points odds
createdAt string Order creation timestamp (ms)
updatedAt string Last update timestamp (ms)

Order Status

Status Description
PENDING_PLACE Submitted, waiting for on-chain confirmation
ACTIVE Active open order
PENDING_CANCEL Cancellation submitted, waiting for on-chain confirmation
FILLED Fully filled
PARTIALLY_FILLED Partially filled then cancelled or expired
FAILED On-chain transaction failed
CANCELLED Cancelled by user or system
EXPIRED GTD order expired

3.4 Get user orders

Query the current authenticated user's order list, with filtering and pagination.

HTTP Request

GET /api/v5/predictions/orders

Request Parameters (Query)

Parameter Type Required Description
marketId string No Filter by market ID
status string No open (PENDING_PLACE / ACTIVE / PENDING_CANCEL), closed (FILLED / PARTIALLY_FILLED / CANCELLED / EXPIRED / FAILED); defaults to open
cursor string No Pagination cursor
limit int No Items per page (default 20, max 50)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "list": [
      {
        "id": "1",
        "oid": "1",
        "clientOrderId": "1",
        "marketId": "string",
        "assetId": "1",
        "side": "BUY",
        "orderType": "GTC",
        "sizeType": "BASE",
        "size": "100",
        "price": "0.65",
        "expiration": null,
        "txHash": "0xdef...789",
        "status": "ACTIVE",
        "filledSize": "40",
        "filledAmount": "26",
        "failReason": null,
        "cancelReason": null,
        "oddsType": "points",
        "createdAt": "1710000000000",
        "updatedAt": "1710000000000"
      }
    ],
    "nextCursor": "eyJpZCI6MTIzfQ",
    "hasNext": false
  }
}
Field Type Description
list[].id string Order ID
list[].marketId string Market ID
list[].oid string Order oid
list[].clientOrderId string Order clientOrderId
list[].tokenId string YES/NO token address
list[].assetId string TradeZone asset ID
list[].side string BUY / SELL
list[].orderType string GTC / GTD / FOK / IOC / POST_ONLY
list[].sizeType string BASE / QUOTE
list[].size string Original order size
list[].price string Order price
list[].expiration string GTD expiry timestamp (ms); null for non-GTD
list[].txHash string Transaction hash for the current stage
list[].status string Order status: PENDING_PLACE / ACTIVE / PENDING_CANCEL / FILLED / PARTIALLY_FILLED / FAILED / CANCELLED / EXPIRED
list[].filledSize string Filled token amount
list[].filledAmount string Filled xp amount
list[].failReason string Failure reason (present only when status = FAILED)
list[].cancelReason string Cancel reason (system-initiated cancellations)
list[].oddsType string Odds type: points = points odds
list[].createdAt String Order creation timestamp (ms)
list[].updatedAt string Last update timestamp (ms)
nextCursor string Next-page cursor
hasNext boolean Whether more data is available

3.5 Cancel all / market orders

Cancel the current authenticated user's active orders. When assetIds is an empty list, cancel orders across all markets; when specific values are passed, cancel only the orders in the markets of the corresponding assets.

HTTP Request

POST /api/v5/predictions/orders/cancel-all

Request Body

Request example (cancel by market)

{
  "action": {
    "type": "cancelAll",
    "assetIds": [1, 2, 5],
    "marketType": "prediction"
  },
  "nonce": 1708929600000,
  "expiresAfter": 1708929660000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}

Request example (cancel all)

{
  "action": {
    "type": "cancelAll",
    "assetIds": [],
    "marketType": "prediction"
  },
  "nonce": 1708929600000,
  "expiresAfter": 1708929660000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
Field Type Required Description
action object Yes Cancel action
action.type string Yes Fixed "cancelAll"
action.assetIds array Yes Asset ID list; pass an empty list to cancel all orders, or specific values to cancel by the corresponding markets
action.marketType string Yes Market type, fixed "prediction"
nonce long Yes Request timestamp (ms)
expiresAfter long Yes Expiry timestamp (ms)
signature object Yes Signature object
signature.Ecdsa object Yes ECDSA signature components
signature.Ecdsa.r string Yes Signature r value ("0x...")
signature.Ecdsa.s string Yes Signature s value ("0x...")
signature.Ecdsa.v int Yes Signature v value (0 or 1)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
Field Type Description
txHash string TradeZone transaction hash

3.6 Send heartbeat

A heartbeat mechanism that protects the developer's active orders. The request body carries a pre-signed cancelAll action with nonce set to the current time + 5 minutes. The server stores this signature; if the developer does not renew the heartbeat within 5 minutes, the system automatically uses this signature to cancel all orders.

The developer should keep calling this endpoint (recommended interval < 5 minutes), each time overwriting the previous pre-signature with a new nonce (current time + 5 minutes).

HTTP Request

POST /api/v5/predictions/heartbeat

Request Body

Request example

{
  "action": {
    "type": "cancelAll",
    "assetIds": [],
    "marketType": "prediction"
  },
  "nonce": 1708929900000,
  "expiresAfter": 1708929960000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
Field Type Required Description
action object Yes Cancel action; same structure as POST /api/v5/predictions/orders/cancel-all
action.type string Yes Fixed "cancelAll"
action.assetIds array Yes Always pass an empty list []; cancels all orders when the heartbeat fires
action.marketType string Yes Market type, fixed "prediction"
nonce long Yes Timestamp of current time + 5 minutes (ms)
expiresAfter long Yes Expiry timestamp (ms)
signature object Yes Signature object
signature.Ecdsa object Yes ECDSA signature components
signature.Ecdsa.r string Yes Signature r value ("0x...")
signature.Ecdsa.s string Yes Signature s value ("0x...")
signature.Ecdsa.v int Yes Signature v value (0 or 1)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "serverTimestamp": 1708929600000,
    "expireAt": 1708929900000
  }
}
Field Type Description
serverTimestamp long Server current timestamp (ms)
expireAt long This heartbeat's expiry timestamp (ms)

Order Semantics

Order Mode Support Matrix

Support matrix

Scenario sizeType tif size price semantics Supported
Limit buy base gtc / gtd / ioc / fok / alo user-entered shares user-entered limit price βœ…
Limit sell base gtc / gtd / ioc / fok / alo user-entered shares user-entered limit price βœ…
Market buy (by size) base ioc / fok user-entered shares system-simulated worst fill price (protection price) from the book βœ…
Market buy (by amount) quote ioc user-entered notional amount (xp) system-simulated worst fill price (protection price) from the book βœ…
Market sell (by size) base ioc / fok user-entered shares system-simulated worst fill price (protection price) from the book βœ…
Market buy (by amount) + FOK quote fok β€” β€” ❌ Not supported

Key rules

  1. In the prediction market, ioc is equivalent to the commonly used fak: it fills as much as possible, cancels the remainder, and never leaves a resting order.

  2. fok is only supported when ordering by shares: the combination of sizeType=quote and tif=fok is rejected by the server.

  3. alo (Post Only) applies only to limit orders: combining it with sizeType=quote or a market order is rejected.

  4. A market order is not an "unprotected order": at order time the system simulates a worstPrice from the book and submits it to TradeZone as the price. The actual fill price will never be worse than worstPrice.

  5. tif=gtd must include expiration: the field path is action.orders[].orderType.limit.expiration, of type String (Unix ms timestamp, 13 digits), an absolute time. If missing or invalid, it currently throws 10001 PARAM_ERROR.

Error returns for unsupported combinations

Trigger Current error code
sizeType=quote and tif=fok 10001 PARAM_ERROR
sizeType=quote and tif ∈ {gtc, gtd, alo} 10001 PARAM_ERROR
tif=gtd without expiration 10001 PARAM_ERROR

Minimum Order Amount & Liquidation Exception

Minimum order amount rule

Sell-All Exception

Price Unit & Precision Rules

Valid range

Condition Allowed
price ≀ 0 Rejected
price β‰₯ 1 Rejected
0 < price < 1 Allowed, proceeds to precision validation

Segmented precision rules

Precision is split into two segments by [0.04, 0.96] and outside it:

Range Max decimal places Equivalent cent range Example (allowed) Example (rejected)
0 < price < 0.04 3 places (0, 4) cents 0.001 / 0.025 / 0.039 0.0125 (4 places)
0.04 ≀ price ≀ 0.96 2 places (tick=0.01) [4, 96] cents 0.04 / 0.25 / 0.96 0.045 / 0.123 / 0.961
0.96 < price < 1 3 places (96, 100) cents 0.962 / 0.9875 / 0.999 0.99875 (4 places)

ClientOrderId Generation Specification

clientOrderId (cloid for short) is the order's unique identifier on chain. On-chain events are broadcast globally, and consumers in each region use the region/env prefix in the cloid to filter out the orders that belong to them. Any cloid generated by any integrator must follow this specification, otherwise orders will be misattributed.

Format

The format is 0x{region}{env}{random}:

Segment Length (chars) Content Description
prefix 2 0x Fixed literal
region 1 1 hex char Region code
env 1 1 hex char Environment code
random 30 30 hex chars Random number (lowercase)

The total length is fixed at 34 characters, all lowercase hex (0-9, a-f).

region / env code table

region Meaning
0 HK
1 US
2 EU
env Meaning
1 Production

Each value is a single hex char; currently 0/1/2 are used, and future expansion will not exceed f (15).

random generation requirements

Reference implementations

Java (UUID-derived)

UUID u = UUID.randomUUID();
long hi = u.getMostSignificantBits();
long lo = u.getLeastSignificantBits();
StringBuilder sb = new StringBuilder("0x");
sb.append(Integer.toHexString(region & 0xF));
sb.append(Integer.toHexString(env & 0xF));
for (int i = 14; i >= 0; i--) {
    sb.append(Character.forDigit((int)((hi >>> (i * 4)) & 0xF), 16));
}
for (int i = 14; i >= 0; i--) {
    sb.append(Character.forDigit((int)((lo >>> (i * 4)) & 0xF), 16));
}
return sb.toString();

Node.js

const crypto = require('crypto');
function generate(region, env) {
  const rand = crypto.randomBytes(15).toString('hex'); // 30 hex
  return `0x${region.toString(16)}${env.toString(16)}${rand}`;
}
import secrets
def generate(region: int, env: int) -> str:
    return f"0x{region:x}{env:x}{secrets.token_hex(15)}"

Go

import (
    "crypto/rand"
    "encoding/hex"
    "fmt"
)

func Generate(region, env int) (string, error) {
    b := make([]byte, 15)
    if _, err := rand.Read(b); err != nil {
        return "", err
    }
    return fmt.Sprintf("0x%x%x%s", region, env, hex.EncodeToString(b)), nil
}

Server-side parsing and attribution

The receiver decides whether a cloid belongs to the current environment using the following rules:

if cloid is null/empty
  or length < 4
  or does not start with "0x"
  or cloid[2], cloid[3] are not valid hex chars:
    treat as HK-staging (region=0, env=0)  <- fallback rule
else:
    region = parse_hex(cloid[2])
    env    = parse_hex(cloid[3])
    check whether it equals the current environment's (region, env)

FAQ

4. Positions Operations API

4.1 Split (xp β†’ YES + NO)

Split xp into an equal amount of YES and NO conditional token pairs. The developer constructs and signs the calldata; after validation, the server submits it directly to TradeZone.

HTTP Request

POST /api/v5/predictions/positions/split

Request Body

Request example

{
  "action": {
    "type": "predictionSplit",
    "marketId": "1",
    "size": "100000000"
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
Field Type Required Description
action object Yes Split action
action.type string Yes Fixed "predictionSplit"
action.marketId string Yes Market ID (e.g. "1")
action.size string Yes xp amount (minimal-unit string, e.g. "100000000")
nonce long Yes Request timestamp (ms), anti-replay
signature object Yes Signature object
signature.Ecdsa object Yes ECDSA signature components
signature.Ecdsa.r string Yes Signature r value ("0x...")
signature.Ecdsa.s string Yes Signature s value ("0x...")
signature.Ecdsa.v int Yes Signature v value (0 or 1)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
Field Type Description
txHash string TradeZone transaction hash

4.2 Merge (YES + NO β†’ xp)

Merge equal amounts of YES and NO conditional tokens back into xp (the inverse of Split). The developer constructs and signs the calldata; after validation, the server submits it directly to TradeZone.

HTTP Request

POST /api/v5/predictions/positions/merge

Request Body

Request example

{
  "action": {
    "type": "predictionMerge",
    "marketId": "1",
    "size": "100000000"
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
Field Type Required Description
action object Yes Merge action
action.type string Yes Fixed "predictionMerge"
action.marketId string Yes Market ID (e.g. "1")
action.size string Yes Merge amount (minimal-unit string, e.g. "100000000")
nonce long Yes Request timestamp (ms), anti-replay
signature object Yes Signature object
signature.Ecdsa object Yes ECDSA signature components
signature.Ecdsa.r string Yes Signature r value ("0x...")
signature.Ecdsa.s string Yes Signature s value ("0x...")
signature.Ecdsa.v int Yes Signature v value (0 or 1)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
Field Type Description
txHash string TradeZone transaction hash

4.3 Redeem (redeem xp after settlement)

After a market settles, redeem xp 1:1 with the winning conditional tokens. The redeem amount is not passed in; by default it redeems all of the user's winning tokens.

HTTP Request

POST /api/v5/predictions/positions/redeem

Request Body

Request example

{
  "action": {
    "type": "predictionRedeem",
    "marketId": "1"
  },
  "nonce": 1708929600000,
  "signature": {
    "Ecdsa": {
      "r": "0x...",
      "s": "0x...",
      "v": 1
    }
  }
}
Field Type Required Description
action object Yes Redeem action
action.type string Yes Fixed "predictionRedeem"
action.marketId string Yes Market ID (e.g. "1")
nonce long Yes Request timestamp (ms), anti-replay
signature object Yes Signature object
signature.Ecdsa object Yes ECDSA signature components
signature.Ecdsa.r string Yes Signature r value ("0x...")
signature.Ecdsa.s string Yes Signature s value ("0x...")
signature.Ecdsa.v int Yes Signature v value (0 or 1)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "txHash": "0xdef...abc"
  }
}
Field Type Description
txHash string TradeZone transaction hash

5. Trade History API

5.1 Query trades

Query the current authenticated user's trade (fill) records. The server resolves the userId from the login session and internally converts it to an address for the query.

HTTP Request

GET /api/v5/predictions/trades

Request Parameters (Query)

Parameter Type Required Description
marketId string No Filter by market ID
side string No Filter by side: BUY / SELL
startTime long No Start timestamp (ms), inclusive
endTime long No End timestamp (ms), exclusive
cursor string No Pagination cursor
limit int No Items per page (default 20, max 100)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "list": [
      {
        "tradeId": "1",
        "orderId": "1",
        "marketId": "1",
        "tokenId": "0xabc...001",
        "side": "BUY",
        "size": "50",
        "amount": "32.5",
        "price": "0.65",
        "fee": "0.065",
        "role": "TAKER",
        "txHash": "0xdef...789",
        "createdAt": 1710000030000
      }
    ],
    "nextCursor": "eyJpZCI6MTAwMDF9",
    "hasNext": false
  }
}
Field Type Description
list[].tradeId string Trade record ID
list[].orderId string Associated order ID
list[].marketId string Market ID
list[].tokenId string Token address (YES or NO)
list[].side string BUY / SELL
list[].size string Filled token amount
list[].amount string Filled xp amount
list[].price string Fill price
list[].fee string Fee (xp)
list[].role string MAKER / TAKER
list[].txHash string On-chain transaction hash
list[].createdAt string Trade timestamp (ms)
nextCursor string Next-page cursor
hasNext boolean Whether more data is available

6. Positions Query API

6.1 Query current positions

Query the current authenticated user's active positions.

HTTP Request

GET /api/v5/predictions/positions?status=open

6.2 Query closed positions

Query the current authenticated user's exited or settled positions.

HTTP Request

GET /api/v5/predictions/positions?status=closed

6.3 Query positions in a specified market

Query the current authenticated user's positions in a specified market.

HTTP Request

GET /api/v5/predictions/positions?marketId={marketId}

6.4 Unified endpoint

All three scenarios above are implemented through the same endpoint + query parameter combinations:

HTTP Request

GET /api/v5/predictions/positions

Request Parameters (Query)

Parameter Type Required Description
status string No open (active positions), closed (liquidated); returns all if omitted
marketId long No Filter by market ID
cursor string No Pagination cursor
limit int No Items per page (default 20, max 100)

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": {
    "list": [
      {
        "id": "100001",
        "tokenId": "0xabc...001",
        "marketId": "1",
        "tokenIndex": "1",
        "tokenName": "Yes",
        "size": "500",
        "availableSize": "480",
        "value": "325",
        "avgPrice": "0.62",
        "unRealizedPnl": "15",
        "unRealizedPnlPercentage": "0.048",
        "title": "Will BTC exceed $100k by end of 2026?",
        "icon": "https://cdn.example.com/markets/1.png",
        "eventId": "100",
        "winningToken": null,
        "positionStatus": 1,
        "oddsType": "points",
        "curPrice": "0.65",
        "realizedPnl": "12.5",
        "realizedPnlPercentage": "0.04"
      }
    ],
    "nextCursor": "eyJpZCI6MTAwMDAxfQ",
    "hasNext": false
  }
}
Field Type Description
list[].id string Position ID
list[].tokenId string Token ID
list[].marketId string Market ID
list[].tokenIndex string Token direction ("1" = YES, "2" = NO)
list[].tokenName string Token name ("Yes" / "No")
list[].size string Position size (= remain)
list[].availableSize string Available position size (= remain βˆ’ frozen, the amount not locked by resting SELL orders)
list[].value string Current value (curPrice Γ— size)
list[].avgPrice string Weighted average entry cost
list[].unRealizedPnl string Unrealized PnL
list[].unRealizedPnlPercentage string Unrealized PnL percentage
list[].title string Market question text
list[].icon string Market icon URL
list[].eventId string Parent event ID
list[].winningToken string Winning token ID after settlement; null before settlement
list[].positionStatus integer Position status code (see PositionStatusEnum)
list[].oddsType string Odds type: points = points odds
list[].curPrice string Current real-time token price
list[].realizedPnl string Realized PnL
list[].realizedPnlPercentage string Realized PnL percentage
nextCursor string Next-page cursor
hasNext boolean Whether more data is available

7. Account Balance API

7.1 Query account balance

Query the current authenticated user's xp balance.

HTTP Request

GET /api/v5/predictions/balance

Request parameters: none

Response (data):

Response example

{
  "code": 0,
  "message": "OK",
  "data": [
    { "oddsType": "points", "balance": "2.2",  "available": "2.2"  }
  ]
}

data is an array; each element corresponds to the balance of one odds type:

Field Type Description
oddsType string Odds type: points = points odds
balance string Total balance
available string Available balance (total balance βˆ’ frozen amount)

8. Rate Limits

Events & Markets API

Endpoint Limit
GET /api/v5/predictions/events 20 times / 1s
GET /api/v5/predictions/events/{eventId} 20 times / 1s
GET /api/v5/predictions/events/{eventId}/markets 20 times / 1s
GET /api/v5/predictions/markets/{marketId} 20 times / 1s
GET /api/v5/predictions/events/search 10 times / 1s

Price API (market data)

Endpoint Limit
GET /api/v5/market/ticker 10 times / 1s
GET /api/v5/market/candles 20 times / 1s
GET /api/v5/market/pm-books 20 times / 1s

Orders API

Query

Endpoint Limit
GET /api/v5/predictions/orders/{orderId} 20 times / 1s
GET /api/v5/predictions/orders 20 times / 1s

Write

Endpoint Limit
POST /api/v5/predictions/orders 50 times / 1s
POST /api/v5/predictions/orders/cancel 20 times / 1s
POST /api/v5/predictions/orders/cancel-all 1 time / 1h
POST /api/v5/predictions/heartbeat 1 time / 1s

Positions Operations API

Endpoint Limit
POST /api/v5/predictions/positions/split 5 times / 1s
POST /api/v5/predictions/positions/merge 5 times / 1s
POST /api/v5/predictions/positions/redeem 5 times / 1s

Trade History / Positions Query / Balance

Endpoint Limit
GET /api/v5/predictions/trades 10 times / 1s
GET /api/v5/predictions/positions 10 times / 1s
GET /api/v5/predictions/balance 10 times / 1s

Rate-limit response

SDK API Reference

A complete reference for every public method, request body, response struct, error variant, and WebSocket channel exposed by the okx-outcomes-sdk Rust crate. The README is a quick start; this document is the detailed version.

Conventions used throughout:

1. Installation

Add the dependency via Git in your Cargo.toml:

Cargo.toml dependencies

[dependencies]
okx-outcomes-sdk = { git = "https://github.com/okx/outcomes-sdk.git", features = ["signing", "websocket"] }

2. Client Construction

Module: okx_outcomes_sdk::{OutcomesSdkClient, ApiCredentials}.

pub struct ApiCredentials {
    pub api_key:    String, // value of the OK-ACCESS-KEY header
    pub secret_key: String, // HMAC-SHA256 signing secret; never transmitted
    pub passphrase: String, // value of the OK-ACCESS-PASSPHRASE header
}

impl OutcomesSdkClient {
    pub fn with_credentials(creds: ApiCredentials) -> Self;
    pub fn with_credentials_and_url(creds: ApiCredentials, base_url: impl Into<String>) -> Self;
}

Base URL resolution order: the explicit argument passed to with_credentials_and_url > the PREDICTIONS_API_BASE environment variable > https://www.okx.com. Endpoint constants are full absolute paths (/api/v5/predictions/..., /api/v5/market/...) concatenated with the base URL, so a single host configuration covers both prediction and market-data calls.

3. Errors

Module: okx_outcomes_sdk::SdkError.

Every fallible call returns Result<T, SdkError>. The enum has seven variants:

pub enum SdkError {
    /// Network failure: connection refused, DNS, timeout, TLS handshake failure.
    /// Transport layer; retrying is usually safe.
    Http(reqwest::Error),

    /// The server returned a non-zero business error code in the response envelope.
    /// `code` is the upstream OKX business code; decide retry / backoff / abort based on it.
    Api { code: i64, message: String },

    /// The response body could not be deserialized to the expected schema.
    /// Usually means the SDK and server versions are mismatched.
    Deserialize(serde_json::Error),

    /// WS connect, send, login, or close failed.
    /// Includes login rejection (`60xxx` error codes) and timeout during login.
    WebSocket { message: String },

    /// Reserved for callers that bypass the public constructors and end up without credentials.
    /// Clients constructed via `with_credentials` / `with_credentials_and_url` never
    /// return this variant β€” they always carry credentials.
    NotAuthenticated { hint: String },

    /// Invalid URL, missing state, or an unexpected internal precondition.
    /// Almost always a programming error by the caller.
    Internal { message: String },

    /// Serializing the request body or WS payload failed.
    /// Rare in practice (only triggered by, e.g., non-finite floats).
    Serialization { message: String },
}

Each variant implements Display via thiserror, so format!("{e}") produces a readable one-line log.

4. Events and Markets

Module: okx_outcomes_sdk::models::event::*. The API is implemented in okx_outcomes_sdk::api::events.

4.1 get_events

Get a paginated list of prediction market events.

pub async fn get_events(
    &self,
    status:    Option<&str>,   // "active" (default) | "resolved"
    tag:       Option<&str>,   // sports tag ID
    league_id: Option<&str>,   // sports league ID
    sort:      Option<&str>,   // "volume" | "volume_24h" (default) | "ending_soon" | "newest"
    cursor:    Option<&str>,   // pagination cursor from the previous `EventsResponse.pagination.next_cursor`
    page_size: Option<i32>,    // items per page, max 50 (default 10)
) -> Result<EventsResponse, SdkError>;

pub struct EventsResponse {
    events:     Vec<EventObject>,
    pagination: Pagination,    // see Common Types
}

pub struct EventObject {
    id:                       String,         // globally unique event ID
    event_id:                 String,         // event ID
    neg_risk:                 bool,           // mutually exclusive (negRisk) event
    status:                   EventStatus,    // Active / Paused / Resolved / Unknown
    event_title:              String,         // display title
    description:              String,         // long description
    event_icon:               Option<String>, // icon URL
    volume:                   String,         // total volume across all markets
    start_time:               Option<i64>,    // trading start time (ms)
    end_time:                 Option<i64>,    // trading end time (ms)
    created_at:               i64,            // creation timestamp (ms)
    total_markets_count:      i32,            // number of markets under this event
    final_outcomes_market_id: Option<String>, // winning market ID after settlement
    markets:                  Vec<MarketObject>,      // the list endpoint returns at most 2 markets;
                                                      // call `get_event_markets` for the full list
}

Search events and markets by keyword.

pub async fn search(
    &self,
    keyword:   &str,           // free-text query (required)
    cursor:    Option<&str>,   // pagination cursor
    page_size: Option<i32>,    // default 10
) -> Result<EventsResponse, SdkError>;

Response: EventsResponse (same shape as get_events).

4.3 get_event

Get a single event, with its full market list inlined.

pub async fn get_event(&self, event_id: &str) -> Result<EventObject, SdkError>;

Returns Api { code: 40404, ... } if the event ID does not exist.

4.4 get_event_markets

Get all markets of an event (no pagination, no list cap).

pub async fn get_event_markets(&self, event_id: &str) -> Result<MarketsResponse, SdkError>;

pub struct MarketsResponse {
    markets: Vec<MarketObject>,
}

pub struct MarketObject {
    id:                        String,         // globally unique market ID
    market_id:                 String,         // market ID
    neg_risk:                  bool,           // negRisk market flag
    status:                    MarketStatus,   // Active / Paused / Settling / Resolved / Unknown
    settle_stage:              i32,            // 0 = not started, 5 = settled
    question:                  String,         // full market question
    short_question:            Option<String>, // short question
    description:               String,         // long description
    market_icon:               Option<String>, // icon URL
    start_time:                String,         // trading start time (ms, string)
    end_time:                  String,         // trading end time (ms, string)
    resolve_start_at:          String,         // settlement window start time (ms, string)
    resolve_at:                String,         // settlement time (ms, string)
    best_bid:                  Option<String>, // decimal in [0, 1]; None if no bids
    best_ask:                  Option<String>, // decimal in [0, 1]; None if no asks
    last_trade_price:          Option<String>, // None before the first trade
    volume:                    String,         // market volume
    probability:               Option<String>, // YES outcome probability in [0, 1]
    resolution_sources:        Vec<String>,    // source URLs used for resolution
    yes_outcome:               OutcomeObject,
    no_outcome:                OutcomeObject,
}

pub struct OutcomeObject {
    token_id:     Option<String>, // conditional token address; None before deployment
    asset_id:     Option<String>, // asset ID; used as `inst_id` in order placement / market data
    name:         String,         // "Yes" or "No"
    price:        String,         // decimal in [0, 1]
    final_result: Option<bool>,   // Some(true) = won, Some(false) = lost, None = not settled
}

4.5 get_market

Get a single market.

pub async fn get_market(&self, market_id: &str) -> Result<MarketObject, SdkError>;

5. Account: Balance

Module: okx_outcomes_sdk::models::balance::*. The API is in okx_outcomes_sdk::api::balance.

5.1 get_balance

Returns the authenticated user's available balance grouped by odds type.

pub async fn get_balance(&self) -> Result<BalanceResponse, SdkError>;

pub type BalanceResponse = Vec<BalanceEntry>;

pub struct BalanceEntry {
    odds_type: OddsType, // Spots / Points / Unknown (spots = real, points = points)
    balance:   String, // total balance (unit determined by odds_type)
    available: String, // available balance (total balance - amount frozen by open orders)
}

6. Account: Orders

Module: okx_outcomes_sdk::models::order::*. The API is in okx_outcomes_sdk::api::orders.

6.1 place_order

Submit a signed limit (or trigger) order.

pub async fn place_order(&self, req: &PlaceOrderRequest) -> Result<TxHashResponse, SdkError>;

struct PlaceOrderRequest {
    action:    PlaceOrderAction,
    nonce:     i64,                // millisecond timestamp, anti-replay
    signature: SignatureWrapper,   // { Ecdsa: { r, s, v } }
}

struct PlaceOrderAction {
    action_type: String,           // always "placeOrder"
    grouping:    String,           // always "na"
    orders:      Vec<OrderItem>,
}

struct OrderItem {
    asset_id:        String,           // outcome assetId
    side:            SigningOrderSide,  // Buy / Sell (wire side lowercase; bytes used for the EIP-712 hash)
    market_type:     String,           // always "prediction"
    client_order_id: Option<String>,   // 34-char client order ID; see Signing > Client Order ID
    price:           String,           // decimal in [0, 1]
    reduce_only:     bool,
    size:            String,           // decimal
    size_type:       SizeType,         // Base (default, omitted on wire) / Quote
    order_type:      OrderTypeSpec,    // { limit: { tif } }
}

struct OrderTypeSpec   { limit: LimitOrderType }
struct LimitOrderType  { tif: LimitTif /* Gtc | Gtd { expires_after } | Ioc | Fok | Alo */ }

Response: TxHashResponse { tx_hash: String }.

Build a typed signing::types::OrderRequest, sign it with signing::sign_to_wrapper, then derive the wire-side OrderItem via OrderItem::from(&OrderRequest) so the signed bytes and the JSON body cannot drift. See Signing.

6.2 cancel_order

Cancel an active order (by server ID or client order ID).

pub async fn cancel_order(&self, req: &CancelOrderRequest) -> Result<TxHashResponse, SdkError>;

struct CancelOrderRequest {
    action:    CancelOrderAction,
    nonce:     i64,
    signature: SignatureWrapper,
}

struct CancelOrderAction {
    action_type: String,           // always "cancel"
    cancels:     Vec<CancelItem>,
}

struct CancelItem {
    asset_id:     String,
    market_type:  String,           // "prediction"
    // exactly one of the two:
    by: CancelBy,                   // flattened-serialized as { "oid": ... } or { "clientOrderId": ... }
}

enum CancelBy {
    Oid           { oid: String },          // server-assigned, decimal string
    ClientOrderId { client_order_id: String }, // 34-char client order ID, hex with 0x prefix
}

Response: TxHashResponse { tx_hash: String }.

6.3 cancel_all

Cancel all active orders, or all active orders under a given set of asset IDs.

pub async fn cancel_all(&self, req: &CancelAllRequest) -> Result<TxHashResponse, SdkError>;

struct CancelAllRequest {
    action:        CancelAllAction,
    nonce:         i64,
    expires_after: i64,            // expiry timestamp (ms), required
    signature:     SignatureWrapper,
}

struct CancelAllAction {
    action_type: String,            // always "cancelAll"
    asset_ids:   Vec<String>,       // empty = all markets; non-empty = filter
    market_type: String,            // "prediction"
}

Response: TxHashResponse.

6.4 heartbeat

Refresh the dead-man's switch that protects active orders (auto-cancels on disconnect).

pub async fn heartbeat(&self, req: &CancelAllRequest) -> Result<HeartbeatResponse, SdkError>;

struct HeartbeatResponse {
    server_timestamp: i64, // current server time (ms)
    expire_at:        i64, // time this heartbeat expires (ms)
}

The request body uses the same CancelAllRequest shape: the signed payload is a pre-authorized cancel-all β€” the server executes it on your behalf after the heartbeat times out. Set nonce to now_ms and expires_after to now_ms + 300_000 (5 minutes). Heartbeats should be sent more frequently than once every 5 minutes.

6.5 get_order

Query a single order by its server-assigned ID.

pub async fn get_order(&self, order_id: &str) -> Result<OrderRecord, SdkError>;

pub struct OrderRecord {
    id:              String,          // server-assigned order ID
    oid:             String,          // order oid (distinct from `id`)
    market_id:       String,
    token_id:        String,          // YES/NO token contract address
    asset_id:        String,          // YES or NO outcome asset ID
    client_order_id: Option<String>,  // client order ID provided at placement (if any)
    side:            OrderSide,        // Buy / Sell / Unknown
    order_type:      TimeInForce,      // Gtc / Gtd / Ioc / Fok / PostOnly / Unknown
    size_type:       OrderSizeType,    // Base / Quote / Unknown
    size:            String,           // decimal
    price:           String,           // decimal
    expiration:      Option<String>,   // GTD expiry (ms, string); None for non-GTD
    tx_hash:         String,           // submission transaction hash
    status:          RestOrderStatus,  // PendingPlace / Active / PendingCancel / Filled /
                                       // PartiallyFilled / Failed / Cancelled / Expired / Unknown
    filled_size:     String,           // decimal
    filled_amount:   String,           // decimal
    fail_reason:     Option<String>,   // present only when status == RestOrderStatus::Failed
    cancel_reason:   Option<String>,   // set when the server initiates the cancel (heartbeat timeout, market settlement, etc.)
    odds_type:       OddsType,         // Spots / Points / Unknown
    created_at:      String,           // Unix ms (string)
    updated_at:      String,           // Unix ms (string)
}

6.6 list_orders

List the authenticated user's orders.

pub async fn list_orders(
    &self,
    market_id: Option<&str>,   // filter by market ID
    status:    Option<&str>,   // "open" (pending + active) | "closed" (filled / cancelled / expired / failed)
    cursor:    Option<&str>,   // pagination cursor
    limit:     Option<i32>,    // max 50, default 20
) -> Result<OrdersResponse, SdkError>;

// Type alias over the shared paginated-list response wrapper.
pub type OrdersResponse = PagedListResponse<OrderRecord>;
// pub struct PagedListResponse<T> { list: Vec<T>, next_cursor: Option<String>, has_next: bool }

7. Account: Positions

Module: okx_outcomes_sdk::models::position::*. The API is in okx_outcomes_sdk::api::positions.

7.1 get_positions

Query the authenticated user's positions.

pub async fn get_positions(
    &self,
    status:    Option<&str>,   // "open" | "closed"; all if omitted
    market_id: Option<&str>,
    cursor:    Option<&str>,   // pagination cursor
    limit:     Option<i32>,    // max 100, default 20
) -> Result<PositionsResponse, SdkError>;

pub type PositionsResponse = PagedListResponse<PositionRecord>;

pub struct PositionRecord {
    id:                         String,         // identifier
    token_id:                   String,
    market_id:                  String,
    token_index:                String,         // "1" = YES, "2" = NO
    token_name:                 String,         // "Yes" or "No"
    size:                       String,         // current remaining size
    available_size:             String,         // available size (= size βˆ’ the portion frozen by sell orders)
    value:                      String,         // cur_price * size
    avg_price:                  String,         // weighted average entry cost
    un_realized_pnl:            String,         // unrealized PnL
    un_realized_pnl_percentage: String,
    title:                      String,         // display string
    icon:                       String,         // display string
    event_id:                   String,
    winning_token:              Option<String>, // winning token ID after settlement; None before settlement
    position_status:            i32,            // position status code (full enum in the API reference)
    cur_price:                  String,         // current token price
    realized_pnl:               String,         // realized PnL
    realized_pnl_percentage:    String,
    odds_type:                  OddsType,       // Spots / Points / Unknown
                                                // (verified in production: wire value is "points" or "spots", not "real")
}

8. Account: Trades

Module: okx_outcomes_sdk::models::trade::*. The API is in okx_outcomes_sdk::api::trades.

8.1 get_trades

Query the authenticated user's trade history.

pub async fn get_trades(
    &self,
    market_id:  Option<&str>,   // filter by market ID
    side:       Option<&str>,   // "BUY" | "SELL"
    start_time: Option<i64>,    // start time (inclusive, ms)
    end_time:   Option<i64>,    // end time (exclusive, ms)
    cursor:     Option<&str>,   // pagination cursor
    limit:      Option<i32>,    // max 100, default 20
) -> Result<TradesResponse, SdkError>;

type TradesResponse = PagedListResponse<TradeRecord>;

struct TradeRecord {
    trade_id:   String,    // empty string for TAKER rows and MAKER rows before on-chain assignment
    order_id:   String,
    market_id:  String,
    token_id:   String,
    side:       OrderSide, // Buy / Sell / Unknown
    size:       String,    // filled token amount
    amount:     String,    // filled amount
    price:      String,
    fee:        String,
    role:       Role,      // Maker / Taker / Unknown
    tx_hash:    String,
    created_at: String,    // Unix ms (string)
}

trade_id is None for TAKER rows, and for historical MAKER rows from before the on-chain trade-id assignment mechanism.

9. Conditional Tokens

Module: okx_outcomes_sdk::models::position::* (shared with positions). The API is in okx_outcomes_sdk::api::positions.

All three are write operations that require an EIP-712 signature. Each request body has the same outer shape: { action, nonce, signature }, differing only in action. Each returns TxHashResponse { tx_hash: String }.

9.1 split

Split an equal amount of xp in a market into equal amounts of YES + NO tokens (the inverse of merge).

pub async fn split(&self, req: &SplitRequest) -> Result<TxHashResponse, SdkError>;

struct SplitRequest { action: SplitAction, nonce: i64, signature: SignatureWrapper }
struct SplitAction {
    action_type: String, // "predictionSplit"
    market_id:   String,
    size:        String, // amount in minimal units
}

9.2 merge

Merge equal amounts of YES + NO tokens (back into xp).

pub async fn merge(&self, req: &MergeRequest) -> Result<TxHashResponse, SdkError>;

struct MergeRequest { action: MergeAction, nonce: i64, signature: SignatureWrapper }
struct MergeAction {
    action_type: String, // "predictionMerge"
    market_id:   String,
    size:        String,
}

9.3 redeem

After a market settles, redeem the caller's entire winning token balance. There is no size field; the server redeems the full amount the caller holds.

pub async fn redeem(&self, req: &RedeemRequest) -> Result<TxHashResponse, SdkError>;

struct RedeemRequest { action: RedeemAction, nonce: i64, signature: SignatureWrapper }
struct RedeemAction {
    action_type: String, // "predictionRedeem"
    market_id:   String,
}

Returns Api { code: 51020, ... } if the market has not been resolved yet.

10. Market Data

Module: okx_outcomes_sdk::models::price::*. The API is in okx_outcomes_sdk::api::prices.

These calls hit OKX's market data API at https://www.okx.com/api/v5/market/* β€” the same host as the prediction market API but with a different path prefix and response envelope. The market data envelope wraps code as a JSON string, so the code in SdkError::Api { code } is the parsed integer value.

10.1 get_ticker

Latest ticker for a single instrument. inst_id is the market's yes_outcome.asset_id.

pub async fn get_ticker(&self, inst_id: &str) -> Result<Ticker, SdkError>;

pub struct Ticker {
    inst_type:  String,
    inst_id:    String,
    last:       String, // latest trade price
    last_sz:    String, // latest trade size
    ask_px:     String, // best ask price
    ask_sz:     String, // size at the best ask
    bid_px:     String, // best bid price
    bid_sz:     String, // size at the best bid
    open24h:    String, // 24h open price
    high24h:    String, // 24h high price
    low24h:     String, // 24h low price
    vol24h:     String, // 24h volume (base currency)
    vol_ccy24h: String, // 24h volume (quote currency)
    sod_utc0:   String, // open price at UTC 0
    sod_utc8:   String, // open price at UTC+8
    ts:         String, // update timestamp (Unix ms decimal string)
}

The server returns a 1-element array; the SDK unwraps it. Returns Api { code: -1, message: "ticker not found" } when the inst ID is unknown.

10.2 get_candles

Candlestick history.

pub async fn get_candles(
    &self,
    inst_id: &str,
    bar:     Option<&str>,   // "1m" / "5m" / "15m" / "30m" / "1H" / "4H" / "1D" / ... ; default "1m"
    after:   Option<&str>,   // return candles with timestamp **before** this value (ms)
    before:  Option<&str>,   // return candles with timestamp **after** this value (ms)
    limit:   Option<i32>,    // max 300, default 100
) -> Result<Vec<Candle>, SdkError>;

pub struct Candle(pub Vec<String>);

impl Candle {
    pub fn ts(&self)        -> &str;   // index 0: open time (Unix ms string)
    pub fn open(&self)      -> &str;   // index 1: open price
    pub fn high(&self)      -> &str;   // index 2: high price
    pub fn low(&self)       -> &str;   // index 3: low price
    pub fn close(&self)     -> &str;   // index 4: close price
    pub fn vol(&self)       -> &str;   // index 5: volume (contracts)
    // index 6: volume in the base currency (no helper)
    // index 7: volume in the quote currency (no helper)
    pub fn confirmed(&self) -> bool;   // index 8: returns true when "1" (candle closed)
}

10.3 get_pm_books

Prediction market order book depth snapshot.

pub async fn get_pm_books(
    &self,
    inst_id: &str,             // YES outcome asset ID
    sz:      Option<i32>,      // number of depth levels per side; max 400 (up to 800 across both sides).
                               // Defaults to 1 (BBO only) when omitted.
) -> Result<PmBookDepth, SdkError>;

pub struct PmBookDepth {
    asks:   Vec<Vec<String>>,  // ask levels, ascending by price. Each entry is [price, size, order_count].
    bids:   Vec<Vec<String>>,  // bid levels, descending by price. Each entry is [price, size, order_count].
    ts:     String,            // snapshot timestamp (Unix ms decimal string)
    seq_id: i64,               // order book version sequence; opaque to most callers,
                               // exposed only to align with the API response
}

The server returns a 1-element data array; the SDK unwraps it. Returns Api { code: -1, message: "pm-books snapshot not found" } when the response is empty.

11. WebSocket

Module: okx_outcomes_sdk::ws::*. Requires the websocket Cargo feature.

Connection model

The Open API uses the same endpoint for public and private channels: wss://<host>/ws/v5/business. Public channels can be used anonymously. Private channels require a one-time op: "login" after the WS handshake.

Hosts:

pub mod ws::endpoints {
    pub const DEFAULT_WS_HOST: &str = "wss://ws.okx.com:8443";
    pub const EU_WS_HOST:      &str = "wss://wseea.okx.com";
    pub const US_WS_HOST:      &str = "wss://wsus.okx.com";
    pub const BUSINESS_PATH:   &str = "/ws/v5/business";
}

PredictionsWsClient uses DEFAULT_WS_HOST by default; override it with PredictionsWsClient::with_host(...) or the PREDICTIONS_WS_HOST environment variable.

Lifecycle and resilience:

Public API

pub struct PredictionsWsClient { /* ... */ }

impl PredictionsWsClient {
    pub fn new() -> Self;
    pub fn with_host(host: &str) -> Self;

    pub async fn connect(&self, path: &str) -> Result<(), SdkError>;
    pub async fn login(&self, creds: &ApiCredentials) -> Result<(), SdkError>;
    pub async fn subscribe(&self, channel: &str, params: Vec<HashMap<String, String>>) -> Result<(), SdkError>;
    pub async fn unsubscribe(&self, channel: &str, params: Vec<HashMap<String, String>>) -> Result<(), SdkError>;
    pub async fn disconnect(&self);

    pub fn set_on_data(&self, callback: WsDataCallback);
    pub fn set_on_connection_state(&self, callback: WsConnectionStateCallback);
}

pub type WsDataCallback             = Arc<dyn Fn(&WsMessage) + Send + Sync>;
pub type WsConnectionStateCallback  = Arc<dyn Fn(&str, bool) + Send + Sync>;

subscribe is idempotent: calling it twice with the same (channel, params) pair does not create a duplicate subscription, nor a duplicate entry in the replay list.

Login signature (handled internally by login): the SDK computes sign = Base64(HMAC-SHA256(secret_key, timestamp + "GET" + "/users/self/verify")) and sends:

{"op": "login", "args": [{"apiKey": "...", "passphrase": "...", "timestamp": "...", "sign": "..."}]}

The future returned by login resolves only after the server responds:

Message dispatch

Each incoming JSON frame is parsed once into the WsMessage enum and handed to on_data. Consumers never see the raw JSON.

pub enum WsMessage {
    Event {
        event:   String,
        channel: Option<String>,
        inst_id: Option<String>,
        msg:     Option<String>,
    },
    Prices(Vec<WsPriceTick>),
    Books { data: Vec<WsPmBookData>, action: String }, // action = "snapshot" | "update"
    Trades(Vec<WsPmTrade>),
    Tickers(Vec<WsPmTicker>),
    Game(Vec<WsGameStatus>),
    EventStatus(Vec<WsEventStatus>),
    Candle(Vec<Candle>),                   // typed wrapper, 9-column OHLCV array
    Orders(Vec<WsOrder>),
    Positions(Vec<WsPosition>),
    UserTrades(Vec<WsUserTrade>),
    Balance(Vec<WsBalance>),
    Pnl(Vec<WsPnl>),
    Unknown { channel: String, raw: serde_json::Value },
}

WsMessage::Event carries acknowledgement messages such as event:"subscribe"|"unsubscribe"|"login"|"error", unrelated to any data channel.

Public channels

11.1 prediction-market-prices

Per-market price ticks.

struct WsPriceTick {
    yes_asset_id:     String,
    last_trade_price: Option<String>, // None before the first trade
    best_bid:         Option<String>, // None if no bids
    best_ask:         Option<String>, // None if no asks
    timestamp:        String,         // Unix ms decimal string
    probability:      String,         // basis points * 100, e.g. "6500" = 65.00%
    market_volume:    String,
    event_volume:     String,
    event_id:         String,
}

11.2 pm-books

Order book snapshots and incremental updates.

struct WsPmBookData {
    asks:        Vec<Vec<String>>, // [[price, size, ...], ...]
    bids:        Vec<Vec<String>>, // [[price, size, ...], ...]
    ts:          String,
    checksum:    Option<i64>,      // CRC32 integrity check over the normalized book
    seq_id:      Option<i64>,      // monotonic sequence; a gap = loss, reset required
    prev_seq_id: Option<i64>,      // -1 for the first snapshot
}

When prev_seq_id does not match the previous frame's seq_id, discard the local book and wait for the next snapshot.

11.3 pm-trades

Public trade feed.

struct WsPmTrade {
    inst_id:  String,
    trade_id: Option<String>, // single push: Some; aggregated push: None
    f_id:     Option<String>, // aggregated push: first trade id; single: None
    l_id:     Option<String>, // aggregated push: last trade id; single: None
    px:       String,         // price
    sz:       String,         // size
    side:     String,         // "buy" / "sell" (taker side)
    ts:       String,
}

Distinguish single pushes from windowed aggregated pushes by which of trade_id and (f_id, l_id) is Some.

11.4 pm-tickers

OKX-style per-instrument ticker push.

struct WsPmTicker {
    inst_type: String, inst_id: String,
    last:       String, last_sz: String,
    ask_px:     String, ask_sz:  String,
    bid_px:     String, bid_sz:  String,
    open24h:    String, high24h: String, low24h: String,
    vol24h:     String, vol_ccy24h: String,
    sod_utc0:   String, sod_utc8: String,
    ts:         String,
}

11.5 pm-event-status

Event settlement push.

struct WsEventStatus {
    event_id:       String,
    status:         String,  // e.g. "resolved"
    market_id:      String,  // winning market ID
    outcome_option: String,  // "yes" / "no" / "others" / team name / "draw"
    timestamp:      String,
}

11.6 pm-candle*

Candlestick stream. The channel name encodes the bar: pm-candle1m, pm-candle5m, pm-candle1H, pm-candle1D, etc.

Private channels (require login)

Subscribe with empty params (the server scopes the subscription to the logged-in account).

11.7 pm-order

Order status changes.

struct WsOrder {
    order_id:        String,
    market_id:       String,
    status:          OrderStatus,       // Active / Filled / PartiallyFilled / PlaceFailed /
                                        // CancelFailed / Cancelled / Expired / Unknown
    side:            OrderSide,         // Buy / Sell / Unknown
    // All fields below depend on `status` β€” see the status -> required-fields table in the spec.
    // Modeled as Option; a missing key deserializes to None.
    client_order_id: Option<String>,
    asset_id:        Option<String>,    // assetId of YES or NO
    direction:       Option<Direction>, // Yes / No / Unknown β€” the outcome side this order takes
    filled_size:     Option<String>,
    order_size:      Option<String>,    // serde alias = "size"
    avg_price:       Option<String>,
    amount:          Option<String>,    // BUY = spent, SELL = received (xp)
    limit_price:     Option<String>,    // serde alias = "price"
    fail_message:    Option<String>,    // present only for PLACE_FAILED / CANCEL_FAILED
    odds_type:       Option<OddsType>,
    tx_hash:         Option<String>,    // serde rename = "txHash"
    trade_id:        Option<String>,
}

11.8 pm-position

Position updates.

This channel has two payload variants; WsPosition represents both with a single flat struct, with the variant-specific fields wrapped in Option. Branch on status (via PositionStatus::is_position_snapshot() / is_failed()) to decide which fields are meaningful.

struct WsPosition {
    // Common to both variants
    market_id: String,
    status:    PositionStatus,    // Fill / FillFailed / Redeem / RedeemFailed /
                                  // Split / SplitFailed / Merge / MergeFailed /
                                  // Deposit / DepositFailed / Withdraw / WithdrawFailed / Unknown
    amount:    String,            // variant 1: position `remain` ("0" for REDEEM)
                                  // variant 2: split/merge/deposit/withdraw amount
    odds_type: Option<OddsType>,

    // Variant 1 (FILL / REDEEM / *_FAILED) β€” full position snapshot
    id:                          Option<String>,
    token_id:                    Option<String>,
    asset_id:                    Option<String>,
    timestamp:                   Option<String>,
    un_realized_pnl:             Option<String>,
    un_realized_pnl_percentage:  Option<String>,
    value:                       Option<String>,
    avg_price:                   Option<String>,
    trade_id:                    Option<String>,

    // Variant 2 (SPLIT / MERGE / DEPOSIT / WITHDRAW / *_FAILED)
    tx_hash: Option<String>,           // serde rename = "txHash"
    ext:     Option<WsPositionExt>,    // populated only for DEPOSIT
}

struct WsPositionExt {
    to_tx_hash: String,                // serde rename = "toTxHash"
}

11.9 pm-user-trade

The user's own trade stream.

struct WsUserTrade {
    order_id:        String,
    client_order_id: String,    // defaults to empty string for resilience; usually present
    market_id:       String,
    token_id:        String,
    asset_id:        String,    // yesAssetId or noAssetId
    side:            OrderSide, // Buy / Sell / Unknown
    size:            String,
    price:           String,
    txhash:          String,
    timestamp:       String,
    trade_id:        String,    // trade ID
}

11.10 pm-balance

Balance changes.

struct WsBalance {
    wallet_address: String,
    available:      String,
    total:          String,
    frozen:         String,
    token_id:       String,                  // on-chain Point token id
    change_type:    BalanceChangeType,       // Place / Cancel / Fill / Split / Merge /
                                             // Redeem / Deposit / Withdraw / Unknown
    change_amount:  Option<String>,          // spec: may be null
    update_time:    String,
    odds_type:      Option<OddsType>,
}

11.11 pm-pnl

Unrealized PnL stream β€” pushes two payloads; modeled with a serde untagged enum that picks the right variant based on the fields present.

enum WsPnl {
    Overview(WsPnlOverview),     // portfolioValue + per-period summaries
    Timeseries(WsPnlTimeseries), // chart points with high/low/current
}

struct WsPnlOverview {
    portfolio_value: String,                 // xp balance + position market value
    periods:         Vec<WsPnlPeriodSummary>,
}

struct WsPnlPeriodSummary {
    period:      String, // "1D" / "1W" / "1M" / "6M" / "1Y"
    period_pnl:  String,
    pnl_percent: String,
}

struct WsPnlTimeseries {
    period:      String,         // "0"=1D / "1"=1W / "2"=1M / "3"=6M / "4"=1Y
    interval:    String,         // ms: 600000 / 1800000 / 3600000 / 86400000
    points:      Vec<WsPnlPoint>,
    current_pnl: String,
    high:        String,
    low:         String,
}

WS Error Codes

WS-layer errors are exposed via WsMessage::Event { event: "error", msg, .. }; login-related errors are exposed via the Err returned by login(). Common error codes:

Error code Meaning
60004 Invalid login timestamp (clock drift, expired).
60005 Invalid API key.
60006 Timestamp expired (30-second window).
60007 Invalid signature.
60009 Login failed (generic).
60011 This private channel requires login.
60012 Invalid op value.
60018 Subscription failed (wrong channel name or params).

12. Signing

Module: okx_outcomes_sdk::signing::*. Requires the signing Cargo feature.

The full pipeline for any write operation: build a typed Action, sign it with your k256::ecdsa::SigningKey via sign_to_wrapper, then put the resulting SignatureWrapper into the request body.

pub fn parse_private_key(hex_key: &str) -> Result<SigningKey, String>;
pub fn now_millis() -> u64;

pub fn sign_to_wrapper(
    action:        &Action,
    nonce:         u64,
    expires_after: Option<u64>,
    key:           &SigningKey,
) -> Result<SignatureWrapper, String>;

Action constructors:

pub fn action_place_order(orders: Vec<OrderRequest>) -> Action;
pub fn action_cancel(cancels: Vec<CancelRequest>) -> Action;
pub fn action_cancel_all(asset_ids: Vec<String>, market_type: &str) -> Action;
pub fn action_prediction_split (market_id: &str, size: &str) -> Action;
pub fn action_prediction_merge (market_id: &str, size: &str) -> Action;
pub fn action_prediction_redeem(market_id: &str) -> Action;

Typed inputs:

struct OrderRequest {
    asset_id:        String,
    side:            SigningOrderSide,  // Buy / Sell (wire side lowercase)
    market_type:     String,            // "prediction"
    client_order_id: Option<String>,    // 34-char client order ID
    price:           String,
    reduce_only:     bool,
    size:            String,
    size_type:       SizeType,          // Base (default) / Quote
    order_type:      OrderType,
}

enum OrderType {
    Limit(LimitTif),
}
enum LimitTif {
    Plain(String),                                  // "gtc" | "ioc" | "fok" | "alo"
    Gtd { expiry_time: u64 },                       // good-til-date, ms
}

struct CancelRequest {
    asset_id:    String,
    market_type: String,                            // "prediction"
    target:      CancelTarget,
}
enum CancelTarget { Oid(String), ClientOrderId(String) }

The wire-side counterparts (OrderItem, CancelItem) implement TryFrom<&OrderRequest> and From<&CancelRequest> respectively, so the JSON body and the signed bytes are both constructed from the same source struct.

Client order ID:

pub fn generate_client_order_id_default() -> Result<String, String>;
pub fn generate_client_order_id(region: Region, env: Env) -> Result<String, String>;
pub fn validate_client_order_id(s: &str) -> bool;
pub fn parse_client_order_id_prefix(client_order_id: Option<&str>) -> ClientOrderIdPrefix;
pub fn register_client_order_id_context(region: Region, env: Env);

The client order ID is a 34-char hex string of the form 0x{region}{env}{30 hex random chars}. generate_client_order_id_default() reads the registered global context (HK / PROD by default). To use a different context, register it once at startup to override.

Low-level helpers:

pub fn signer_address(key: &SigningKey) -> String;
pub fn ecrecover(signing_hash: &str, signature: &str) -> Result<String, String>;
pub fn sign_action(...) -> Result<String, String>;        // returns "0x..." hex
pub fn sign_action_full(...) -> Result<(String, String, String, u8), String>; // (txhash, r, s, v)
pub fn sign_action_debug(...) -> Result<SigningDebug, String>; // returns all intermediate hashes

For the normal flow, use sign_to_wrapper. The low-level functions are only for debugging, or for callers that need access to the txhash (e.g. to display a "view in explorer" link).

13. Common Types

Module: okx_outcomes_sdk::models::common::*.

struct Pagination {
    next_cursor: Option<String>, // None on the last page
    has_more:    bool,
    page_size:   i32,            // item count on the current page
}

struct EcdsaSignature {
    r: String, // hex, with 0x prefix
    s: String, // hex, with 0x prefix
    v: u8,     // recovery id: 0 or 1
}

struct SignatureWrapper {
    // serialized as { "Ecdsa": { r, s, v } }
    ecdsa: EcdsaSignature,
}

The SDK transparently wraps two API envelopes:

WebSocket

1. WebSocket Login Authentication

WebSocket login authentication is required only before subscribing to private channels. Public channels do not require login.

See WebSocket login documentation.

Generic subscribe format

{
  "op": "subscribe",
  "args": [
     { "channel": "<channel1>", "instId": "<yesAssetId1>" },
     { "channel": "<channel2>", "instId": "<yesAssetId2>" }
  ]
}

Generic unsubscribe format

{
  "op": "unsubscribe",
  "args": [
      { "channel": "<channel1>", "instId": "<yesAssetId1>" },
      { "channel": "<channel2>", "instId": "<yesAssetId2>" }
   ]
}

2. WebSocket Private Channels

Channel Subscribe params Auth required Description
pm-order channelName Yes User order data push
pm-position channelName Yes Position changes
pm-user-trade channelName Yes User trade history push
pm-balance channelName Yes Balance changes
pm-pnl channelName Yes Unrealized PnL of current positions

2.1 Order Status Push

Push frequency: Event-driven, pushed when an order status changes

Subscribe example

{
  "op": "subscribe",
  "args": [{ "channel": "pm-order" }]
}

Push data format

{
  "arg": { "channel": "pm-order", "uid": "{cexUserId}" },
  "data": [{
    "orderId": "307173036051017730",
    "clientOrderId": "cli-abc-123",
    "marketId": "100001",
    "status": "FILLED",
    "assetId": "71",
    "side": "BUY",
    "direction": "YES",
    "filledSize": "10",
    "orderSize": "10",
    "avgPrice": "0.57",
    "amount": "5.7",
    "limitPrice": "0.45",
    "failMessage": null,
    "oddsType": "points",
    "txHash": "0xdef...",
    "tradeId": "9876543210"
  }]
}

Push Field Descriptions

Field Type Description
orderId string Order ID
clientOrderId string / null Client order ID (cloid); null if not provided by the client
marketId string Market ID
status string Push event type (see the enum table below)
assetId string Outcome asset ID (yesAssetId or noAssetId)
side string Trade direction: BUY / SELL
direction string Position direction: YES / NO
filledSize string / null Cumulative filled size; null if no fill
orderSize string Order size
avgPrice string / null Cumulative average fill price (= amount / filledSize); null if no fill
amount string / null Cumulative filled amount (xp), BUY = spent / SELL = received; null if no fill
limitPrice string / null Limit order price (limit scenario only); null for market orders
failMessage string / null Failure message; only populated for PLACE_FAILED / CANCEL_FAILED
oddsType string Odds type: points
txHash string / null On-chain transaction hash; null for events not yet on-chain (e.g. PLACE_FAILED)
tradeId string / null TradeZone trade ID; only populated for limit order partial fills (status=ACTIVE)

status Enum

code Description Required fields
ACTIVE Active (limit order on-chain / partially filled with remainder valid) orderSize, limitPrice (partial fills also include filledSize, avgPrice, amount, tradeId)
FILLED Fully filled filledSize, avgPrice, amount, txHash
PARTIALLY_FILLED Cancelled after partial fill filledSize, orderSize, avgPrice, amount, txHash
PLACE_FAILED Order placement failed orderSize, failMessage
CANCEL_FAILED Cancellation failed (unexpected) orderId, failMessage
CANCELLED Cancelled by user / batch-cancelled by system orderSize, limitPrice
EXPIRED Order expired orderSize, limitPrice

2.2 Position Change Push

Push frequency: Event-driven

Subscribe example

{
  "op": "subscribe",
  "args": [{ "channel": "pm-position" }]
}

2.3 Position

(status = FILL / REDEEM / FILL_FAILED / REDEEM_FAILED)

Push data format

{
  "arg": { "channel": "pm-position", "uid": "{cexUserId}" },
  "data": [{
    "id": "307173036051017730",
    "marketId": "100001",
    "tokenId": "20000",
    "assetId": "71",
    "amount": "10",
    "timestamp": "1712736000000",
    "unRealizedPnl": "5.20",
    "unRealizedPnlPercentage": "1.05",
    "value": "100.00",
    "avgPrice": "0.57",
    "status": "FILL",
    "tradeId": "9876543210",
    "oddsType": "points"
  }]
}

Field Descriptions

Field Type Description
id String Position ID
marketId String Market ID
tokenId String YES/NO token on-chain ID
assetId String Outcome asset ID (yesAssetId or noAssetId)
amount String Current position size (remain snapshot); "0" for REDEEM
timestamp String Event timestamp (ms)
unRealizedPnl String Unrealized PnL = (currentPrice βˆ’ avgCost) Γ— remain; "0" for REDEEM
unRealizedPnlPercentage String Unrealized PnL percentage (8-digit precision); "0" for REDEEM
value String Position market value = currentPrice Γ— remain; "0" for REDEEM
avgPrice String Weighted average cost basis; "0" for REDEEM
status String FILL / REDEEM
tradeId String / null TradeZone trade ID; only populated for FILL (null for REDEEM)
oddsType String Odds type: points

2.4 status Scenarios

code Description Pushes per event
FILL Position change caused by an order fill 1 (single tokenId position)
REDEEM Settlement redemption; the market's position is cleared N (one per tokenId the market originally had, with amount=0)

2.5 Position

(status = SPLIT / MERGE / DEPOSIT / WITHDRAW / SPLIT_FAILED / MERGE_FAILED / DEPOSIT_FAILED / WITHDRAW_FAILED)

Push data format

{
  "arg": { "channel": "pm-position", "uid": "{cexUserId}" },
  "data": [{
    "marketId": "100001",
    "status": "DEPOSIT",
    "amount": "100",
    "txHash": "0xdef...",
    "oddsType": "points",
    "ext": {
      "toTxHash": "0xabc..."
    }
  }]
}

Field Descriptions

Field Type Description
marketId String Market ID
status String SPLIT / MERGE / DEPOSIT / WITHDRAW
amount String Amount
txHash String On-chain transaction hash. For DEPOSIT, the original XLayer transaction hash; otherwise the on-chain hash of the corresponding operation
oddsType String Odds type: points
ext Object / null Extended info; only populated for DEPOSIT, null otherwise
ext.toTxHash String / null The TZ-side crediting transaction hash; only populated for DEPOSIT

status Enum

code Description
FILL Position change caused by an order fill
SPLIT Split succeeded
MERGE Merge succeeded
REDEEM Settlement redemption succeeded
DEPOSIT Deposit succeeded
WITHDRAW Withdrawal succeeded
FILL_FAILED Fill failed
SPLIT_FAILED Split failed
MERGE_FAILED Merge failed
REDEEM_FAILED Redemption failed
DEPOSIT_FAILED Deposit failed
WITHDRAW_FAILED Withdrawal failed

2.6 Trade Fill Push

Each FILL event pushes one trade record; the front end uses it to display the trade list in real time.

Push frequency: Event-driven, one per fill

Subscribe example

{
  "op": "subscribe",
  "args": [{ "channel": "pm-user-trade" }]
}

Push data format

{
  "arg": { "channel": "pm-user-trade", "uid": "{cexUserId}" },
  "data": [{
    "orderId": "307173036051017730",
    "clientOrderId": "cli-abc-123",
    "marketId": "100001",
    "tokenId": "20000",
    "assetId": "71",
    "side": "BUY",
    "size": "10",
    "price": "0.57",
    "txhash": "0xdef...",
    "timestamp": "1712736000000",
    "tradeId": "9876543210"
  }]
}

Push Field Descriptions

Field Type Description
orderId string TradeZone order ID
clientOrderId string / null Client order ID; null if not provided by the client
marketId string Market ID
tokenId string YES/NO token on-chain ID
assetId string Outcome asset ID (yesAssetId or noAssetId)
side string Fill direction: BUY / SELL
size string Fill size
price string Fill price
txhash string On-chain transaction hash
timestamp string Event timestamp (ms)
tradeId string TradeZone trade ID

2.7 Balance Change Push

Pushed after a balance sync succeeds; the front end uses it to update the balance display in real time.

Push frequency: Event-driven, pushed on balance changes (place / cancel / fill / Split / Merge / redeem / deposit / withdraw)

Subscribe example

{
  "op": "subscribe",
  "args": [{ "channel": "pm-balance" }]
}

Push data format

{
  "arg": { "channel": "pm-balance", "uid": "{cexUserId}" },
  "data": [{
    "walletAddress": "0x1234abcd5678ef901234abcd5678ef901234abcd",
    "available": "950.5",
    "total": "1000",
    "frozen": "49.5",
    "tokenId": "0",
    "changeType": "FILL",
    "changeAmount": "100",
    "updateTime": "1712736000000",
    "oddsType": "points"
  }]
}

Push Field Descriptions

Field Type Description
walletAddress String User's AA wallet address
available String Available balance
total String Total balance (including frozen)
frozen String Frozen amount (= total βˆ’ available)
tokenId String xp token on-chain ID (corresponds to oddsType)
changeType String Trigger reason (see the enum table below)
changeAmount String / null Amount changed this time; null for non-applicable scenarios
updateTime String Event timestamp (ms)
oddsType String Odds type: points

2.8 changeType Enum

code Trigger scenario
PLACE Funds frozen on order placement
CANCEL Funds unfrozen on cancellation
FILL Balance change caused by a fill
SPLIT Split deducts xp
MERGE Merge adds xp
REDEEM Settlement redemption
DEPOSIT Deposit
WITHDRAW Withdrawal

2.9 PnL Change Push

Pushes both the PnL curve and the overview.

Push data format

{
  "arg": { "channel": "pm-pnl", "uid": "{cexUserId}" },
  "data": [{
    "portfolioValue": "1234.56",
    "periods": [
      { "period": "1D", "periodPnl": "12.34",  "pnlPercent": "1.01" },
      { "period": "1W", "periodPnl": "56.78",  "pnlPercent": "4.83" },
      { "period": "1M", "periodPnl": "120.45", "pnlPercent": "10.81" },
      { "period": "6M", "periodPnl": "320.10", "pnlPercent": "35.02" },
      { "period": "1Y", "periodPnl": "500.00", "pnlPercent": "67.93" }
    ]
  }]
}

Field Descriptions

Field Type Description
portfolioValue String Current total portfolio value (xp available balance + position market value)
periods Array Multi-period PnL summary array
periods[].period String Period: 1D / 1W / 1M / 6M / 1Y
periods[].periodPnl String Absolute PnL within the period
periods[].pnlPercent String PnL percentage within the period

Push data format

{
  "arg": { "channel": "pm-pnl", "uid": "{cexUserId}" },
  "data": [{
    "period": "0",
    "interval": "600000",
    "points": [
      { "time": "1712707200000", "pnl": "1000.00" },
      { "time": "1712728800000", "pnl": "1010.50" },
      { "time": "1712750400000", "pnl": "1023.75" }
    ],
    "currentPnl": "1023.75",
    "high": "1030.00",
    "low": "995.00"
  }]
}

Field Descriptions

Field Type Description
period String Period code: 0=1D / 1=1W / 2=1M / 3=6M / 4=1Y
interval String Data point interval (ms), corresponding to the period (see the table below)
points Array Time-series data points
points[].time String Data point timestamp (ms)
points[].pnl String Total assets at that moment (= xp balance + position market value)
currentPnl String Current total assets (compatibility field)
high String Highest total assets within the period
low String Lowest total assets within the period

2.10 Period Code and interval Mapping

period code Period interval (ms) Meaning
0 1D 600000 10 minutes
1 1W 1800000 30 minutes
2 1M 3600000 1 hour
3 6M 86400000 1 day
4 1Y 86400000 1 day

3. WebSocket Public Channels

3.1 Order Book Push

3.2 Push Frequency

Subscribe example

{
  "op": "subscribe",
  "args": [{
    "channel": "pm-books",
    "instId": "{yesAssetId}"
  }]
}

Subscribe success response

{
  "event": "subscribe",
  "arg": { "channel": "pm-books", "instId": "{yesAssetId}" }
}

Push data format

{
      "arg": {"channel": "pm-books", "instId": "{yesAssetId}"},
      "action": "snapshot",
      "data": [
          {
              "asks": [
                  ["67300.1", "201.18", "25"],
                  ["67300.2", "421.45", "5"]
              ],
              "bids": [
                  ["67300", "525.41", "34"],
                  ["67299.9", "0.17", "7"]
              ],
              "ts": "1774944028506",
              "checksum": -702280706,
              "seqId": 308306650401,
              "prevSeqId": -1
          }
      ]
  }

3.3 Trade Push

Pushes each trade record in real time.

Push frequency: push interval 200ms, up to 20 trades per push, encoded as aggregated trades

Subscribe example

{
  "op": "subscribe",
  "args": [{
    "channel": "pm-trades",
    "instId": "{yesAssetId}"
  }]
}

Push data format

{
  "arg": { "channel": "pm-trades", "instId": "{yesAssetId}" },
  "data": [{
    "instId": "{yesAssetId}",
    "tradeId": "123456789",
    "px": "0.65",
    "sz": "100",
    "side": "buy",
    "ts": "1711900800123"
  }]
}

Push Field Descriptions

Field Type Description
instId String yesAssetId
fId String First trade ID within the aggregation window
lId String Last trade ID within the aggregation window
px String Trade price
sz String Trade size
side String Trade direction: buy / sell
ts String Trade time (Unix ms timestamp)

3.4 Candlestick Push

Pushes candlestick data in real time; the current candle keeps updating until it closes. Clients can draw a price trend chart based on the candle close price; the subscription granularity is determined by the client.

Channel naming rule: pm-candle + period, e.g. pm-candle1m, pm-candle15m

Subscribe example (1-minute and 5-minute as examples)

{
  "op": "subscribe",
  "args": [
    { "channel": "pm-candle1m", "instId": "{yesAssetId}" },
    { "channel": "pm-candle15m", "instId": "{yesAssetId}" }
  ]
}

Push data format

{
  "arg": { "channel": "pm-candle15m", "instId": "{yesAssetId}" },
  "data": [
    ["1711900800000", "0.65", "0.67", "0.63", "0.66", "2000", "1300", "1300", "0"]
  ]
}

3.5 Push Field Descriptions

(each record in the data array is in index order)

Index Field Description
0 ts Candle start time (Unix ms timestamp)
1 o Open price
2 h High price
3 l Low price
4 c Close price
5 vol Volume (in contracts)
6 volCcy Volume (in the base currency)
7 volCcyQuote Volume (in the quote currency)
8 confirm Candle status: 0 unconfirmed (current candle), 1 confirmed (closed)

3.6 Ticker Push

Pushes the latest trade price, best bid, best ask, 24h volume, and other info in real time.

Push frequency: up to once every 100ms, triggered by trades or best-bid/best-ask changes; nothing is pushed without a trigger event

Subscribe example

{
  "op": "subscribe",
  "args": [{ "channel": "pm-tickers", "instId": "{yesAssetId}" }]
}

Subscribe success response

{
  "event": "subscribe",
  "arg": { "channel": "pm-tickers", "instId": "{yesAssetId}" },
  "connId": "accb8e21"
}

Push data format

{
  "arg": { "channel": "pm-tickers", "instId": "{yesAssetId}" },
  "data": [{
    "instType": "PREDICTIONS",
    "instId": "{yesAssetId}",
    "last": "0.4999",
    "lastSz": "1",
    "askPx": "0.6",
    "askSz": "30",
    "bidPx": "0.4",
    "bidSz": "30",
    "open24h": "0.5001",
    "high24h": "0.5001",
    "low24h": "0.4999",
    "vol24h": "372",
    "volCcy24h": "186",
    "sodUtc0": "0.4999",
    "sodUtc8": "0.4999",
    "ts": "1774948808119"
  }]
}

Push Field Descriptions

Field Type Description
instType String Instrument type
instId String yesAssetId
last String Latest trade price
lastSz String Latest trade size
askPx String Best ask price
askSz String Size at the best ask
bidPx String Best bid price
bidSz String Size at the best bid
open24h String 24h open price
high24h String 24h high price
low24h String 24h low price
vol24h String 24h volume (in contracts)
volCcy24h String 24h volume (in the base currency)
sodUtc0 String Open price at UTC 0
sodUtc8 String Open price at UTC+8
ts String Data push time (Unix ms timestamp)

3.7 Probability Price Push

Pushes prediction market probability prices, including market probability, cumulative volume, best bid/ask, latest trade price, etc.

Push frequency: pushed every 3s on a timer

Subscribe example

{
  "op": "subscribe",
  "args": [{
    "channel": "prediction-market-prices",
    "instId": "{yesAssetId}"
  }]
}

Push data format

{
    "arg": {
        "channel": "prediction-market-prices",
        "instId": "{yesAssetId}"
    },
    "data": [
        {
            "yesAssetId": "71",
            "eventId": "1774875348987717503",
            "bestBid": "0.4896",
            "bestAsk": "0.6134",
            "lastTradePrice": "0.5848",
            "probability": "5515",
            "marketVolume": "17.1463",
            "eventVolume": "773.6524",
            "timestamp": "1775036647300"
        }
    ]
}

Push Field Descriptions

Field Type Description
yesAssetId String Prediction market yesAssetId
eventId String The event the market belongs to
bestBid String Best bid price
bestAsk String Best ask price
lastTradePrice String Latest trade price
probability String Market probability for the Yes side, pushed as a basis-point integer (e.g. 0.6500 β†’ 6500)
marketVolume String Current cumulative market volume
eventVolume String Current cumulative event volume
timestamp String Event timestamp (ms)

3.8 Event Status Push

Event status changes, used to display an event's final settlement result.

Push frequency: event-driven, pushed when an event reaches its final result. During the World Cup there are 2-3 matches per day; pushed when a NegRisk or single binary event reaches its final result.

Subscribe example

Note: When subscribing to this channel, instId is event-{eventId}.

{
  "op": "subscribe",
  "args": [{
    "channel": "pm-event-status",
    "instId": "event-{eventId}"
  }]
}

Push data format

{
  "arg": {
    "channel": "pm-event-status",
    "instId": "event-{eventId}"
  },
  "data": [{
    "eventId": "{eventId}",
    "status": "resolved",
    "marketId":"marketId",
    "outcomeOption":"yes | no | others | team name | draw",
    "timestamp": "1672290687"
  }]
}

Push Field Descriptions

Field Type Description
eventId string Event ID
status string Event status
marketId string The marketId of the winning market
outcomeOption string Display of the final winning outcome
timestamp string Event timestamp (ms)

Error Codes

1. Response Format

All errors are returned in the following unified JSON structure:

{
  "code": 100015,
  "msg": "Invalid calldata or malformed fields"
}

1.1 Common

May be returned by any OpenAPI v5 endpoint. For the full list of common error codes, see: OKX public error codes

Code Meaning
10000 User not logged in
10001 Parameter validation failed
10002 Authentication failed
206004 Request oddsType does not match the account

1.2 Event / Market

Code Meaning
201001 Event does not exist
201002 Market does not exist

Applicable endpoints:

2. Order / Write Operations

Covers: place order, cancel order, cancel all, heartbeat, Split, Merge, Redeem.

Code Meaning
100001 Market is not tradable
100002 Insufficient balance
100006 Account is frozen
100010 Insufficient token balance
100011 Order does not exist
100012 Order status does not allow cancellation
100013 Request address does not match the user address
100015 Invalid calldata or malformed fields
100016 Nonce already used
100017 Nonce expired
100018 Order amount is below the minimum notional
100101 TradeZone SDK signature exception
120007 User does not exist
120022 Account is in escape (exit) process
201002 Market does not exist
213003 Signature verified but TradeZone submission failed

Applicable endpoints:

3. Order / Position Queries

Covers: query single order, order list, trade history, position queries.

Code Meaning
100011 Order does not exist
400 Path parameter parsing failed

Applicable endpoints: