基本信息
状态:已实现。
本页汇总现货 API 所有端点都适用的基础约定:Base URL、请求规范、两种签名方案(附可直接复制的示例)、限流、错误码,以及与 Binance Spot 的差异汇总。
Base URL
| 环境 | REST | WebSocket |
|---|---|---|
| 生产 | https://api.rocky.exchange | wss://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 拒绝。
规范化载荷
被签名的字符串按收到的请求原样构造:
- 取原始 query string(
?之后的全部内容)和原始请求体。 - 若只有其中一个非空,它就是载荷。若两者都非空,载荷为
<query>&<body>。 - 载荷必须以
&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 测量你的时钟偏移(见 时间同步)。
校验顺序
检查按以下顺序执行,你收到的是第一个失败项:
- 鉴权前按 IP 的洪泛刹车(24000 权重/分钟)→
429 -1003 X-MBX-APIKEY存在且格式正确 →-2014- 请求体 ≤ 64 KB 且为合法 UTF-8 →
-1100 signature存在且只包含[A-Za-z0-9+/=_%-]→-1102(缺失)/-1022(非法字符)timestamp/recvWindow解析及时间窗检查 →-1102/-1100/-1021- key 存在且未吊销 →
-2015 - 签名按 key 类型校验通过 →
-1022 - 按 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×tamp=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×tamp=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×tamp=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×tamp=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 的默认值 | 1200 | 50 | 200 |
ext-mm | 外部做市商(在 接入 时分配) | 2400 | 100 | 600 |
house | Rocky 自有做市集群 | 6000 | 200 | 1200 |
按 IP 的上限
| 桶 | 适用范围 | 上限 |
|---|---|---|
| 公开权重 | 未鉴权的 /api/v3/*(行情数据) | 6000 权重 / 1 分钟 |
| 鉴权前洪泛刹车 | 签名的 /api/v3/*,在签名校验之前计费 | 24000 权重 / 1 分钟 |
| WebSocket 连接数 | /ws/* 与 /stream 上的并发连接 | 每 IP 16 个 |
客户端 IP 取自 Rocky 边缘代理设置的 X-Real-IP。如果你的公司在同一个 NAT 后用多个进程报价,请相应地为共享的 IP 刹车预留预算。IP 桶豁免仅保留给 Rocky 自有基础设施主机,不向外部参与方开放;按 key 的档位始终生效。