wss://api.liftx.io/v1/stream using a client that supports an HTTP Authorization: Bearer … upgrade header. read permission and current entitlement are required. Do not put a key in the URL or a WebSocket subprotocol.
This is a state stream. Trading commands remain durable HTTP requests. The connection reuses Liftx publications and does not create a bot-specific exchange socket.
Authorization and connection scope
All channel controls belong to one exactexchange_link_id. A connection cannot switch to another link by sending another control. Use a separate authorized connection when another link is required, rather than multiplexing account authority implicitly.
Instrument-restricted credentials cannot subscribe to link-wide balances, positions pages or private indexes. An exact-instrument ticker or authorized exact-position history remains subject to scope checks. Expiry has an exact connection deadline. Local revocation cancels registered streams, and an authority sweep detects out-of-process changes within its 30-second interval.
Client controls: JSON text frames
Position page offset is 0–10,000,000 and limit is 1–200. Each control can include at most one ticker channel and at most one position-history channel. History selection uses a monotonically increasing request ID within that connection; an identical current tuple can be repeated. Do not reuse an older request ID for a different history demand.
Server data frame header
Each binary WebSocket message contains one seven-byte header followed by its payload. WebSocket fragmentation is resolved by the WebSocket library before this parser runs.
Require
message_length == 7 + payload_length. Set a bounded receive limit appropriate to the documented pages; reject unknown versions and malformed/trailing payloads. Control ping/pong/close frames are WebSocket protocol operations, not this application envelope.
Do not assume all payloads are JSON or that the same version applies to every channel.
Balances: channel 1, version 1
For an update frame, the payload is:count: uint32 little-endian.- For each row, an asset string ending in NUL (
0x00). - A signed int64 little-endian free balance with fixed scale 12.
message_type:3) with the single payload byte 0x01 marks readiness for the empty complete snapshot path. Completion metadata also arrives on channel 6. The connection’s exact link supplies scope; the balance row itself has no link field.
Positions: channel 2, version 1
Position publications contain revision, product/account identity, OPEN/close/SL/TP rows, canonical allocation/execution amounts, FSM projections and exact fixed-point fields. The full schema is included under
Position and PositionOrder in the OpenAPI contract.
Do not overwrite a complete position with a partial event as if it were a full model. Apply only understood event fields for the correct link/position/generation. If an event requires state that is absent or cannot be safely interpreted, mark the local view incomplete and reconcile the affected position. Coalesce that read; do not poll every position on every event.
History notifications use event type position_pnl_history. Its data includes position_id, request_id, ready and reset. Verify the exact requested tuple before reading history. A reset invalidates the retained history baseline; a ready/reset notification is not the history samples themselves.
Tickers: channel 3, version 2
Every ticker payload begins with:- One-byte kind:
1topic mapping or2price batch. - A uint16 little-endian row count.
3 and kind 1. Each row contains:
An update frame has message type
1 and kind 2. Each row contains:
Retain the mapping for the current connection/generation and resolve updates only against it. An unknown topic is not permission to infer a market from the numeric ID. Rebuild the mapping after reconnect. Exchange/source values describe the mapped publication; they do not override execution authority from your authenticated link/catalog.
Position and order indexes
Channel 4 snapshots usekind:"positions_index_snapshot"; deltas use "positions_index_delta". Channel 5 uses "orders_index_snapshot" and "orders_index_delta".
Common fields are exchange_link_id, kind, session_gen (an exact decimal string) and rev. A snapshot adds complete and items; each item contains exchange_link_id and instrument_id. A delta has optional add/remove arrays with the same item shape.
Positions index messages also include server_epoch, active_total and inactive_total. These count positions, while the items array is an instrument index, not one row per position. The orders index is likewise an instrument membership index, not a full executable-order inventory.
Private control: channel 6, version 1
snapshot_required is true, do not continue making trading decisions from an assumed complete cache. Rebuild the appropriate canonical snapshot. is_snapshot and revisions describe state publication; neither is a command receipt or fill confirmation.
Minimal decoder example
Download decode_stream.py for a bounded standard-library parser of the documented framing, balance and ticker wire formats. It does not open a connection, store credentials, reconstruct position FSMs or submit commands. JSON channels are returned as decoded values for a separate versioned application model. The decoder retains fractional JSON numbers as Pythondecimal.Decimal, integers as exact Python integers and fixed-point coefficient strings as strings. It does not convert money through binary floating-point or turn a large finite exponent into floating-point infinity. Application schema validation must still reject values outside each field’s documented bounds.
A transport decoder alone is not a production SDK. A real client must implement schema validation, exact-link checks, snapshot/revision rules, bounded queues, credential lifecycle, websocket ping/pong and reconciliation before trading from observed state.