Signed Request Walkthrough
This page shows a complete signed request end-to-end for each of BTSE's URL shapes. Every part of the request is explicit β wire URL, signed payload, headers, body, and a runnable cURL β in one place, on one screen.
If you've read Authentication and are still getting 401 Authentication Failed or api parameter is mandatory, the walkthroughs below are the canonical reference.
The signatures shown below were generated with the demo API key, secret, and nonce in each section. Plug those exact values into your code and you should compute the same request-sign β if you don't, your signer disagrees with ours. Use the Auth Tester to verify in the browser.
What you signβ
The signing algorithm is identical for every endpoint, but the base URL and the signed urlpath differ per surface:
| Surface | Products | Base URL (= spec server) | Signed urlpath (= spec path) |
|---|---|---|---|
| Spot v4 | Spot | https://api.btse.com | /spot/api/v4/... |
| Futures v3 | Futures | https://api.btse.com/futures/api | /v3/... |
/spot prefix | Earn | https://api.btse.com/spot | /api/v3.3/invest/... |
public-api | Wallet, OTC, Markets | https://api.btse.com | /public-api/... (full path) |
The key rule: what you sign is the path exactly as it appears in the OpenAPI spec for that endpoint β i.e., everything between the host and the query string. Whatever the spec places in the server URL (e.g. /futures/api for Futures) is part of the host, not part of the signed path. For Spot v4 the /spot prefix is part of the spec path, so it is signed.
| Always signed | Never signed |
|---|---|
The path from the OpenAPI spec (e.g. /spot/api/v4/trade/orders) | Any prefix that lives in the server URL (/futures/api, /spot for Earn) |
| The current nonce in milliseconds | Query string (even when present on the wire) |
| The raw request body for POST/PUT/DELETE | Header values |
Example 1 β Spot signed GET with a query stringβ
Endpoint: GET /spot/api/v4/trade/orders β list the caller's open orders, optionally filtered by symbol.
Demo inputs (use these to reproduce the signature in your own code):
| Field | Value |
|---|---|
| API key | 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x |
| Secret | 848db84ac252b6726e5f6e7a711d9c96d9fd77d020151b45839a5b59c37203bx |
| Nonce (ms) | 1715000000000 |
| Method | GET |
| Wire URL | https://api.btse.com/spot/api/v4/trade/orders?symbol=BTC-USD |
| Signed urlpath | /spot/api/v4/trade/orders |
| Body string | (empty) |
String to sign (concatenated, no separators):
/spot/api/v4/trade/orders1715000000000
Resulting request-sign:
b50e5f80f6a5bec9185fb6dac72bad3ec5b27775b8da2f3c5f9c52b1b393ced5772f06228f82c76b6e7b66ad8ace8651
Wire request:
curl -X GET 'https://api.btse.com/spot/api/v4/trade/orders?symbol=BTC-USD' \
-H 'request-api: 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x' \
-H 'request-nonce: 1715000000000' \
-H 'request-sign: b50e5f80f6a5bec9185fb6dac72bad3ec5b27775b8da2f3c5f9c52b1b393ced5772f06228f82c76b6e7b66ad8ace8651'
Why the query string is missing from the signed payload: the server reconstructs
urlpathfrom the request without the?...portion. If you include?symbol=BTC-USDin the string you sign, the server computes a different signature and returns401 Authentication Failed. Send the query string on the wire, but do not sign it.
Example 2 β Futures signed POST with a JSON bodyβ
Endpoint: POST /v3/trade/orders β place a futures order.
Demo inputs:
| Field | Value |
|---|---|
| API key | 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x |
| Secret | 848db84ac252b6726e5f6e7a711d9c96d9fd77d020151b45839a5b59c37203bx |
| Nonce (ms) | 1715000000000 |
| Method | POST |
| Wire URL | https://api.btse.com/futures/api/v3/trade/orders |
| Signed urlpath | /v3/trade/orders |
| Body | {"symbol":"BTC-PERP","orderType":"LIMIT","orderSide":"BUY","orderSize":1,"orderPrice":57009.5} |
String to sign:
/v3/trade/orders1715000000000{"symbol":"BTC-PERP","orderType":"LIMIT","orderSide":"BUY","orderSize":1,"orderPrice":57009.5}
Resulting request-sign:
ce94311ce48fd256e0f724377b24b16a222d7fbbd282bdc933ecfeda5d0a2c87ca2dba26a193ca5281312a231e021038
Wire request:
curl -X POST 'https://api.btse.com/futures/api/v3/trade/orders' \
-H 'request-api: 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x' \
-H 'request-nonce: 1715000000000' \
-H 'request-sign: ce94311ce48fd256e0f724377b24b16a222d7fbbd282bdc933ecfeda5d0a2c87ca2dba26a193ca5281312a231e021038' \
-H 'Content-Type: application/json' \
-d '{"symbol":"BTC-PERP","orderType":"LIMIT","orderSide":"BUY","orderSize":1,"orderPrice":57009.5}'
The body you sign must be byte-identical to the body you send. If your HTTP client reorders keys, adds whitespace, or re-stringifies the JSON differently between signing and sending, the signature will not match. Serialize once, sign that string, send that string.
Note the Futures base URL is
https://api.btse.com/futures/apiβ the/futures/apiprefix lives in the host, so the signedurlpathstarts at/v3/....
Example 3 β Wallet signed GET on public-apiβ
Endpoint: GET /public-api/wallet/v1/user/assets β list wallet assets.
Demo inputs:
| Field | Value |
|---|---|
| API key | 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x |
| Secret | 848db84ac252b6726e5f6e7a711d9c96d9fd77d020151b45839a5b59c37203bx |
| Nonce (ms) | 1715000000000 |
| Method | GET |
| Wire URL | https://api.btse.com/public-api/wallet/v1/user/assets |
| Signed urlpath | /public-api/wallet/v1/user/assets |
| Body string | (empty) |
String to sign:
/public-api/wallet/v1/user/assets1715000000000
Resulting request-sign:
f1a04a8842726e92b4d4bf915b485b818ad707ac23014fa7eddbce5ac2c4ac3288f9491275a378c112376919c03c5309
Wire request:
curl -X GET 'https://api.btse.com/public-api/wallet/v1/user/assets' \
-H 'request-api: 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x' \
-H 'request-nonce: 1715000000000' \
-H 'request-sign: f1a04a8842726e92b4d4bf915b485b818ad707ac23014fa7eddbce5ac2c4ac3288f9491275a378c112376919c03c5309'
The whole
/public-api/...path is signed β there is no separate product prefix to strip, because the host is justhttps://api.btse.com. This is the difference betweenpublic-apiendpoints (Wallet, OTC, Markets) and Futures (/futures/apiprefix in the host).
Verifying your signer against these examplesβ
Drop the inputs from any example above into your signer and compute HMAC-SHA384(secret, urlpath + nonce + bodyStr) β you should get the same request-sign shown.
If they don't match, the most likely causes (in order):
- You're hashing the wrong string. Print the exact bytes your code passes to HMAC and compare character-by-character with the "String to sign" block.
- You included the query string (Example 1 is the canonical test for this).
- Your body string differs from the one you signed (Example 2 is the canonical test for this).
- Wrong algorithm. It is HMAC-SHA384, hex-encoded β not SHA256, not SHA512, not base64.
- Wrong header names. Use
request-api,request-nonce,request-signβ notX-BTSE-*,BTSE-API-KEY, etc. Wrong header names typically surface asapi parameter is mandatory, not a signature error.
See Error Codes β Common error strings for a fuller lookup of opaque error messages to root causes.
See alsoβ
- Authentication β full reference: headers, permissions, rate limits, multi-language signer code
- Auth Tester β interactive signature tool and live request runner
- Error Codes β HTTP and API status code reference
- Machine-readable specs β OpenAPI and AsyncAPI files for code generation and agent consumption