Skip to content

WebSocket

Connection

URL: wss://api-v1-ab.blink.trade/v1/ws

Keepalive requirements

Clients must keep the connection alive by sending at least one frame every 2 minutes. This can be either:

  • A WebSocket ping frame
  • Any application-level message

If the server receives no frames from a client within the two-minute window, it closes the connection. Slow consumers may be disconnected.

Handshake

The server sends the protocol version and keepalive contract when the connection opens:

json
{ "type": "hello", "protocol_version": 1, "keepalive_timeout_ms": 120000 }

Channels

A connection can have at most one subscription per channel. Application-frame timestamps use Unix nanoseconds encoded as decimal strings.

Stateful channels include previous_sequence and sequence. The first update has previous_sequence: null. Before applying a later update, verify that its previous_sequence equals the last installed sequence for that channel. Sequence values are strictly increasing but not necessarily consecutive.

Markets

markets

Subscribe

json
{ "type": "subscribe", "channel": "markets" }

Updates

The complete active market catalog is sent after subscribing:

json
{
  "type": "update",
  "channel": "markets",
  "timestamp_ns": "1785441661123456789",
  "previous_sequence": null,
  "sequence": "812",
  "markets": [
    {
      "market_type": "perp",
      "market_id": 1,
      "symbol": "BTC-USD",
      "base_token": "BTC",
      "quote_token": "USDC",
      "quote_token_id": "token1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxv8v5",
      "base_decimals": 8,
      "quote_decimals": 8,
      "base_atoms_per_base_lot": "100",
      "quote_atoms_per_quote_lot": "1",
      "base_lots_per_base_unit": "1000000",
      "quote_lots_per_quote_unit": "100000000",
      "quote_lots_per_base_unit_per_tick": "1000000",
      "default_maker_fee_100th_bps": 0,
      "default_taker_fee_100th_bps": 200,
      "liquidation_fee_100th_bps": 500,
      "min_size_base_lots": "1",
      "min_order_notional_quote_lots": "1000000",
      "max_market_order_value_quote_lots": "100000000000",
      "max_limit_order_value_quote_lots": "200000000000",
      "max_leverage": 20,
      "oracle_id": "BTC-USD",
      "fee_recipient": "11111111111111111111111111111111",
      "margin_tiers": [
        {
          "tier": 0,
          "notional_lower_bound_quote_lots": "0",
          "max_leverage": 20,
          "maintenance_margin_rate_bps": 250,
          "maintenance_deduction_quote_lots": "0"
        }
      ],
      "status": "active"
    }
  ]
}

Each update contains the complete market catalog.

Unsubscribe

json
{ "type": "unsubscribe", "channel": "markets" }

Market Statistics

  • market-stats/{market_type}
  • market-stats/{market_type}/{market_id}

The market-type channel covers every active market of that type. The market-specific channel covers one market.

Subscribe

json
{ "type": "subscribe", "channel": "market-stats/perp/1" }

Updates

The current statistics are sent after subscribing:

json
{
  "type": "update",
  "channel": "market-stats/perp/1",
  "timestamp_ns": "1785441661123456789",
  "previous_sequence": null,
  "sequence": "812",
  "market_stats": [
    {
      "market_type": "perp",
      "market_id": 1,
      "timestamp_ns": "1785441661123456789",
      "last_price_ticks": "6500000",
      "mark_price_ticks": "6500100",
      "oracle_price_ticks": "6500200",
      "mid_price_ticks": "6500050",
      "best_bid_price_ticks": "6500000",
      "best_ask_price_ticks": "6500100",
      "open_interest_base_lots": "123456",
      "funding_rate_100th_bps": 125,
      "predicted_funding_rate_100th_bps": 140,
      "volume_24h_base_lots": "987654",
      "volume_24h_quote_lots": "64197510",
      "trades_24h": "3200",
      "low_24h_price_ticks": "6400000",
      "high_24h_price_ticks": "6600000",
      "change_24h_bps": 150
    }
  ]
}

Each update contains the complete selected statistics set. Spot rows omit mark price, open interest, leverage, and funding fields.

Unsubscribe

json
{ "type": "unsubscribe", "channel": "market-stats/perp/1" }

Best Bid and Offer

bbo/{market_type}/{market_id}

Subscribe

json
{ "type": "subscribe", "channel": "bbo/perp/1" }

Updates

The current BBO is sent after subscribing. Updates are sent when the best price or size changes:

json
{
  "type": "update",
  "channel": "bbo/perp/1",
  "timestamp_ns": "1785441661123456789",
  "bbo": {
    "bid": { "price_ticks": "6500000", "size_base_lots": "12" },
    "ask": { "price_ticks": "6500100", "size_base_lots": "8" }
  }
}

bid and ask are null when that side of the book is empty.

