Skip to content

Market Channel

The market channel is a public WebSocket feed delivering real-time order book data, price-level mutations, trade fills, and lifecycle events for prediction markets. No authentication needed.

wss://api.openfish.fun/ws/market

Two subscription methods are available:

After establishing the connection, send a JSON message to subscribe:

{
"type": "subscribe",
"assets_ids": ["71321045679252212594626385532706912750332728571942532289631379312455583992563"],
"level": 2,
"initial_dump": true
}
FieldTypeRequiredDescription
typestringYes"subscribe" or "unsubscribe"
assets_idsarrayYesToken asset IDs to subscribe/unsubscribe
levelintegerNoSubscription level: 1=trades only, 2=+best bid/ask (default), 3=+full book
initial_dumpbooleanNoSend initial book snapshot after subscribing (default: false)

Add or drop subscriptions on the fly without reconnecting:

{ "type": "subscribe", "assets_ids": ["new_asset_id_here"] }
{ "type": "unsubscribe", "assets_ids": ["asset_id_to_remove"] }

For backward compatibility, query parameters still work:

ParameterTypeDescription
marketstringFilter by condition ID
asset_idstringFilter by specific token asset ID
wss://api.openfish.fun/ws/market?market=0xbd31dc...&asset_id=71321045...

If neither query parameters nor a subscription message is provided, the connection receives events for all markets.

LevelEvents Received
1last_trade_price only
2Level 1 + best_bid_ask, price_change
3Level 2 + book snapshots

Lifecycle events (new_market, market_resolved, tick_size_change) arrive regardless of subscription level.

The server sends a text message "PING" every 10 seconds. Reply with a text message "PONG" to keep the connection alive.

Note: These are text messages, not WebSocket-level ping/pong frames.

Every message is a JSON object. A type or event_type field identifies the event kind.

Complete order book snapshot. Delivered on initial connection and whenever trades reshape the book.

{
"event_type": "book",
"asset_id": "71321045679252212594626385532706912750332728571942532289631379312455583992563",
"market": "0xbd31dc8a20211944f6b70f31557f1001557b59905b7738480ca09bd4532f84af",
"bids": [
{ "price": "0.48", "size": "30" },
{ "price": "0.49", "size": "20" }
],
"asks": [
{ "price": "0.52", "size": "25" },
{ "price": "0.53", "size": "60" }
],
"timestamp": "1700000000000"
}

Fired when an order placement or cancellation alters a price level. Requires subscription level >= 2.

{
"type": "price_change",
"market": "0x5f65177b394277fd294cd75650044e32ba009a95022d88a0c1d565897d72f8f1",
"asset_id": "71321045...",
"price": "0.50",
"size": "200",
"side": "BUY",
"hash": "abc123",
"best_bid": "0.50",
"best_ask": "0.52",
"timestamp": 1700000001
}

A size of "0" means the price level has been completely emptied.

Fired when a maker and taker order match, producing a fill.

{
"event_type": "last_trade_price",
"asset_id": "71321045...",
"market": "0x6a67b9d8...",
"price": "0.456",
"side": "BUY",
"size": "219.217767",
"fee_rate_bps": "0",
"timestamp": "1700000002000"
}

Fired when the top-of-book bid or ask shifts.

{
"event_type": "best_bid_ask",
"market": "0x0005c0d3...",
"asset_id": "85354956...",
"best_bid": "0.73",
"best_ask": "0.77",
"spread": "0.04",
"timestamp": "1700000003000"
}

Fired when the minimum tick size for a market changes — typically when the book price exceeds 0.96 or drops below 0.04.

{
"event_type": "tick_size_change",
"asset_id": "71321045...",
"market": "0xbd31dc8a...",
"old_tick_size": "0.01",
"new_tick_size": "0.001",
"timestamp": "1700000004000"
}

Fired when a new prediction market appears on the platform.

{
"type": "new_market",
"market": "0x311d0c4b...",
"question": "Will ETH reach $5000 by March?",
"slug": "eth-5000-by-march",
"outcomes": ["Yes", "No"],
"clob_token_ids": ["76043073...", "31690934..."],
"tags": ["crypto", "ethereum"],
"timestamp": 1700000005
}

Fired when a market’s outcome is determined.

{
"type": "market_resolved",
"market": "0x311d0c4b...",
"winning_token_id": "76043073...",
"winning_outcome": "Yes",
"assets_ids": ["76043073...", "31690934..."],
"tags": ["crypto", "ethereum"],
"timestamp": 1700000006
}

Events carrying market and asset_id fields are filtered server-side based on your subscription or query parameters. Events lacking these fields (such as new_market, market_resolved, and tick_size_change) are broadcast to every connected client regardless of filters.

use openfish_client_sdk::ws::MarketSocket;
use futures_util::StreamExt;
#[tokio::main]
async fn main() -> anyhow::Result<()> {
// Connect with optional market filter
let mut ws = MarketSocket::connect(
"wss://api.openfish.fun/ws/market?asset_id=71321045679252212594626385532706912750332728571942532289631379312455583992563"
).await?;
while let Some(event) = ws.next().await {
match event.event_type.as_str() {
"book" => {
println!("Book: {} bids, {} asks", event.bids.len(), event.asks.len());
}
"last_trade_price" => {
println!("Trade: {} @ {}", event.size, event.price);
}
"best_bid_ask" => {
println!("BBO: {} / {} (spread {})", event.best_bid, event.best_ask, event.spread);
}
"new_market" => {
println!("New market: {}", event.question);
}
"market_resolved" => {
println!("Resolved: {} -> {}", event.question, event.winning_outcome);
}
_ => {}
}
}
Ok(())
}