Skip to content

Cap'n Proto schema

With GET /v1/ws?format=capnp, every binary frame of the server is one ServerMessage, serialized with the Cap'n Proto packed encoding. The libraries decode it for you; this page is for writing a client of your own.

@0xe79b2a519cd80da8;

# Binary frames of the Klondike WebSocket API (`GET /v1/ws?format=capnp`).
#
# * Every binary frame sent by the server holds exactly one `ServerMessage`,
#   serialized with the Cap'n Proto *packed* encoding (one segment table, no
#   framing beyond the WebSocket frame).
# * Client messages (subscribe, unsubscribe, ping) are always JSON text frames,
#   whatever the format (see the WebSocket documentation).
# * The fields mirror the JSON messages one to one, with two encodings that
#   JSON does not need:
#     - a missing number (no bid on a venue, for instance) is NaN, a missing
#       timestamp is 0;
#     - the venue of an order book level is an index into `Orderbook.venues`.
# * Timestamps are milliseconds since the Unix epoch (UTC). Prices are in the
#   quote currency of the symbol; quantities in its base currency, except for
#   inverse perpetuals (BTC/USD:BTC) where they are in USD, as on the venues.

struct ServerMessage {
  union {
    trades       @0 :Trades;
    orderbook    @1 :Orderbook;
    overviewJson @2 :Text;
    # The overview is pushed every few seconds at most and has many optional
    # fields: it is sent as the JSON document of `GET /v1/overview`.
    subscribed   @3 :Subscription;
    unsubscribed @4 :Subscription;
    error        @5 :Error;
    pong         @6 :Void;
  }
}

struct Subscription {
  channel @0 :Text;   # trades | orderbook | overview
  symbol  @1 :Text;   # empty for overview
}

struct Error {
  code    @0 :Text;   # bad_request, unknown_channel, too_many_subscriptions...
  message @1 :Text;
  channel @2 :Text;   # the subscription the error is about, empty otherwise
  symbol  @3 :Text;
}

enum Side {
  unknown @0;
  buy     @1;
  sell    @2;
}

struct Trade {
  ts    @0 :Int64;    # exchange time, ms (receive time when the venue gives none)
  venue @1 :Text;     # CCXT exchange id: binance, binanceusdm, krakenfutures...
  price @2 :Float64;
  qty   @3 :Float64;
  side  @4 :Side;     # aggressor (taker) side
}

# The trades received since the previous message (batched every 100 ms).
struct Trades {
  symbol @0 :Text;
  trades @1 :List(Trade);   # oldest first
}

struct VenueQuote {
  venue  @0 :Text;
  ts     @1 :Int64;     # last update of the venue book, 0 if unknown
  bid    @2 :Float64;   # NaN when the venue has no bid
  bidQty @3 :Float64;
  ask    @4 :Float64;   # NaN when the venue has no ask
  askQty @5 :Float64;
}

struct Level {
  price @0 :Float64;
  qty   @1 :Float64;
  venue @2 :UInt16;     # index into Orderbook.venues
}

# The merged order book of every venue: the best bid/ask of each venue and the
# first levels of each side, every level with the venue it comes from.
struct Orderbook {
  symbol @0 :Text;
  ts     @1 :Int64;
  venues @2 :List(VenueQuote);
  bids   @3 :List(Level);   # best (highest) first
  asks   @4 :List(Level);   # best (lowest) first
}

Download: klondike_api.capnp.

Decoding a frame, in pseudo code:

msg  = capnp.unpack(frame)            # packed encoding
root = msg.root(ServerMessage)
switch root.which():
  trades       -> Trades: symbol, trades[ts, venue, price, qty, side]
  orderbook    -> Orderbook: venues[i] = {venue, ts (0: none), bid/ask (NaN: none)}
                  bids/asks[j] = {price, qty, venue = venues[venue].venue}
  overviewJson -> JSON.parse(text), the document of GET /v1/overview
  subscribed / unsubscribed -> {channel, symbol}
  error        -> {code, message, channel, symbol}
  pong

The repository has the same messages encoded in both formats in testdata/messages: <n>-<type>.json and <n>-<type>.capnp decode to the same message, which makes a good test for a decoder of your own.