跳到主要内容

基本信息

状态:已实现。

本页汇总现货 API 所有端点都适用的基础约定:Base URL、请求规范、两种签名方案(附可直接复制的示例)、限流、错误码,以及与 Binance Spot 的差异汇总。

Base URL​

环境RESTWebSocket
生产https://api.rocky.exchangewss://api.rocky.exchange
沙盒见你的 集成包(Integration Pack)见你的集成包

请求规范​

  • 所有参数一律放在 query string 中,GET、POST、DELETE 均如此。签名校验器可以接受 form-encoded 的请求体(见 规范化载荷),但现货接口的处理函数只从 query string 读取参数。把参数放在请求体里的 POST /api/v3/order 会通过签名校验,随后被框架以纯文本 HTTP 400 拒绝(而不是 {code,msg} 结构)。这比 Binance 更严格——Binance 两处都读。
  • 未知参数会被忽略。列表类端点上 timeInForce、quoteOrderQty、startTime、endTime、fromId、orderId 等 Binance 专有参数不起作用,除非页面另有说明。
  • 响应为 application/json。所有小数(价格、数量、余额)均为字符串;时间戳为 Unix 毫秒,以 JSON 数字表示。
  • orderId 与成交 id 均为 UUID 字符串,不是整数。
  • 交易对为大写、带连字符(CBTC-USDCB),匹配时区分大小写。

端点安全类型​

类型含义
NONE公开。无需 API key。按客户端 IP 限流。
USER_DATA需要 X-MBX-APIKEY 与有效签名。按 key 限流。
TRADE同 USER_DATA,并额外计入订单计数桶。

鉴权​

签名端点需要三样东西:X-MBX-APIKEY header、timestamp 参数,以及作为规范化载荷中最后一个参数的 signature。key 的类型——HMAC 或 ED25519——在签发时确定,并决定运行哪个校验器;除此之外请求格式完全相同。

API key header​

X-MBX-APIKEY: <key>

key 为 1–256 个字符,取自 [A-Za-z0-9_-]。其他任何内容(或缺少该 header)都会以 -2014 拒绝。

规范化载荷​

被签名的字符串按收到的请求原样构造:

  1. 取原始 query string(? 之后的全部内容)和原始请求体。
  2. 若只有其中一个非空,它就是载荷。若两者都非空,载荷为 <query>&<body>。
  3. 载荷必须以 &signature=<value> 结尾(或在没有其他内容时恰好为 signature=<value>)。校验器按最后一个 &signature= 切分;其之前的部分就是你必须逐字节签名的字符串。

由此推论:

  • signature 必须是最后一个参数。其后的任何内容都会被视为签名值的一部分,导致校验失败。
  • 请对你实际发送的字节签名。签名后不要重新编码、重新排序或追加参数。百分号编码是载荷的一部分;如果 HTTP 库的编码方式与签名器不一致,会得到 -1022。
  • 请求体上限为 64 KB(超出返回 -1100)。实践中请保持请求体为空,把所有内容放进 query string(见 请求规范)。

timestamp 与 recvWindow​

参数必填规则
timestamp是Unix 毫秒。缺失或非数字 → -1102。
recvWindow否毫秒。默认 5000,上限 60000。负数、非数字或超过上限 → -1100。

仅当 |serverTime − timestamp| ≤ recvWindow 时请求才被接受;否则返回 -1021。该检查在查找 key 之前执行,因此即使 key 也有问题,时钟问题仍会以 -1021 报告。用 GET /api/v3/time 测量你的时钟偏移(见 时间同步)。

校验顺序​

检查按以下顺序执行,你收到的是第一个失败项:

  1. 鉴权前按 IP 的洪泛刹车(24000 权重/分钟)→ 429 -1003
  2. X-MBX-APIKEY 存在且格式正确 → -2014
  3. 请求体 ≤ 64 KB 且为合法 UTF-8 → -1100
  4. signature 存在且只包含 [A-Za-z0-9+/=_%-] → -1102(缺失)/ -1022(非法字符)
  5. timestamp / recvWindow 解析及时间窗检查 → -1102 / -1100 / -1021
  6. key 存在且未吊销 → -2015
  7. 签名按 key 类型校验通过 → -1022
  8. 按 key 的请求权重桶与订单计数桶 → 429 -1003

HMAC-SHA256 密钥​

对于 HMAC 类型的 key,你持有一个共享 secret。signature 是以该 secret 为密钥、对规范化载荷计算的 HMAC-SHA256 的小写十六进制。比较是逐字节精确的,因此大写十六进制会被拒绝。

