Skip to main content

SBE Order Entry Integration

Overview​

  • Channel: Private MM WebSocket only (not available on public WS).
  • Transport: WebSocket binary frames — each frame contains exactly one SBE message (no JSON).
  • Encoding: SBE (Simple Binary Encoding), little-endian. schemaId = 2, version = 2.
  • Purpose: High-performance, low-latency order entry — create, replace, and cancel orders individually or in batch.
  • Compression: Disabled to avoid head-of-line blocking and CPU overhead.

Testnet​

Testnet URL: wss://stream-testnet.bybits.org/v5/trade-sbe

SBE XML Template​

sbe xml template

Connection​

Connection Lifecycle​

  1. Open WebSocket connection.
  2. Send AuthReq (templateId = 1).
  3. Receive AuthResp (templateId = 2) — proceed only if retCode = 0.
  4. Send order requests (CreateOrderReqV5, ReplaceOrderReqV5, CancelOrderReqV5, or batch variants).
  5. Receive the corresponding response for each request.
  6. Send PingReq (templateId = 3) periodically; expect PongResp (templateId = 4) in return.

Heartbeat​

  • Send a PingReq every 10 s to keep the connection alive.
  • If no data is received within 2 × heartbeat interval, reconnect and re-authenticate.

Reconnect Strategy​

  • Use exponential backoff with jitter.
  • Re-authenticate immediately after reconnect before resuming order flow.
  • Use orderLinkId for client-side idempotency — query order status before resubmitting.

Authentication Flow​

Send AuthReq​

Signature: HMAC-SHA256 over "GET/realtime{expires}", where expires is a future Unix timestamp in milliseconds.

import hashlib, hmac, struct, time

def generate_signature(api_secret: str, expires: int) -> str:
message = f"GET/realtime{expires}"
return hmac.new(
api_secret.encode("utf-8"),
message.encode("utf-8"),
hashlib.sha256,
).hexdigest()

Receive AuthResp​

{
"header": {
"block_length": 132,
"template_id": 2,
"schema_id": 2,
"version": 2
},
"reqId": "req_00000000001",
"retCode": 0,
"connId": "d30fdpbboasp1pjbe7r0",
"retMsg": "OK"
}
  • retCode = 0 — authentication succeeded.
  • Any non-zero retCode is a failure; read retMsg for the cause.

Order Operations​

Create Order​

Send CreateOrderReqV5 (templateId = 5), receive CreateOrderRespV5 (templateId = 6).

  • price and qty use Decimal64: value = mantissa × 10^exponent.
  • orderLinkId is a fixed 64-byte char field (null-padded); used for client-side deduplication.

CreateOrderRespV5 decode example:

{
"header": {
"block_length": 364,
"template_id": 6,
"schema_id": 2,
"version": 2
},
"respHeader": {
"reqId": "req_00000000002",
"connId": "d30fdpbboasp1pjbe7r0",
"traceId": "abc123def456789",
"timeNow": 1780902835759344,
"inTime": 1780902835758533,
"bapiLimit": 1000,
"bapiLimitStatus": 999,
"bapiLimitResetTimestamp": 1780902835758
},
"retCode": 0,
"result": {
"orderId": "1912284048591699456",
"orderLinkId": "cli_order_001"
},
"retMsg": "OK"
}

Replace Order​

Send ReplaceOrderReqV5 (templateId = 7), receive ReplaceOrderRespV5 (templateId = 8).

  • Identify the order to replace by orderId or orderLinkId (at least one required).
  • Submit new qty and/or price as Decimal64.

Cancel Order​

Send CancelOrderReqV5 (templateId = 9), receive CancelOrderRespV5 (templateId = 10).

  • Identify the order to cancel by orderId or orderLinkId.

Batch Operations​

All batch requests embed an ApiRequestHeader at the top, followed by the category field, then a repeating group of order items. The group is prefixed by a groupSize16Encoding header (uint16 blockLength + uint16 numInGroup).

