FAQ
What is the maximum allowed drift between timestamp / X-CH-TS and server time?
The server rejects requests whose timestamp is older than 5000 ms. You can widen this window with optional recvWindow.
X-CH-TS header cannot be empty — how to fix?
Log X-CH-TS on failure and confirm it is non-empty before each request.
Why does signature auth always return invalid signature?
Walk this checklist in order — items 1 and 2 cause >90% of cases:
requestPathincludes the gateway prefix by mistake. The full URL ishttps://openapi.bitbaby.com/futures/open/fapi/v1/openOrders, but the signature must use only/fapi/v1/openOrders— do not put/futures/open(or/spot/open) intoPreHash. Same rule for/sapi/...on spot.- The POST body is being serialized twice. A common mistake: you call
json.dumps(obj)to compute the signature, then callrequests.post(json=obj, ...)which serializes again, changing field order or whitespace and breaking the signature. Serialize once → sign that string → send that exact string as the HTTP body. - GET and POST PreHash differ:
GET:timestamp + GET + requestPath + (?queryString)?— no body.POST:timestamp + POST + requestPath + (?queryString)? + body—bodyis the raw request body.- The
?separator is added by the formula;queryStringitself must not start with?.
X-CH-TSand thetimestampinsidePreHashmust be byte-for-byte identical (both 13-digit millisecond strings).- API key and secret are case-sensitive; trim whitespace before use.
Content-Typemust beapplication/json, otherwise you'll see-1017 ILLEGAL_CONTENT_TYPE.- Clock skew above ±5s is rejected. Call
/sapi/v1/timeor/fapi/v1/timeto readserverTime, then fix the local clock or passrecvWindow(in the body for POST, in the query for GET).
When debugging, print the following three values on the client and diff them against your packet capture:
- The HTTP method, URL, headers, and body you actually send
- The exact
PreHashstring you used to compute the signature - The resulting
X-CH-SIGN
PreHash examples (identical to General information):
GET example: 1588591856950GET/sapi/v1/openOrders?symbol=BTCUSDT&limit=10
POST example: 1588591856950POST/sapi/v1/order/test{"symbol":"BTCUSDT","price":"9300","volume":"1","side":"BUY","type":"LIMIT"}Headers example:
Content-Type: application/json
X-CH-APIKEY: 44c541a1-****-****-****-10fe390df2
X-CH-TS: 1574327555669
X-CH-SIGN: c50d0a74bb9427a9a03933d0eded03af9bf50115dc5b706882a4fcf07a26b761ILLEGAL_CONTENT_TYPE (-1017) — why?
Send header Content-Type: application/json on requests.
Are there per-second API rate limits?
Yes—see each endpoint’s documented rate limits.
How are API rate limits applied?
Private data is limited per API key. Public data is limited per IP. If you call public endpoints with a valid user context, limits may follow the API key instead.
What causes HTTP 429?
Too many requests; slow down.
Will I get IP-banned for exceeding rate limits? For how long?
Usually not—reducing frequency is enough.
Why does WebSocket disconnect?
- Missing heartbeat—keep ping/pong to stabilize the connection.
- Network loss so the server never sees pong, or other network issues.
- Implement reconnect logic with heartbeat so the client recovers after drops.
Requests time out
Check network connectivity to the server.
How do I list all trading pairs?
Use spot GET /sapi/v1/symbols.
Are there limits for batch place/cancel?
Yes—batch endpoints allow at most 10 orders per call.
What is newClientOrderId for?
- A custom id you assign to an order; after placement you can query by
newClientOrderId. - You must keep ids unique—we do not dedupe; duplicates may cause cancel/query to affect only the latest matching row.
How do I get the latest trade price?
Use the ticker endpoint; last is the latest price.
Can 24h volume on the ticker decrease?
Yes. Rolling 24h stats can move with the window, so volume/amount in a later window may be lower than in the previous one.