黄金向量(即 Binance 文档中的向量;网关自身的测试套件也固定了它):

secret = NhqPtmdSJYdKjVHjA7PZj4Mge3R5YNiP1e3UZjInClVN65XAbvqqM6A7H5fATj0j
payload = symbol=LTCBTC&side=BUY&type=LIMIT&timeInForce=GTC&quantity=1&price=0.1&recvWindow=5000&timestamp=1499827319559
HMAC-SHA256 hex = c8db56825ae71d6d79447849e617115f4a920fa2acdcab2b053c4b2838bd6b71

Python:

import hashlib, hmac, time, urllib.parse, requests

BASE = "https://api.rocky.exchange"
API_KEY = "your_key"
API_SECRET = "your_secret"

def signed_query(params: dict) -> str:
params = {**params, "timestamp": int(time.time() * 1000)}
payload = urllib.parse.urlencode(params)
sig = hmac.new(API_SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()
return f"{payload}&signature={sig}"

headers = {"X-MBX-APIKEY": API_KEY}

# USER_DATA: GET /api/v3/account
r = requests.get(f"{BASE}/api/v3/account?{signed_query({})}", headers=headers)
print(r.status_code, r.headers.get("x-mbx-used-weight-1m"), r.json())

# TRADE: POST /api/v3/order — parameters go in the query string, body stays empty
q = signed_query({"symbol": "CBTC-USDCB", "side": "BUY", "type": "LIMIT",
"quantity": "0.001", "price": "50000"})
r = requests.post(f"{BASE}/api/v3/order?{q}", headers=headers)
print(r.status_code, r.json())

Ed25519 密钥​

对于 ED25519 类型的 key,你自行生成密钥对,只登记公钥(32 字节,base64)。Rocky 永远不会接触私钥。这是机构接入的推荐方案。

  • signature 是对规范化载荷(与 HMAC key 签名的字符串完全相同)计算的 64 字节 Ed25519 签名,以 base64 编码。标准字母表(+/=)和 URL-safe 字母表(-_)都接受,值也可以百分号编码——query string 中的 + / = 以 %2B %2F %3D 形式到达时,会在校验前先解码。
  • 被签名的载荷就是规范化字符串的原始字节;你这一侧不需要做哈希。
  • 校验使用严格 Ed25519(RFC 8032)。任何解码失败或不匹配都返回 -1022。

参考向量,与网关单元测试(fapi/sign.rs)完全一致。Ed25519 是确定性的,因此正确的实现会得到完全相同的签名:

seed (32 bytes) = 0x07 repeated 32 times
public key base64 = 6kpsY+KcUgq+9VB7Ey7F+ZVHdq6+vnuSQh7qaRRG0iw=
payload = symbol=CBTC-USDCB&side=BUY&type=LIMIT&quantity=0.001&price=50000&timestamp=1756600000000
signature base64 = chwbFjh1nJSjnj+i0fFMMmtTUigvPTjwk5maFhIFWrf24/xNQohyGlGAhXQS6ciKveL7pQQ7Gy4VfodWpB9fDQ==

使用 PyNaCl 的 Python 示例:

import base64, time, urllib.parse, requests
from nacl.signing import SigningKey

BASE = "https://api.rocky.exchange"
API_KEY = "your_key"
seed = bytes([7] * 32) # replace with your own 32-byte seed, kept secret
sk = SigningKey(seed)
print("register this public key:", base64.b64encode(bytes(sk.verify_key)).decode())

def sign(payload: str) -> str:
return base64.b64encode(sk.sign(payload.encode()).signature).decode()

# Self-test against the reference vector
vector = "symbol=CBTC-USDCB&side=BUY&type=LIMIT&quantity=0.001&price=50000&timestamp=1756600000000"
assert sign(vector) == "chwbFjh1nJSjnj+i0fFMMmtTUigvPTjwk5maFhIFWrf24/xNQohyGlGAhXQS6ciKveL7pQQ7Gy4VfodWpB9fDQ=="

params = {"symbol": "CBTC-USDCB", "side": "BUY", "type": "LIMIT",
"quantity": "0.001", "price": "50000", "timestamp": int(time.time() * 1000)}
payload = urllib.parse.urlencode(params)
# GET/DELETE and POST alike: query string, signature last, percent-encode the base64
url = f"{BASE}/api/v3/order?{payload}&signature={urllib.parse.quote(sign(payload), safe='')}"
r = requests.post(url, headers={"X-MBX-APIKEY": API_KEY})
print(r.status_code, r.json())

使用 cryptography 的 Python 示例:

import base64, time, urllib.parse, requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey

BASE = "https://api.rocky.exchange"
API_KEY = "your_key"
sk = Ed25519PrivateKey.from_private_bytes(bytes([7] * 32)) # your secret seed
pk = sk.public_key().public_bytes(serialization.Encoding.Raw, serialization.PublicFormat.Raw)
print("register this public key:", base64.b64encode(pk).decode())

def sign(payload: str) -> str:
return base64.b64encode(sk.sign(payload.encode())).decode()

vector = "symbol=CBTC-USDCB&side=BUY&type=LIMIT&quantity=0.001&price=50000&timestamp=1756600000000"
assert sign(vector) == "chwbFjh1nJSjnj+i0fFMMmtTUigvPTjwk5maFhIFWrf24/xNQohyGlGAhXQS6ciKveL7pQQ7Gy4VfodWpB9fDQ=="

payload = urllib.parse.urlencode({"timestamp": int(time.time() * 1000)})
url = f"{BASE}/api/v3/account?{payload}&signature={urllib.parse.quote(sign(payload), safe='')}"
r = requests.get(url, headers={"X-MBX-APIKEY": API_KEY})
print(r.status_code, r.json())

生成待登记的密钥(上面两个库都会打印公钥;用 OpenSSL 的话):

openssl genpkey -algorithm ed25519 -out spot-ed25519.pem
# 32-byte raw public key, base64 — this is the value you hand to Rocky
openssl pkey -in spot-ed25519.pem -pubout -outform DER | tail -c 32 | base64

公钥如何登记、指纹如何通过带外渠道确认,见 机构接入。

限流​

限流适用于每一个 /api/v3/* 请求。它与 Binance 同形:固定的、按墙钟对齐的时间窗;一个请求权重桶和两个订单计数桶;用量通过响应 header 报告;桶耗尽时返回 HTTP 429 与 -1003。

按 key 的档位(签名端点)​

每个 API key 都有一个档位。下表为默认上限;实际生效的值由 GET /api/v3/exchangeInfo 公布(rateLimits,standard 档位)。

档位适用对象请求权重 / 1 分钟订单数 / 10 秒订单数 / 1 分钟
standard所有 key 的默认值120050200
ext-mm外部做市商(在 接入 时分配)2400100600
houseRocky 自有做市集群60002001200

按 IP 的上限​

桶适用范围上限
公开权重未鉴权的 /api/v3/*(行情数据)6000 权重 / 1 分钟
鉴权前洪泛刹车签名的 /api/v3/*,在签名校验之前计费24000 权重 / 1 分钟
WebSocket 连接数/ws/* 与 /stream 上的并发连接每 IP 16 个

客户端 IP 取自 Rocky 边缘代理设置的 X-Real-IP。如果你的公司在同一个 NAT 后用多个进程报价,请相应地为共享的 IP 刹车预留预算。IP 桶豁免仅保留给 Rocky 自有基础设施主机,不向外部参与方开放;按 key 的档位始终生效。

请求权重​

权重端点
10GET /api/v3/exchangeInfo、GET /api/v3/allOrders、GET /api/v3/myTrades
5GET /api/v3/depth、GET /api/v3/klines、GET /api/v3/trades、GET /api/v3/ticker/24hr
1其余全部,包括 POST / GET / DELETE /api/v3/order、openOrders、account、ping、time

权重按 Rocky 的实际服务成本定价,不遵循 Binance 的表(例如 depth 无论 limit 多少都是 5)。

只有 POST /api/v3/order 计入订单桶。撤单不计入,因此做市商随时可以撤回报价。被订单桶拒绝的下单仍然消耗了其请求权重。

响应 header​

Header出现于值
X-MBX-USED-WEIGHT-1M每一个公开与签名响应当前分钟内已使用的权重,按 IP(公开)或按 key(签名)
X-MBX-ORDER-COUNT-10SPOST /api/v3/order 的响应当前 10 秒窗口内已下的订单数
X-MBX-ORDER-COUNT-1MPOST /api/v3/order 的响应当前分钟内已下的订单数

被限流时​

HTTP/1.1 429 Too Many Requests
X-MBX-USED-WEIGHT-1M: 1203

{"code":-1003,"msg":"Too many requests."}
  • 时间窗是固定的,不是滑动的。用量在每个对齐窗口的起点重置,跨窗口边界的突发可以短暂达到上限的 2 倍——与 Binance 行为一致。
  • 没有 418 升级,也没有 Retry-After header。 Binance 会把持续违规者升级为 418 I'm a teapot 并封禁 IP;Rocky 不会。持续撞桶的客户端只是持续收到 429。请退避到下一个窗口;不要等待一个永远不会到来的 418。
  • WebSocket 连接上限在 HTTP upgrade 阶段执行:同一 IP 的第 17 个并发连接收到的是 429 -1003 而不是 101。

错误码​

所有现货错误使用 FAPI 的信封格式:

{ "code": -1013, "msg": "Filter failure: NOTIONAL" }
HTTPcodemsg触发场景
500-1000An unknown error occurred while processing the request.数据库或下游故障。
429-1003Too many requests.某个限流桶耗尽,或达到按 IP 的 WebSocket 连接上限。
400-1013Filter failure: NOTIONALprice × quantity 低于该交易对的 minNotional(钱包出资账户)。
401-1021Timestamp for this request is outside of the recvWindow.时钟漂移或请求过期。
401-1022Signature for this request is not valid.secret/key 错误、规范化载荷错误、签名格式错误。
400-1100Illegal characters found in a parameter.另外还有:side 非法、type 不支持、MARKET 单带了 price、orderId 非 UUID、recvWindow 超范围、交易服务拒绝了某个非法参数。
400-1102Mandatory parameter was not sent, was empty/null, or malformed.缺少 symbol、quantity、price(LIMIT)、orderId、timestamp 或 signature。
422-2010Account has insufficient balance for requested action.可用余额不足以锁定。市场未激活或下单被风控闸门暂停时也会返回同一 msg(下游均表现为同一种前置条件失败)。
422-2010Market order rejected: no counterparty liquidity.MARKET 单的对手方一侧为空。
422-2010Account is frozen pending settlement review.钱包出资账户被镜像对账器冻结;见 链上结算。
422-2011Order does not exist.未知 orderId,或订单已不在挂单中。
422-2012Complete wallet enrollment (MemberAuth) before trading.已绑定 party 但尚未完成 MemberAuth 的账户;见 机构接入。
400-2014API-key format invalid.X-MBX-APIKEY 缺失或格式错误。
401-2015Invalid API-key, IP, or permissions for action.key 未知或已吊销。

注意 -2010 被三种不同的拒绝复用,与 Binance 用它表示各种订单被拒原因的做法一致;请通过 msg 区分。

时间同步​

  • 所有 timestamp 值均为 Unix 毫秒。
  • GET /api/v3/time 返回 {"serverTime": <ms>},权重 1。在启动时以及每次收到 -1021 后计算一次 offset = serverTime − localNow,并把它加到你的时间戳上。
  • 保持 recvWindow 较小(默认 5000 对同机房客户端已合适)。放大它会掩盖时钟漂移,并扩大被截获请求的重放窗口。

与 Binance 的差异​

汇总清单;每一项也会在受影响的页面上注明。

方面Rocky 行为
交易对大写、带连字符、大小写敏感(CBTC-USDCB)。流名为小写且保留连字符(cbtc-usdcb@trade)。
标识符orderId 与成交 id 为 UUID 字符串。不要按数值排序,也不要用作 fromId 游标(不支持)。
参数只从 query string 读取;处理函数不读取 form 请求体。未知参数被忽略。
exchangeInfo没有 baseAssetPrecision / quotePrecision / quoteAssetPrecision,没有 permissions,没有 PERCENT_PRICE 或 MAX_NUM_ORDERS 过滤器。精度由 tickSize / stepSize 隐含。
手续费链上结算的成交 maker 与 taker 手续费均为零(commission = 0)。
myTrades没有 orderId 字段。多两个字段:settlementStatus 与 cantonUpdateId。internal 表示该成交未在链上结算(自有机器人分段,或在启用链上结算之前执行)。
trades最新在前(Binance 返回最旧在前)。
限流没有 418 封禁升级,没有 Retry-After。权重遵循 Rocky 的成本表。撤单不计入订单数。
订单类型仅 LIMIT(GTC)与 MARKET(IOC,5% 保护价带)。没有 timeInForce、quoteOrderQty、icebergQty、stopPrice、OCO。
自成交防护始终开启:撮合器会跳过你自己的挂单,绝不与你自己成交。不可配置。
查询端点GET /api/v3/order 只返回挂单;已完成订单来自 allOrders。列表端点没有 startTime / endTime / fromId / orderId 过滤。
WebSocket没有 SUBSCRIBE / UNSUBSCRIBE 控制消息(流列表由 URL 固定),没有 listenKey 用户数据流,没有增量深度,没有 @aggTrade / @bookTicker / !ticker@arr,K 线仅 1m。