Signed Request Walkthrough
This page shows a complete signed request end-to-end for each of BTSE's three 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 three 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.
The three URL shapesβ
BTSE has three URL shapes. The signing algorithm is identical, but the base URL and the signed urlpath differ:
| Surface | Products | Base URL (host) | Signed urlpath (path in spec) |
|---|---|---|---|
/spot prefix | Spot, Earn | https://api.btse.com/spot | /api/v3.3/... |
/futures prefix | Futures | https://api.btse.com/futures | /api/v2.3/... |
public-api | Wallet, OTC, Markets | https://api.btse.com | /public-api/wallet/v1/... (full path) |
The key rule: what you sign is the path that appears in the OpenAPI spec for that endpoint β i.e., everything between the host and the query string. The product prefix /spot / /futures is part of the host, not part of the signed path.
| Always signed | Never signed |
|---|---|
The path from the OpenAPI spec (e.g. /api/v3.3/order) | The product prefix on the host (/spot, /futures) |
| 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 /api/v3.3/user/open_orders β list open orders for a 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/v3.3/user/open_orders?symbol=BTC-USD |
| Signed urlpath | /api/v3.3/user/open_orders |
| Body string | (empty) |
String to sign (concatenated, no separators):
/api/v3.3/user/open_orders1715000000000
Resulting request-sign:
1bbc035a0f728033820489f259fbb55fac146a2678213e8fa843e7b7f8c64df5e8a721b5c1d16c7cf476b4a2960268df
Wire request:
curl -X GET 'https://api.btse.com/spot/api/v3.3/user/open_orders?symbol=BTC-USD' \
-H 'request-api: 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x' \
-H 'request-nonce: 1715000000000' \
-H 'request-sign: 1bbc035a0f728033820489f259fbb55fac146a2678213e8fa843e7b7f8c64df5e8a721b5c1d16c7cf476b4a2960268df'
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 /api/v2.3/order β 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/v2.3/order |
| Signed urlpath | /api/v2.3/order |
| Body | {"price":50000,"side":"BUY","size":1,"symbol":"BTCPFC","time_in_force":"GTC","type":"LIMIT"} |
String to sign:
/api/v2.3/order1715000000000{"price":50000,"side":"BUY","size":1,"symbol":"BTCPFC","time_in_force":"GTC","type":"LIMIT"}
Resulting request-sign:
f29766877732524b814478c2b87f6a36005d6c81d70798c200de3c3fb8c3f87e9a83a33f0ba3822608e235150b356ba5
Wire request:
curl -X POST 'https://api.btse.com/futures/api/v2.3/order' \
-H 'request-api: 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x' \
-H 'request-nonce: 1715000000000' \
-H 'request-sign: f29766877732524b814478c2b87f6a36005d6c81d70798c200de3c3fb8c3f87e9a83a33f0ba3822608e235150b356ba5' \
-H 'Content-Type: application/json' \
-d '{"price":50000,"side":"BUY","size":1,"symbol":"BTCPFC","time_in_force":"GTC","type":"LIMIT"}'
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.
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/spotor/futuresendpoints.
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