Skip to content

WebSocket ​

Connection ​

URL: wss://api.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",
      "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.

An update's sequence is the latest transaction its statistics reflect, and a sequence is never repeated: changes that reflect the same transaction, such as one per market it touched, produce one update, so previous_sequence is always smaller than sequence. The rolling 24-hour values are recomputed whenever an update is sent. No update is sent while no new transaction is processed, so read GET /v1/marketStats for the values as of now.

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.