OperationRequest Template IDResponse Template ID
Batch Create1112
Batch Replace1314
Batch Cancel1516

Each response group item carries its own code and msg (varString8), plus orderId / orderLinkId for the acknowledged order. A top-level retMsg (varString8) follows all groups.

Error Handling​

CommonErrResp (templateId = 17) is returned when the server cannot associate an error with a specific request message.

Fields: respHeader (ApiRespHeader), retCode (int32), retMsg (varString8).

SBE Message Structure​

Message Header (8 bytes)​

FieldTypeSize (bytes)Description
blockLengthuint162Fixed-body length
templateIduint162Message type identifier
schemaIduint162Fixed = 2
versionuint162Fixed = 2

Composite Types​

ApiRequestHeader (140 bytes)​

Embedded at the start of every request message.

FieldTypeSize (bytes)Description
reqIdchar[64]64Client request ID (optional; echoed in response)
timestampuint648Client timestamp (milliseconds); must satisfy: server_time − recvWindow ≤ timestamp < server_time + 1000
recvWindowuint324Acceptable time window (milliseconds); default 5000
refererchar[64]64Broker / source identifier

ApiRespHeader (232 bytes)​

Embedded at the start of every response message.

FieldTypeSize (bytes)Description
reqIdchar[64]64Echoed client request ID
connIdchar[64]64Connection identifier
traceIdchar[64]64Trace ID for diagnostics
timeNowint648Server timestamp (microseconds)
inTimeint648Message ingress timestamp (microseconds)
bapiLimitint648Total rate limit
bapiLimitStatusint648Remaining rate limit tokens
bapiLimitResetTimestampint648Rate-limit reset timestamp (milliseconds)

CommonOrderRespData (128 bytes)​

FieldTypeSize (bytes)Description
orderIdchar[64]64Exchange-assigned order ID
orderLinkIdchar[64]64Echoed client order ID

Decimal64 (9 bytes)​

FieldTypeSize (bytes)Description
exponentint81Power of 10
mantissaint648Significand

actual_value = mantissa × 10^exponent. Example: price 69000.00 → exponent=0, mantissa=69000; qty 0.01 → exponent=-2, mantissa=1.

Enumerations​

EnumValues (uint8)
CategoryType0=UNKNOWN, 1=SPOT, 2=LINEAR, 3=INVERSE, 4=OPTION, 254=NON_REPRESENTABLE
SideType0=UNKNOWN, 1=BUY, 2=SELL, 254=NON_REPRESENTABLE
OrderType0=UNKNOWN, 1=MARKET, 2=LIMIT, 254=NON_REPRESENTABLE
TimeInForceType0=UNKNOWN, 1=GTC, 2=POST_ONLY, 3=IOC, 4=FOK, 5=RPI, 254=NON_REPRESENTABLE
PositionIdxType0=ONE_WAY, 1=HEDGE_BUY, 2=HEDGE_SELL, 253=UNKNOWN, 254=NON_REPRESENTABLE
MarketUnitType0=UNKNOWN, 1=BASE_COIN, 2=QUOTE_COIN, 254=NON_REPRESENTABLE
SmpType0=UNKNOWN, 1=CANCEL_TAKER, 2=CANCEL_MAKER, 3=CANCEL_BOTH, 254=NON_REPRESENTABLE
BoolEnum0=FALSE, 1=TRUE, 254=NON_REPRESENTABLE

Message Field Tables​

AuthReq (id=1, blockLength=200)​

IDFieldTypeSize (bytes)Description
1reqIdchar[64]64Client request ID
2apiKeychar[64]64API Key (null-padded)
3expiresuint648Expiry timestamp (milliseconds); must be in future
4signaturechar[64]64HMAC-SHA256 over "GET/realtime{expires}"

AuthResp (id=2, blockLength=132)​

IDFieldTypeSize (bytes)Description
1reqIdchar[64]64Echoed request ID
2retCodeint3240 = OK
3connIdchar[64]64Connection identifier
20retMsgvarString16variable"OK" on success; error text otherwise

