API

Read SpotEx.trade market data with no key, or trade from your own software with a signed private API.

Public API
/api/public/v1/*

Market data anyone may read — prices, the order book, trades, candles, markets and assets.

No key and no signature. 1200 requests per minute per address, and readable from a browser on any site.

Private API
/api/private/v1/*

Your account — balances, orders, fills and a private live feed.

Every request is signed with an API key. No key can ever withdraw.

Public API
Binance-shaped responses. Only the endpoints below exist; anything else under /api/public/v1 is 404.
RequestParametersDoes
GET /api/public/v1/timeServer time in ms — the clock private signatures are checked against
GET /api/public/v1/pingConnectivity check
GET /api/public/v1/exchangeInfoEvery market: status, precision, tick and step size, minimum notional, fees
GET /api/public/v1/coinssymbol?Assets, their price, and whether deposits and withdrawals are open
GET /api/public/v1/ticker/24hrsymbol?24-hour statistics for one market, or all
GET /api/public/v1/ticker/pricesymbol?Last price for one market, or all
GET /api/public/v1/ticker/bookTickersymbol?Best bid and ask for one market, or all
GET /api/public/v1/depthsymbol, limit (20–100)Order book as [price, amount] pairs, best price first
GET /api/public/v1/tradessymbol, limit (50–200)Recent trades
GET /api/public/v1/aggTradessymbol, limit (50–200)Recent trades, aggregated
GET /api/public/v1/klinessymbol, interval, limit (200–1000), endTime?Candles — intervals 1m 5m 15m 30m 1h 4h 1D 1W

Every response carries X-RateLimit-Limit and X-RateLimit-Remaining; past the limit you get 429 rate_limited with Retry-After. Market data is cached for one second.

For updates as they happen, subscribe to public topics on the WebSocket — no ticket needed. See streaming.

curl "https://spotex.trade/api/public/v1/depth?symbol=BTC-USDT&limit=5"

// in a browser, from any site — reads are CORS-open
const book = await fetch("https://spotex.trade/api/public/v1/ticker/24hr?symbol=BTC-USDT")
  .then((r) => r.json());
Private API — keys

Create keys under Security → API keys. A key can read your account and, if you allow it, place and cancel orders. No key can ever withdraw.

The secret is shown once and stored only encrypted. Restrict trading keys to your server's IP address, and revoke a key the moment you suspect it has leaked — it stops working at once.

Signing a request
  • X-SPOTEX-KEY — your key id
  • X-SPOTEX-TIMESTAMP — milliseconds since the epoch; within 5 s of server time (set X-SPOTEX-RECV-WINDOW up to 60000 ms for more). Sync from GET /api/public/v1/time.
  • X-SPOTEX-SIGNATURE — hex HMAC-SHA256 of timestamp + METHOD + path + query + body with your secret

Path and query exactly as sent; the body is the exact JSON string sent, or nothing. Amounts are decimal strings"0.015", never 0.015.

import { createHmac } from "node:crypto";

async function call(method, pathAndQuery, body) {
  const timestamp = String(Date.now());
  const raw = body ? JSON.stringify(body) : "";
  const signature = createHmac("sha256", SECRET)
    .update(timestamp + method + pathAndQuery + raw)
    .digest("hex");
  const res = await fetch(BASE_URL + pathAndQuery, {
    method,
    headers: {
      "X-SPOTEX-KEY": KEY_ID,
      "X-SPOTEX-TIMESTAMP": timestamp,
      "X-SPOTEX-SIGNATURE": signature,
      ...(raw && { "Content-Type": "application/json" }),
    },
    body: raw || undefined,
  });
  return res.json();
}

await call("POST", "/api/private/v1/order", {
  symbol: "BTC-USDT", side: "BUY", type: "LIMIT",
  price: "78000.00", size: "0.0150", timeInForce: "GTC",
  clientOrderId: "grid-42",
});
import hashlib, hmac, json, time, requests

def call(method, path_and_query, body=None):
    ts = str(int(time.time() * 1000))
    raw = json.dumps(body, separators=(",", ":")) if body else ""
    sig = hmac.new(SECRET.encode(), (ts + method + path_and_query + raw).encode(),
                   hashlib.sha256).hexdigest()
    headers = {"X-SPOTEX-KEY": KEY_ID, "X-SPOTEX-TIMESTAMP": ts, "X-SPOTEX-SIGNATURE": sig}
    if raw:
        headers["Content-Type"] = "application/json"
    return requests.request(method, BASE_URL + path_and_query,
                            headers=headers, data=raw or None).json()
Private endpoints
The stream ticket connects you to the same WebSocket the website uses: send {"op":"auth","token":"…"}, then subscribe to book:, trades: or tickers. Your own balances, orders and fills arrive without subscribing.
RequestKey needsDoes
GET /api/private/v1/accountreadBalances (available, locked) and whether the key can trade
GET /api/private/v1/openOrders?symbol=readOrders still holding funds
GET /api/private/v1/order?clientOrderId= | ?orderId=readOne order
GET /api/private/v1/myTrades?symbol=&limit=readYour fills, newest first (limit up to 500)
GET /api/private/v1/streamreadA short-lived ticket for the private WebSocket feed
POST /api/private/v1/ordertradePlace an order
DELETE /api/private/v1/order?clientOrderId= | ?orderId=tradeAsk to cancel one order
DELETE /api/private/v1/openOrders?symbol=tradeAsk to cancel every resting order
Orders

A limit order needs price and size; timeInForce is GTC (default), IOC or FOK, and postOnly applies to GTC. A market sell needs size; a market buy needs quoteAmount, the total to spend. Market orders never fill beyond the exchange's price-protection band; a limit priced far through the book is refused.

The response state is open (resting), processing (IOC/FOK/market — read the order back for the result), queued (held until the matching engine answers), waiting (a stop order held until its trigger) or replayed (nothing new was placed).

Send a clientOrderId (1–40 of A–Z a–z 0–9 . _ : -) and a retry is always safe: the same id returns the existing order. Without one, the id is derived from the signature, so replaying the identical signed request places nothing new. A cancel is a request — the order closes when the engine confirms, usually within a second.

Errors and limits

Both APIs answer errors as { "error": { "code", "message" } } — for example invalid_signature, timestamp_outside_window, ip_not_allowed, trading_not_permitted, rate_limited (with Retry-After), rejected, engine_unavailable and, on the public API, not_found and market_data_unavailable.

Public: 1200 requests per minute per address. Private, per key: 120 reads and 100 order or cancel requests per 10 seconds; 300 orders per 10 seconds across all of an account's keys; 10 cancel-alls per 10 seconds.