Authentication
All private BTSE API endpoints require three HTTP headers on every request. Public endpoints (market data, orderbook, trades) do not require authentication.
Set up your API key and understand the signing process before calling any private endpoint. Most integration failures are authentication issues.
Need to verify your signing code? Use the Auth Tester to compute signatures and send live test requests from your browser.
Step 1 β Create an API Keyβ
- Log in to www.btse.com
- Click Account in the top-right corner
- Select the API tab
- Click New API to generate a key and passphrase
Important: The passphrase is shown only once at creation time. Store it securely β you cannot retrieve it again.
Step 2 β Understand the Three Headersβ
Every authenticated request must include these three headers:
| Header | Type | Description |
|---|---|---|
request-api | String | Your API key |
request-nonce | Long | Current UTC timestamp in milliseconds (e.g. 1624985375123) |
request-sign | String | HMAC-SHA384 signature (see below) |
Step 3 β Construct the Signatureβ
The signature is computed as:
HMAC-SHA384(secret, urlpath + nonce + bodyStr)
Where:
secretβ Your API secret (the passphrase from Step 1)urlpathβ The path portion of the URL only (e.g./api/v3.3/order). Do not include query strings. Query parameters are sent on the wire but are NOT part of the signed payload β including them produces401 Signature verification failed.nonceβ The same value you send inrequest-noncebodyStrβ The raw JSON request body. Use an empty string""for GET requests (no body)
The three parts are concatenated directly with no separators between them.
Shellβ
echo -n "/api/v3.3/order1624985375123{\"postOnly\":false,\"price\":8500.0,\"side\":\"BUY\",\"size\":0.002,\"stopPrice\":0.0,\"symbol\":\"BTC-USD\",\"time_in_force\":\"GTC\",\"trailValue\":0.0,\"triggerPrice\":0.0,\"txType\":\"LIMIT\",\"type\":\"LIMIT\"}" \
| openssl dgst -sha384 -hmac "YOUR_SECRET"
Pythonβ
import hmac, hashlib, time, json, requests
API_KEY = "your-api-key"
API_SECRET = "your-api-secret"
BASE_URL = "https://api.btse.com/spot"
def signed_request(method, path, body=None):
nonce = str(int(time.time() * 1000))
body_str = json.dumps(body) if body else ""
message = path + nonce + body_str
signature = hmac.new(
API_SECRET.encode(), message.encode(), hashlib.sha384
).hexdigest()
headers = {
"request-api": API_KEY,
"request-nonce": nonce,
"request-sign": signature,
"Content-Type": "application/json",
}
url = BASE_URL + path
resp = requests.request(method, url, headers=headers,
json=body if method != "GET" else None)
return resp.json()
# Example: query open orders
orders = signed_request("GET", "/api/v3.3/user/open_orders")
JavaScript / Node.jsβ
import crypto from "crypto";
const API_KEY = "your-api-key";
const API_SECRET = "your-api-secret";
const BASE_URL = "https://api.btse.com/spot";
async function signedRequest(method, path, body) {
const nonce = Date.now().toString();
const bodyStr = body ? JSON.stringify(body) : "";
const message = path + nonce + bodyStr;
const signature = crypto
.createHmac("sha384", API_SECRET)
.update(message)
.digest("hex");
const resp = await fetch(BASE_URL + path, {
method,
headers: {
"request-api": API_KEY,
"request-nonce": nonce,
"request-sign": signature,
"Content-Type": "application/json",
},
body: body ? JSON.stringify(body) : undefined,
});
return resp.json();
}
// Example: query open orders
const orders = await signedRequest("GET", "/api/v3.3/user/open_orders");
Goβ
package main
import (
"crypto/hmac"
"crypto/sha512"
"encoding/hex"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
)
const (
apiKey = "your-api-key"
apiSecret = "your-api-secret"
baseURL = "https://api.btse.com/spot"
)
func signedRequest(method, path, body string) ([]byte, error) {
nonce := strconv.FormatInt(time.Now().UnixMilli(), 10)
message := path + nonce + body
mac := hmac.New(sha512.New384, []byte(apiSecret))
mac.Write([]byte(message))
signature := hex.EncodeToString(mac.Sum(nil))
req, _ := http.NewRequest(method, baseURL+path, strings.NewReader(body))
req.Header.Set("request-api", apiKey)
req.Header.Set("request-nonce", nonce)
req.Header.Set("request-sign", signature)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
return io.ReadAll(resp.Body)
}
func main() {
data, err := signedRequest("GET", "/api/v3.3/user/open_orders", "")
if err != nil {
panic(err)
}
fmt.Println(string(data))
}
Worked example β Place an orderβ
Given these values:
| Field | Value |
|---|---|
request-nonce | 1624985375123 |
request-api | 4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x |
| Secret | 848db84ac252b6726e5f6e7a711d9c96d9fd77d020151b45839a5b59c37203bx |
| URL path | /api/v3.3/order |
| Request body | {"postOnly":false,"price":8500.0,"side":"BUY","size":0.002,"stopPrice":0.0,"symbol":"BTC-USD","time_in_force":"GTC","trailValue":0.0,"triggerPrice":0.0,"txType":"LIMIT","type":"LIMIT"} |
String to sign:
/api/v3.3/order1624985375123{"postOnly":false,"price":8500.0,"side":"BUY","size":0.002,"stopPrice":0.0,"symbol":"BTC-USD","time_in_force":"GTC","trailValue":0.0,"triggerPrice":0.0,"txType":"LIMIT","type":"LIMIT"}
Resulting request-sign:
e9cd0babdf497b536d1e48bc9cf1fadad3426b36406b5747d77ae4e3cdc9ab556863f2d0cf78e0228c39a064ad43afb7
Permission Typesβ
API keys carry one or more permissions. Endpoints enforce the minimum required permission. You can see which permission each endpoint needs from the badge at the top of its section.
| Badge | Permission | What it unlocks |
|---|---|---|
| π Public | None | Market data, orderbook, public trades |
| π Read | Read | Account balances, order status, trade history, wallet history |
| π Trading | Trading | Place, amend, cancel orders |
| π Transfer | Transfer | Withdraw, deposit, transfer between wallets |
Rate Limitsβ
Rate limits are enforced per API endpoint and per user account.
| Category | Per API | Per User |
|---|---|---|
| Query | 15 req/s | 30 req/s |
| Orders | 75 req/s | 75 req/s |
Tiered blockingβ
Exceeding a rate limit triggers a tiered block:
| Tier | Duration |
|---|---|
| 1st breach | 1 second |
| 2nd breach | 5 minutes |
| 3rd breach | 15 minutes |
The block timer resets if no further violations occur within 1 hour, or after the 15-minute block expires.
A Retry-After header is included in all 429 responses indicating the unlock timestamp.
Environmentβ
Test your integration without risking real funds. Testnet endpoints mirror production behaviour.
| Product | Production | Testnet |
|---|---|---|
| Spot REST | https://api.btse.com/spot | https://testapi.btse.io/spot |
| Spot WebSocket | wss://ws.btse.com/ws/spot | wss://testws.btse.io/ws/spot |
| Spot OSS | wss://ws.btse.com/ws/oss/spot | wss://testws.btse.io/ws/oss/spot |
| Futures REST | https://api.btse.com/futures | https://testapi.btse.io/futures |
| Futures WebSocket | wss://ws.btse.com/ws/futures | wss://testws.btse.io/ws/futures |
| Futures OSS | wss://ws.btse.com/ws/oss/futures | wss://testws.btse.io/ws/oss/futures |
SDKs and Librariesβ
BTSE provides auto-generated SDK packages for Python, JavaScript, and Java. See Client SDKs to download. The signing examples above (Python, JavaScript, Go) can be used as a starting point for manual integration in any other language.
Other resources:
- The API Explorer provides interactive request testing with auto-generated code snippets
- The OpenAPI specs in this documentation can be used with standard OpenAPI code generators (e.g.
openapi-generator-cli) to produce client libraries in any language - Download specs from the API Explorer β each product's overview page includes a spec download link
If you build a community SDK and would like it listed here, contact support.btse.com.