PingReq (id=3, blockLength=8)​

IDFieldTypeSize (bytes)Description
1timestampuint648Client timestamp (milliseconds)

PongResp (id=4, blockLength=16)​

IDFieldTypeSize (bytes)Description
1timestampuint648Echoed client timestamp (milliseconds)
2pongTimeuint648Server pong timestamp (milliseconds)

CreateOrderReqV5 (id=5, blockLength=242)​

IDFieldTypeSize (bytes)Description
1headerApiRequestHeader140Request header
2categoryCategoryType11=SPOT, 2=LINEAR, 3=INVERSE, 4=OPTION
3symbolIdint648Internal numeric symbol ID
4sideSideType11=BUY, 2=SELL
5orderTypeOrderType11=MARKET, 2=LIMIT
6qtyDecimal649Order quantity
7priceDecimal649Order price; set mantissa=0 for MARKET orders
8orderLinkIdchar[64]64Client order ID (null-padded)
9timeInForceTimeInForceType11=GTC, 2=POST_ONLY, 3=IOC, 4=FOK, 5=RPI
10positionIdxPositionIdxType10=ONE_WAY, 1=HEDGE_BUY, 2=HEDGE_SELL
11marketUnitMarketUnitType11=BASE_COIN, 2=QUOTE_COIN
12isLeverageBoolEnum10=FALSE, 1=TRUE
13reduceOnlyBoolEnum10=FALSE, 1=TRUE
14closeOnTriggerBoolEnum10=FALSE, 1=TRUE
15mmpBoolEnum1Market Maker Protection
16smpTypeSmpType10=UNKNOWN, 1=CANCEL_TAKER, 2=CANCEL_MAKER, 3=CANCEL_BOTH
17rpiTakerAccessBoolEnum10=FALSE, 1=TRUE; added in schema version 2; Supported order combinations: (1) orderType=Market; (2) orderType=Limit with timeInForce=IOC or FOK. See announcement

CreateOrderRespV5 (id=6, blockLength=364)​

IDFieldTypeSize (bytes)Description
1respHeaderApiRespHeader232Response header
2retCodeint3240 = accepted
3resultCommonOrderRespData128Order identifiers
20retMsgvarString16variable"OK" on success

ReplaceOrderReqV5 (id=7, blockLength=295)​

IDFieldTypeSize (bytes)Description
1headerApiRequestHeader140Request header
2categoryCategoryType1Product category
3symbolIdint648Internal numeric symbol ID
4orderIdchar[64]64Order to replace (use orderId or orderLinkId)
5orderLinkIdchar[64]64Client order ID of the order to replace
6qtyDecimal649New quantity
7priceDecimal649New price

ReplaceOrderRespV5 (id=8, blockLength=364)​

Same layout as CreateOrderRespV5.

CancelOrderReqV5 (id=9, blockLength=277)​

IDFieldTypeSize (bytes)Description
1headerApiRequestHeader140Request header
2categoryCategoryType1Product category
3symbolIdint648Internal numeric symbol ID
4orderIdchar[64]64Order to cancel (use orderId or orderLinkId)
5orderLinkIdchar[64]64Client order ID of the order to cancel

CancelOrderRespV5 (id=10, blockLength=364)​

Same layout as CreateOrderRespV5.

BatchCreateOrderReqV5 (id=11)​

Fixed body (141 bytes): header (ApiRequestHeader, 140 bytes) + category (uint8, 1 byte).

Followed by repeating group request (groupSize16Encoding header + items):

