Appearance
Realtime WebSocket protocol v2
The API gateway exposes one public realtime endpoint: /ws. The former /stream, thin account sockets, order-book listener, and candle socket are not part of protocol v2. All chain-derived account data remains publicly readable; subscriptions do not add authentication.
Locally, connect to ws://localhost:8090/ws. Production uses wss://api-v1-ab.blink.trade/ws.
Handshake and commands
The gateway's first frame is:
json
{"type":"hello","protocol_version":2,"heartbeat_interval_ms":25000}Clients must reject any protocol version other than 2. Every client command has a non-empty request_id:
json
{
"method":"subscribe",
"request_id":"request_1",
"subscription":{
"channel":"account_state",
"address":"0x0000000000000000000000000000000000000000",
"subaccount_id":0
}
}json
{"method":"unsubscribe","request_id":"request_2","subscription_id":"sub_42"}json
{"method":"ping","request_id":"request_3","timestamp":1784710800000}The subscribe acknowledgement assigns the logical subscription's server ID:
json
{
"type":"subscribed",
"request_id":"request_1",
"subscription_id":"sub_42",
"channel":"account_state"
}Snapshot, update, error, resync, and unsubscribe frames carry this subscription_id. A physical connection may hold any number of logical subscriptions.
Channels
| Channel | Required scope | Snapshot contents |
|---|---|---|
account_state | address, subaccount_id | Margin account, per-market leverage, positions, active spot and perp orders, spot balances, recent spot and perp fills, position fills, and funding payments. |
account_catalog | address | Subaccounts and delegations. |
markets | none | Market metadata, rolling values, margin tiers, and recent funding rates. |
orderbook | market_id, is_perps | Complete aggregated L2 book and sequence. |
trades | market_id, is_perps | Bounded recent trades. |
funding | market_id | Recent funding rates for a perp market. |
mark_price | market_id | Current perp mark and oracle prices. |
market_stats | optional market_id, is_perps | Current rolling market statistics. |
all_mids | none | Current perp mark price keyed by market ID. |
bbo | market_id, is_perps | Current best bid and offer. |
candles | market_id, is_perps, interval | Current trade candle. |
mark_price_candles | market_id, is_perps: true, interval | Current perp mark-price candle. |
Supported candle intervals are 1m, 5m, 15m, 1h, 4h, and 1d. The optional legacy snapshot request field is accepted, but protocol v2 is always snapshot-first.
Snapshot lifecycle
The gateway registers a subscription with live fanout before reading its snapshot. It then sends an atomic lifecycle:
json
{"type":"snapshot_begin","subscription_id":"sub_42","channel":"account_state"}json
{
"type":"snapshot_data",
"subscription_id":"sub_42",
"channel":"account_state",
"operations":[
{"collection":"positions","op":"reset","rows":[]},
{"collection":"orders","op":"reset","rows":[]}
]
}json
{"type":"snapshot_end","subscription_id":"sub_42","channel":"account_state"}Large reset operations are split across snapshot_data frames. Consumers must buffer them and expose the replacement only at snapshot_end. Data-oriented channels use the same lifecycle with a data field instead of operations.
Account, catalog, trade, and market collection updates use insert, upsert, delete, or reset operations. Every operation produced by one sequencer transaction is carried in the same update frame:
json
{
"type":"update",
"subscription_id":"sub_42",
"channel":"account_state",
"operations":[
{"collection":"orders","op":"delete","row":{"client_order_id":17}},
{"collection":"fills","op":"insert","row":{"event_id":"991-3"}}
]
}Append-only rows use stable {tx_index}-{ordinal} event IDs. The transaction watermark used to bridge a durable snapshot to the in-memory replay ring is internal and is not a client resume cursor. If the ring cannot bridge a read, the gateway retries the snapshot and then emits resync_required rather than installing partial state.
Order-book continuity
An order-book snapshot includes the complete book and its installed sequence. Every delta contains absolute quantities plus both continuity values:
json
{
"type":"update",
"subscription_id":"sub_7",
"channel":"orderbook",
"data":{
"market_id":1,
"is_perps":true,
"bids":[["6500000","12"]],
"asks":[],
"previous_sequence":"91",
"sequence":"92",
"timestamp_ms":1784710800000
}
}A quantity of "0" removes the level. If previous_sequence differs from the installed sequence, discard the whole book and resubscribe. After an upstream disconnect, the gateway reseeds durable state and forces a fresh snapshot before subsequent deltas.
Liveness and recovery
Clients should ping every 25 seconds. The gateway replies with the same request_id and optional timestamp in a pong. It disconnects clients that are silent for 60 seconds. Per-connection and per-subscription queues are bounded; a slow consumer receives an error or resync_required and must reconnect or resubscribe for a new snapshot.
Errors contain stable code, human-readable error, and the applicable request_id or subscription_id. A subscription-local replay failure does not corrupt the other subscriptions on the same connection.