Unsubscribe

json
{ "type": "unsubscribe", "channel": "bbo/perp/1" }

Order Book

orderbook/{market_type}/{market_id}

Subscribe

json
{ "type": "subscribe", "channel": "orderbook/perp/1" }

Updates

The current order book is sent after subscribing:

json
{
  "type": "update",
  "channel": "orderbook/perp/1",
  "timestamp_ns": "1785441661123456789",
  "previous_sequence": null,
  "sequence": "812",
  "orderbook": {
    "bids": [
      { "price_ticks": "6500000", "size_base_lots": "12" },
      { "price_ticks": "6499900", "size_base_lots": "6" }
    ],
    "asks": [
      { "price_ticks": "6500100", "size_base_lots": "8" },
      { "price_ticks": "6500200", "size_base_lots": "4" }
    ]
  }
}

Later updates contain changed levels with their absolute sizes:

json
{
  "type": "update",
  "channel": "orderbook/perp/1",
  "timestamp_ns": "1785441662123456789",
  "previous_sequence": "812",
  "sequence": "819",
  "orderbook": {
    "bids": [
      { "price_ticks": "6500000", "size_base_lots": "15" }
    ],
    "asks": [
      { "price_ticks": "6500100", "size_base_lots": "0" }
    ]
  }
}

A size of 0 removes the level. Before applying an update, clients must verify that previous_sequence matches the last installed sequence. On mismatch, discard the local book and subscribe again.

Unsubscribe

json
{ "type": "unsubscribe", "channel": "orderbook/perp/1" }

Trades

trades/{market_type}/{market_id}

Subscribe

json
{ "type": "subscribe", "channel": "trades/perp/1" }

Updates

Up to 100 recent trades are sent after subscribing, ordered oldest to newest:

json
{
  "type": "update",
  "channel": "trades/perp/1",
  "timestamp_ns": "1785441661123456789",
  "previous_sequence": null,
  "sequence": "812",
  "trades": [
    {
      "timestamp_ns": "1785441660123456789",
      "price_ticks": "6500000",
      "size_base_lots": "4",
      "taker_is_bid": true
    }
  ]
}

Later frames contain every trade for the market from one transaction:

json
{
  "type": "update",
  "channel": "trades/perp/1",
  "timestamp_ns": "1785441662123456789",
  "previous_sequence": "812",
  "sequence": "819",
  "trades": [
    {
      "timestamp_ns": "1785441662123456789",
      "price_ticks": "6500100",
      "size_base_lots": "2",
      "taker_is_bid": false
    }
  ]
}

Before applying an update, clients must verify that previous_sequence matches the last installed sequence. On mismatch, discard the local trade collection and subscribe again. If no trade history is available, trades is empty and sequence is null.

Unsubscribe

json
{ "type": "unsubscribe", "channel": "trades/perp/1" }

Trade Candles

candles/{market_type}/{market_id}/{resolution}

Subscribe

json
{ "type": "subscribe", "channel": "candles/perp/1/1m" }

Updates

The latest trade candle is sent after subscribing. candles is empty if no trade candle is available:

json
{
  "type": "update",
  "channel": "candles/perp/1/1m",
  "timestamp_ns": "1785441660123456789",
  "candles": [
    {
      "open_time_ms": 1785441600000,
      "open": "6500000",
      "high": "6500100",
      "low": "6499900",
      "close": "6500000",
      "volume_base": "12",
      "volume_quote": "78000000",
      "trades": 3
    }
  ]
}

Updates contain complete candle records identified by open_time_ms:

json
{
  "type": "update",
  "channel": "candles/perp/1/1m",
  "timestamp_ns": "1785441660123456789",
  "candles": [
    {
      "open_time_ms": 1785441600000,
      "open": "6500000",
      "high": "6500100",
      "low": "6499900",
      "close": "6500000",
      "volume_base": "12",
      "volume_quote": "78000000",
      "trades": 3
    },
    {
      "open_time_ms": 1785441660000,
      "open": "6500200",
      "high": "6500200",
      "low": "6500200",
      "close": "6500200",
      "volume_base": "1",
      "volume_quote": "6500200",
      "trades": 1
    }
  ]
}

The first update for a new bucket contains the finalized previous trade candle and the new trade candle. Quiet buckets are not emitted.

Unsubscribe

json
{ "type": "unsubscribe", "channel": "candles/perp/1/1m" }

Mark Price Candles

mark-candles/{market_id}/{resolution}

Subscribe

json
{ "type": "subscribe", "channel": "mark-candles/1/1m" }

Updates

The latest mark price candle is sent after subscribing. candles is empty if no mark price candle is available:

json
{
  "type": "update",
  "channel": "mark-candles/1/1m",
  "timestamp_ns": "1785441660123456789",
  "candles": [
    {
      "open_time_ms": 1785441600000,
      "open": "6500000",
      "high": "6500100",
      "low": "6499900",
      "close": "6500000",
      "samples": 4
    }
  ]
}

Updates contain complete candle records identified by open_time_ms:

json
{
  "type": "update",
  "channel": "mark-candles/1/1m",
  "timestamp_ns": "1785441660123456789",
  "candles": [
    {
      "open_time_ms": 1785441600000,
      "open": "6500000",
      "high": "6500100",
      "low": "6499900",
      "close": "6500000",
      "samples": 4
    },
    {
      "open_time_ms": 1785441660000,
      "open": "6500200",
      "high": "6500200",
      "low": "6500200",
      "close": "6500200",
      "samples": 1
    }
  ]
}

The first update for a new bucket contains the finalized previous mark price candle and the new mark price candle. Quiet buckets are not emitted.

Unsubscribe

json
{ "type": "unsubscribe", "channel": "mark-candles/1/1m" }

Funding Rates

funding-rates/{market_id}

Subscribe

json
{ "type": "subscribe", "channel": "funding-rates/1" }

Updates

The 200 most recent funding rows are sent after subscribing, ordered oldest to newest:

json
{
  "type": "update",
  "channel": "funding-rates/1",
  "timestamp_ns": "1785441661123456789",
  "previous_sequence": null,
  "sequence": "812",
  "funding_rates": [
    {
      "timestamp_ns": "1785438000000000000",
      "sequence": "800",
      "market_id": 1,
      "funding_rate_100th_bps": 25,
      "long_cumulative_funding_quote_lots": "1234",
      "short_cumulative_funding_quote_lots": "-1234"
    }
  ]
}

Each update is a complete snapshot of the 200 most recent funding rows.

Unsubscribe

json
{ "type": "unsubscribe", "channel": "funding-rates/1" }

Account Catalog

accounts/{address}

Subscribe

json
{
  "type": "subscribe",
  "channel": "accounts/11111111111111111111111111111111"
}

Updates

The complete subaccount and delegation catalog is sent after subscribing:

json
{
  "type": "update",
  "channel": "accounts/11111111111111111111111111111111",
  "timestamp_ns": "1785441661123456789",
  "previous_sequence": null,
  "sequence": "812",
  "subaccounts": [
    {
      "subaccount_id": 0,
      "status": "active",
      "created_at_ns": "1785000000000000000"
    }
  ],
  "delegations": [
    {
      "subaccount_id": 0,
      "delegate": "11111111111111111111111111111111",
      "permissions": ["place_orders", "cancel_orders"],
      "expires_at_ns": "1786046460123000000",
      "name": "trading key"
    }
  ]
}

Each update is a complete snapshot of the subaccount and delegation catalog.

Unsubscribe

json
{
  "type": "unsubscribe",
  "channel": "accounts/11111111111111111111111111111111"
}

Account State

account/{address}/{subaccount_id}

Subscribe

json
{
  "type": "subscribe",
  "channel": "account/11111111111111111111111111111111/0"
}

Updates

The first update contains complete current account, balance, position, leverage, and order state. It also contains up to 200 recent fills and funding payments:

json
{
  "type": "update",
  "channel": "account/11111111111111111111111111111111/0",
  "timestamp_ns": "1785441661123456789",
  "previous_sequence": null,
  "sequence": "812",
  "account": {
    "subaccount_id": 0,
    "status": "active",
    "created_at_ns": "1785000000000000000",
    "cross_margin_balance_quote_lots": "900000",
    "total_isolated_margin_quote_lots": "100000",
    "equity_quote_lots": "1000000",
    "available_margin_quote_lots": "800000",
    "transferable_margin_quote_lots": "750000",
    "initial_margin_requirement_quote_lots": "150000",
    "maintenance_margin_requirement_quote_lots": "75000",
    "delegations": []
  },
  "balances": [
    {
      "token_id": "token1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqxv8v5",
      "total_token_atoms": "100000000",
      "locked_token_atoms": "25000000",
      "deposited_token_atoms": "150000000",
      "withdrawn_token_atoms": "50000000"
    }
  ],
  "positions": [],
  "leverages": [],
  "orders": [],
  "fills": [],
  "funding_payments": []
}

Each update is a complete snapshot of current account state, including up to 200 recent fills and funding payments.

Unsubscribe

json
{
  "type": "unsubscribe",
  "channel": "account/11111111111111111111111111111111/0"
}

Error codes

CodeDescription
400Invalid command or channel
404Resource not found
409Channel already subscribed
503Stream unavailable

After a connection closes, reconnect and subscribe again. See Errors for retry behavior.

The frame schema is available in the v1 AsyncAPI specification.