IDFieldTypeSize (bytes)Description
1symbolIdint648Internal numeric symbol ID
2sideSideType11=BUY, 2=SELL
3orderTypeOrderType11=MARKET, 2=LIMIT
4qtyDecimal649Order quantity
5priceDecimal649Order price
6orderLinkIdchar[64]64Client order ID
7timeInForceTimeInForceType11=GTC, 2=POST_ONLY, 3=IOC, 4=FOK, 5=RPI
8positionIdxPositionIdxType1Position mode
9marketUnitMarketUnitType11=BASE_COIN, 2=QUOTE_COIN
10isLeverageBoolEnum10=FALSE, 1=TRUE
11reduceOnlyBoolEnum10=FALSE, 1=TRUE
12closeOnTriggerBoolEnum10=FALSE, 1=TRUE
13mmpBoolEnum1Market Maker Protection
14smpTypeSmpType10=UNKNOWN, 1=CANCEL_TAKER, 2=CANCEL_MAKER, 3=CANCEL_BOTH

Per-item blockLength = 100 bytes.

BatchCreateOrderRespV5 (id=12)​

Fixed body (236 bytes): respHeader (232 bytes) + retCode (int32, 4 bytes).

Followed by repeating group list (per-item blockLength = 141 bytes):

IDFieldTypeSize (bytes)Description
1codeint324Per-order result code
2categoryCategoryType1
3symbolIdint648
4orderIdchar[64]64Exchange order ID
5orderLinkIdchar[64]64Client order ID
20msgvarString16variablePer-order message

Followed by top-level retMsg (varString16).

BatchReplaceOrderReqV5 (id=13)​

Fixed body (141 bytes): same as BatchCreateOrderReqV5.

Repeating group request (per-item blockLength = 154 bytes):

IDFieldTypeSize (bytes)Description
1symbolIdint648
2orderIdchar[64]64Order to replace
3orderLinkIdchar[64]64
4qtyDecimal649New quantity
5priceDecimal649New price

BatchReplaceOrderRespV5 (id=14)​

Fixed body (236 bytes). Repeating group list (per-item blockLength = 141 bytes, same fields as BatchCreateOrderRespV5). Followed by retMsg (varString16).

BatchCancelOrderReqV5 (id=15)​

Fixed body (141 bytes). Repeating group request (per-item blockLength = 136 bytes):

IDFieldTypeSize (bytes)Description
1symbolIdint648
2orderIdchar[64]64Order to cancel
3orderLinkIdchar[64]64

BatchCancelOrderRespV5 (id=16)​

Same layout as BatchReplaceOrderRespV5.

CommonErrResp (id=17, blockLength=236)​

IDFieldTypeSize (bytes)Description
1respHeaderApiRespHeader232Response header
2retCodeint324Error code
20retMsgvarString16variableError description

Integration Example​

import hashlib
import hmac
import json
import logging
import struct
import threading
import time
from typing import Any, Dict, Optional, Tuple

import websocket

logging.basicConfig(
filename="logfile_order_entry.log",
level=logging.INFO,
format="%(asctime)s %(levelname)s %(message)s",
)

WS_URL = "wss://stream-testnet.bybits.org/v5/trade-sbe"
API_KEY = "your_api_key"
API_SECRET = "your_api_secret"
RECV_WINDOW = 5000

SCHEMA_ID = 2
VERSION = 2

# Template IDs
TMPL_AUTH_REQ = 1
TMPL_AUTH_RESP = 2
TMPL_PING_REQ = 3
TMPL_PONG_RESP = 4
TMPL_CREATE_REQ = 5
TMPL_CREATE_RESP = 6
TMPL_REPLACE_REQ = 7
TMPL_REPLACE_RESP = 8
TMPL_CANCEL_REQ = 9
TMPL_CANCEL_RESP = 10
TMPL_ERR_RESP = 17

# Enum values
CATEGORY_LINEAR = 2
SIDE_BUY = 1
SIDE_SELL = 2
ORDER_TYPE_LIMIT = 2
ORDER_TYPE_MARKET = 1
TIF_GTC = 1
POSITION_ONE_WAY = 0
MARKET_UNIT_BASE = 1
BOOL_FALSE = 0
BOOL_TRUE = 1
SMP_NONE = 0

# Struct formats (little-endian, no padding)
HDR_FMT = "<HHHH" # messageHeader: 8 bytes
HDR_SZ = struct.calcsize(HDR_FMT)

