For the complete documentation index, see llms.txt. This page is also available as Markdown.

Streaming

Stream live market and account data with the SDK's WsClient, on one connection.

WsClient is the SDK's third class, alongside Info and Exchange. It carries nine push feeds over a single WebSocket connection and delivers frames on a background thread, so nothing here asks you to write async.

Reach for it when polling will not do. /info reads default to one per second per client IP (configurable per integration), which cannot drive a quoting loop; the socket pushes instead of costing a read.

1. Connect

from native_core import Info

info = Info("https://api.native.org")
ws = info.ws(on_reconnect=lambda: print("resubscribed"))   # reuses the loaded market table
ws.connect()

Info.ws() and Exchange.ws() hand the client the market table they already loaded, so "ETH/USDT" resolves without another request. WsClient(base_url) works standalone; a client that only ever subscribes by market id makes no HTTP request at all.

For a node you run yourself, pass the WebSocket URL directly (WsClient("ws://localhost:8081")) — the derivation from an HTTP base URL holds only for the public endpoints.

2. Subscribe

Nine feeds: three by market, one global, five by address.

By market
By address

subscribe_trades(market)

subscribe_user_fills(address)

subscribe_l2_book(market)

subscribe_order_updates(address)

subscribe_bbo(market)

subscribe_open_orders(address)

subscribe_spot_state(address)

subscribe_spot_credit_state(address)

Plus one global feed with no topic argument: subscribe_all_mids().

Each returns a Subscription you can pass to unsubscribe(...). Both calls block until the server answers and raise SubscriptionError if it refuses — a silent stream is almost always a refused subscription, so the SDK makes that loud rather than letting it hide.

Ten subscriptions per connection, and one connection per client IP. Multiplex everything onto the one socket; see rate limits for the full table.

3. Consume

Two ways, and you can mix them on one client.

Callbacks run on the reading thread, so they must return quickly. Use them for cheap work like tracking top of book.

stream() yields frames from every subscription created without a callback. It buffers for you, so it is the right choice when handling a message takes real work.

Each stream() item is the full {"channel", "data"} envelope, because the channel is what tells the feeds apart. The buffer holds queue_maxsize frames (10,000 by default); a consumer slower than the feed overflows it and the oldest frames are dropped rather than stalling the reader. ws.dropped_messages counts them — check it, do not assume it is zero.

React on bbo, not on l2Book: the book is a throttled snapshot at 500 ms, while bbo pushes on every change to the top of book.

4. What survives a reconnect, and what does not

reconnect is on by default: the client reopens the socket and restores every subscription, then fires on_reconnect. That is where to re-read anything that gapped.

Feed
Behaviour

l2Book, allMids, openOrders, spotState, spotCreditState

Complete state every frame, so the next frame repairs any gap. Nothing to backfill

bbo

Complete state, but only pushed when the top of book changes. On a quiet market a resubscribe yields nothing at all, so read Info.l2_book once if you need top of book immediately

userFills

The first frame after subscribing replays your most recent 100 fills, so an ordinary resubscribe backfills itself

trades, orderUpdates

No replay. A gap is a gap

Deduplicate fills by (user, tid), not by tid alone: both sides of a trade receive the same tid. And serialize tid as a string before handing it to a browser — the value exceeds JavaScript's safe integer range, which collapses two fills into one silently.

5. Trade on the same socket

Optional, and Exchange.order over HTTP is still the recommended write path. When you want orders on the connection you already hold:

build_order runs every local check; sign_action signs without sending and consumes a nonce, so the body is single-use. What comes back is an ordinary /trade response, read exactly as over HTTP: accepted is a verdict on the transaction, so check is_order_failed or take next_action. See Accepted is not placed.

ws.post_info(payload) runs an /info read over the same connection. It shares the one per-IP read budget with HTTP, so it is convenience, not extra quota.

Field names differ from HTTP

The socket speaks the protocol's own vocabulary and the SDK passes it through rather than inventing a third one. One pair is a trap:

Over HTTP
Over the socket

available / locked (Info.user_balances)

total / hold (spotState), where total is available plus locked

price / quantity / order_count

px / sz / n

Reading total where you used to read available overstates your free balance whenever an order is resting. The full mapping is in the WebSocket reference.

Runnable example

examples/ws_feeds.py ships in the source distribution and needs no key:

Next

API ReferenceWebSocket

Last updated