Skip to main content

Authentication

All private BTSE API endpoints require three HTTP headers on every request. Public endpoints (market data, orderbook, trades) do not require authentication.

New here? Start here first.

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​

  1. Log in to www.btse.com
  2. Click Account in the top-right corner
  3. Select the API tab
  4. 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:

HeaderTypeDescription
request-apiStringYour API key
request-nonceLongCurrent UTC timestamp in milliseconds (e.g. 1624985375123)
request-signStringHMAC-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 produces 401 Signature verification failed.
  • nonce β€” The same value you send in request-nonce
  • bodyStr β€” 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:

FieldValue
request-nonce1624985375123
request-api4e9536c79f0fdd72bf04f2430982d3f61d9d76c996f0175bbba470d69d59816x
Secret848db84ac252b6726e5f6e7a711d9c96d9fd77d020151b45839a5b59c37203bx
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.

BadgePermissionWhat it unlocks
🌐 PublicNoneMarket data, orderbook, public trades
πŸ”’ ReadReadAccount balances, order status, trade history, wallet history
πŸ”’ TradingTradingPlace, amend, cancel orders
πŸ”’ TransferTransferWithdraw, deposit, transfer between wallets

Rate Limits​

Rate limits are enforced per API endpoint and per user account.

CategoryPer APIPer User
Query15 req/s30 req/s
Orders75 req/s75 req/s

Tiered blocking​

Exceeding a rate limit triggers a tiered block:

TierDuration
1st breach1 second
2nd breach5 minutes
3rd breach15 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.

ProductProductionTestnet
Spot RESThttps://api.btse.com/spothttps://testapi.btse.io/spot
Spot WebSocketwss://ws.btse.com/ws/spotwss://testws.btse.io/ws/spot
Spot OSSwss://ws.btse.com/ws/oss/spotwss://testws.btse.io/ws/oss/spot
Futures RESThttps://api.btse.com/futureshttps://testapi.btse.io/futures
Futures WebSocketwss://ws.btse.com/ws/futureswss://testws.btse.io/ws/futures
Futures OSSwss://ws.btse.com/ws/oss/futureswss://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.