API_REQ_HDR_FMT = "<64sQI64s" # ApiRequestHeader: 140 bytes
API_REQ_HDR_SZ = struct.calcsize(API_REQ_HDR_FMT)

API_RESP_HDR_FMT = "<64s64s64sqqqqq" # ApiRespHeader: 232 bytes
API_RESP_HDR_SZ = struct.calcsize(API_RESP_HDR_FMT)

COMMON_RESP_FMT = "<64s64s" # CommonOrderRespData: 128 bytes
COMMON_RESP_SZ = struct.calcsize(COMMON_RESP_FMT)

DECIMAL64_FMT = "<bq" # Decimal64: 9 bytes
DECIMAL64_SZ = struct.calcsize(DECIMAL64_FMT)

_req_counter = 0


def _next_req_id() -> str:
global _req_counter
_req_counter += 1
return f"req_{_req_counter:012d}"


def _encode_sbe_header(block_length: int, template_id: int) -> bytes:
return struct.pack(HDR_FMT, block_length, template_id, SCHEMA_ID, VERSION)


def _parse_sbe_header(data: bytes) -> Dict[str, Any]:
bl, tid, sid, ver = struct.unpack_from(HDR_FMT, data, 0)
return {"block_length": bl, "template_id": tid, "schema_id": sid, "version": ver}


def _encode_str(s: str, length: int) -> bytes:
return s.encode("utf-8").ljust(length, b"\x00")[:length]


def _decode_str(b: bytes) -> str:
return b.rstrip(b"\x00").decode("utf-8")


def _encode_decimal64(mantissa: int, exponent: int) -> bytes:
"""Pack a Decimal64. value = mantissa × 10^exponent."""
return struct.pack(DECIMAL64_FMT, exponent, mantissa)


def _parse_varstring16(data: bytes, offset: int) -> Tuple[str, int]:
"""Parse a varString16: uint16 length prefix + UTF-8 data."""
(length,) = struct.unpack_from("<H", data, offset)
offset += 2
s = data[offset: offset + length].decode("utf-8")
offset += length
return s, offset


def _encode_api_req_header(req_id: str = "", referer: str = "") -> bytes:
ts = int(time.time() * 1000)
return struct.pack(
API_REQ_HDR_FMT,
_encode_str(req_id, 64),
ts,
RECV_WINDOW,
_encode_str(referer, 64),
)


def _parse_api_resp_header(data: bytes, offset: int) -> Tuple[Dict[str, Any], int]:
(req_id, conn_id, trace_id,
time_now, in_time,
bapi_limit, bapi_limit_status, bapi_limit_reset) = struct.unpack_from(
API_RESP_HDR_FMT, data, offset
)
offset += API_RESP_HDR_SZ
return {
"reqId": _decode_str(req_id),
"connId": _decode_str(conn_id),
"traceId": _decode_str(trace_id),
"timeNow": time_now,
"inTime": in_time,
"bapiLimit": bapi_limit,
"bapiLimitStatus": bapi_limit_status,
"bapiLimitResetTimestamp": bapi_limit_reset,
}, offset


# ----------------------------- Encoders -----------------------------

def encode_auth_req(api_key: str, api_secret: str) -> bytes:
req_id = _next_req_id()
expires = int(time.time() * 1000) + 10_000
message = f"GET/realtime{expires}"
signature = hmac.new(
api_secret.encode("utf-8"),
message.encode("utf-8"),
hashlib.sha256,
).hexdigest()

body = struct.pack(
"<64s64sQ64s",
_encode_str(req_id, 64),
_encode_str(api_key, 64),
expires,
_encode_str(signature, 64),
)
return _encode_sbe_header(200, TMPL_AUTH_REQ) + body


def encode_ping_req() -> bytes:
body = struct.pack("<Q", int(time.time() * 1000))
return _encode_sbe_header(8, TMPL_PING_REQ) + body


