Skip to Content
API ReferenceWebSocket

WebSocket

Real-time feeds for order books, public trades, candles, mark price, session volume, IPO allocation progress, competition leaderboard, and your private portfolio deltas.

URL: wss://api.loafmarkets.com/ws (derive from your REST host by replacing /api with /ws and https with wss).

For a batteries-included client, see the Python SDK template .

Connection & auth

  1. Connect to wss://<host>/ws.
  2. For private channels, send an auth frame with your API key.
  3. Send subscribe with one or more channel strings.
  4. Handle incoming frames by type.

Auth frame:

{ "type": "auth", "token": "<LOAF_API_KEY>", "timestamp": 1718000000 }

Subscribe frame:

{ "type": "subscribe", "channels": ["orderbook:42", "portfolio:3"], "timestamp": 1718000000 }

You may also unsubscribe with the same shape. Server replies include auth_result, subscription_confirmed, and error frames when something fails.

Public channels work without auth. The private portfolio:{userId} channel requires a valid API key and a userId that matches the authenticated account (find your numeric id in the Loaf web app).

Channels

ChannelAuthMessage typePayload
orderbook:{propertyId}publicorderbook_updateFull bid/ask snapshot (~500ms)
trades:{propertyId}publictrades_batchRolling recent-trades batch
chart:{propertyId}publiccandle_updateOHLCV candle updates (channel name is singular chart)
markprice:{propertyId}publicmark_priceCanonical mark price (about 1s, on change)
volume:{propertyId}publicvolume_updateSession-to-date settled volume (volume24h), on settlement
ipo:{ipoId}publicipo_allocation_updatePrimary-market allocation progress
leaderboardpublicleaderboard_updateFull competition leaderboard, on change
portfolio:{userId}privatesee belowYour account deltas

Portfolio message types

The private channel is a delta stream. You receive separate frames rather than a full portfolio snapshot on every tick:

typeMeaning
balances_updateCash / frozen balances changed
position_updatePosition quantity or entry changed
order_statusOrder reached a terminal or intermediate status
order_updateOrder fields changed (e.g. remaining quantity)
trade_newYour fill
lifetime_volume_updateYour lifetime traded volume changed
transfer_updateCash or token transfer into/out of your account
offering_order_updateIPO subscription status (PENDING / ALLOCATED / REJECTED)

To value positions live, combine position_update with the markprice channel — the server does not push recomputed portfolio totals on every price tick. Seed volume24h from REST property detail / trade list, then replace it from volume_update frames.


Example: order book

{ "type": "orderbook_update", "propertyId": 42, "bids": [{ "price": 124.0, "quantity": 5.0, "orderId": 8801 }], "asks": [{ "price": 126.0, "quantity": 3.0, "orderId": 8802 }] }

Example: public trades batch

{ "type": "trades_batch", "propertyId": 42, "trades": [ { "tradeId": 9001, "propertyId": 42, "aggressorSide": "BUY", "price": 125.5, "quantity": 2.0, "timestamp": 1718000000 } ] }

Example: mark price

{ "type": "mark_price", "propertyId": 42, "price": 125.25 }

Example: session volume

{ "type": "volume_update", "propertyId": 42, "volume24h": 1204.5 }

Example: your fill (private)

{ "type": "trade_new", "trade": { "tradeId": 9002, "propertyId": 42, "side": "BUY", "quantity": 1.0, "price": 125.5 } }

Client example

const ws = new WebSocket('wss://api.loafmarkets.com/ws'); ws.onopen = () => { ws.send(JSON.stringify({ type: 'auth', token: process.env.LOAF_API_KEY, timestamp: Math.floor(Date.now() / 1000), })); ws.send(JSON.stringify({ type: 'subscribe', channels: ['orderbook:42', `portfolio:${process.env.LOAF_USER_ID}`], timestamp: Math.floor(Date.now() / 1000), })); }; ws.onmessage = (event) => { const msg = JSON.parse(String(event.data)); if (msg.type === 'orderbook_update') { console.log('book', msg.bids?.[0], msg.asks?.[0]); } if (msg.type === 'trade_new') { console.log('filled', msg.trade); } };

A REST 200 on create-order means the exchange accepted the order — not that it filled. Fills and cancellations arrive asynchronously on portfolio:{userId} (trade_new, order_status, order_update).

Last updated on