def encode_create_order(
category: int,
symbol_id: int,
side: int,
order_type: int,
qty_mantissa: int,
qty_exponent: int,
price_mantissa: int,
price_exponent: int,
order_link_id: str,
time_in_force: int = TIF_GTC,
position_idx: int = POSITION_ONE_WAY,
market_unit: int = MARKET_UNIT_BASE,
is_leverage: int = BOOL_FALSE,
reduce_only: int = BOOL_FALSE,
close_on_trigger: int = BOOL_FALSE,
mmp: int = BOOL_FALSE,
smp_type: int = SMP_NONE,
rpi_taker_access: int = BOOL_FALSE,
req_id: str = "",
referer: str = "",
) -> bytes:
body = (
_encode_api_req_header(req_id or _next_req_id(), referer) # 140 bytes
+ struct.pack("<Bq", category, symbol_id) # 9 bytes
+ struct.pack("<BB", side, order_type) # 2 bytes
+ _encode_decimal64(qty_mantissa, qty_exponent) # 9 bytes
+ _encode_decimal64(price_mantissa, price_exponent) # 9 bytes
+ _encode_str(order_link_id, 64) # 64 bytes
+ struct.pack("<BBBBBBBB",
time_in_force, position_idx, market_unit,
is_leverage, reduce_only, close_on_trigger,
mmp, smp_type) # 8 bytes
+ struct.pack("<B", rpi_taker_access) # 1 byte (sinceVersion=2)
)
return _encode_sbe_header(242, TMPL_CREATE_REQ) + body


def encode_cancel_order(
category: int,
symbol_id: int,
order_id: str = "",
order_link_id: str = "",
req_id: str = "",
referer: str = "",
) -> bytes:
body = (
_encode_api_req_header(req_id or _next_req_id(), referer)
+ struct.pack("<Bq", category, symbol_id)
+ _encode_str(order_id, 64)
+ _encode_str(order_link_id, 64)
)
return _encode_sbe_header(277, TMPL_CANCEL_REQ) + body


# ----------------------------- Parsers -----------------------------

def parse_auth_resp(data: bytes) -> Dict[str, Any]:
hdr = _parse_sbe_header(data)
offset = HDR_SZ
req_id_b, ret_code, conn_id_b = struct.unpack_from("<64si64s", data, offset)
offset += 132
ret_msg, _ = _parse_varstring16(data, offset)
return {
"header": hdr,
"reqId": _decode_str(req_id_b),
"retCode": ret_code,
"connId": _decode_str(conn_id_b),
"retMsg": ret_msg,
}


def parse_order_resp(data: bytes) -> Dict[str, Any]:
"""Handles CreateOrderRespV5, ReplaceOrderRespV5, CancelOrderRespV5 (same layout)."""
hdr = _parse_sbe_header(data)
offset = HDR_SZ
resp_header, offset = _parse_api_resp_header(data, offset)
(ret_code,) = struct.unpack_from("<i", data, offset)
offset += 4
order_id_b, order_link_id_b = struct.unpack_from(COMMON_RESP_FMT, data, offset)
offset += COMMON_RESP_SZ
ret_msg, _ = _parse_varstring16(data, offset)
return {
"header": hdr,
"respHeader": resp_header,
"retCode": ret_code,
"result": {
"orderId": _decode_str(order_id_b),
"orderLinkId": _decode_str(order_link_id_b),
},
"retMsg": ret_msg,
}


def parse_pong_resp(data: bytes) -> Dict[str, Any]:
hdr = _parse_sbe_header(data)
ts, pong_time = struct.unpack_from("<QQ", data, HDR_SZ)
return {"header": hdr, "timestamp": ts, "pongTime": pong_time}


PARSERS = {
TMPL_AUTH_RESP: parse_auth_resp,
TMPL_CREATE_RESP: parse_order_resp,
TMPL_REPLACE_RESP: parse_order_resp,
TMPL_CANCEL_RESP: parse_order_resp,
TMPL_PONG_RESP: parse_pong_resp,
}

# ----------------------------- WebSocket handlers -----------------------------

def on_message(ws, message):
try:
if not isinstance(message, (bytes, bytearray)):
logging.warning("unexpected text frame: %r", message)
return

data = bytes(message)
hdr = _parse_sbe_header(data)
tid = hdr["template_id"]
parser = PARSERS.get(tid)

if parser is None:
logging.warning("unhandled templateId=%s", tid)
return

decoded = parser(data)
logging.info("templateId=%s %s", tid, decoded)

if tid == TMPL_AUTH_RESP:
print("auth:", decoded)
if decoded["retCode"] == 0:
# Send a sample limit buy order after successful auth
# qty=0.01 → mantissa=1, exponent=-2
# price=69000 → mantissa=69000, exponent=0
order = encode_create_order(
category=CATEGORY_LINEAR,
symbol_id=123456,
side=SIDE_BUY,
order_type=ORDER_TYPE_LIMIT,
qty_mantissa=1,
qty_exponent=-2,
price_mantissa=69000,
price_exponent=0,
order_link_id=_next_req_id(),
referer="my_broker",
)
ws.send(order)
else:
logging.error("auth failed retCode=%s retMsg=%s",
decoded["retCode"], decoded["retMsg"])

elif tid in (TMPL_CREATE_RESP, TMPL_REPLACE_RESP, TMPL_CANCEL_RESP):
print(
f"order resp templateId={tid} retCode={decoded['retCode']} "
f"orderId={decoded['result']['orderId']} "
f"orderLinkId={decoded['result']['orderLinkId']} "
f"retMsg={decoded['retMsg']}"
)

elif tid == TMPL_PONG_RESP:
print("pong:", decoded)

except Exception as e:
logging.exception("decode error: %s", e)
print("decode error:", e)


def on_error(ws, error):
print("WS error:", error)
logging.error("WS error: %s", error)


def on_close(ws, *_):
print("### connection closed ###")
logging.info("connection closed")


def on_open(ws):
print("opened")
ws.send(encode_auth_req(API_KEY, API_SECRET))
print("auth request sent")
threading.Thread(target=_ping_loop, args=(ws,), daemon=True).start()


def _ping_loop(ws):
while True:
try:
ws.send(encode_ping_req())
except Exception:
return
time.sleep(10)


def connWS():
ws = websocket.WebSocketApp(
WS_URL,
on_open=on_open,
on_message=on_message,
on_error=on_error,
on_close=on_close,
)
ws.run_forever(ping_interval=20, ping_timeout=10)


if __name__ == "__main__":
websocket.enableTrace(False)
connWS()

Limits & Errors​

  • Rate limits are surfaced in every response's ApiRespHeader (bapiLimit, bapiLimitStatus, bapiLimitResetTimestamp).
  • Non-zero retCode in any response indicates failure; read retMsg (varString16) for the diagnostic message.
  • CommonErrResp (templateId = 17) is sent when the server cannot associate the error with a specific request.
  • In batch responses, each group item carries its own code and msg — check per-item codes individually.
  • On socket close, reconnect and re-authenticate before resuming; use orderLinkId to detect duplicates.

Compatibility Notes​

  • Byte order: little-endian for all numeric primitives.
  • Decimal64: packed as int8 (exponent) + int64 (mantissa) = 9 bytes, no alignment padding. value = mantissa × 10^exponent.
  • BoolEnum: encoded as uint8; valid values are 0 (FALSE) and 1 (TRUE). Value 254 signals a non-representable state.
  • Fixed-length strings: null-padded to declared length; strip trailing \x00 on decode.
  • varString16: prefixed by a 2-byte uint16 length; follows all fixed fields in the message body.
  • Repeating groups: prefixed by groupSize16Encoding (uint16 blockLength + uint16 numInGroup), before the group items.
  • Client clock must be NTP/PTP-synchronized; server rejects frames where the timestamp falls outside the recvWindow.