API documentation

Quickstart

The LEXX Trading API lets you do everything the LEXX Terminal does — place and cancel orders, run trading bots, read balances and positions — from your own code. In this guide you will create an API key and make your first authenticated request. It takes about 10 minutes.

You will need:

  • A LEXX account. Keys are created in the LEXX Terminal; availability depends on your plan — see billing (profile → settings → billing) or lexx-trade.com. The tier also affects your rate limits.
  • A terminal with OpenSSL 3+, Node.js 18+, or Python 3.9+ — any one of them is enough.

The base URL for everything below: https://api.lexx-trade.com/api/v2


Step 1 — Generate a key pair (1 minute)

LEXX does not use classic API secrets. Instead, you generate an Ed25519 key pair on your machine: the private key stays with you and is never sent anywhere; LEXX only ever learns your public key. Every request you make is signed with the private key, and the server verifies the signature with the public one.

# 1. Generate the private key (this file is your credential — guard it)
openssl genpkey -algorithm ed25519 -out lexx-api-key.pem
chmod 600 lexx-api-key.pem

# 2. Print the 43-character public key you will register with LEXX
openssl pkey -in lexx-api-key.pem -pubout -outform DER | tail -c 32 \
  | python3 -c "import sys,base64;print(base64.urlsafe_b64encode(sys.stdin.buffer.read()).decode().rstrip('='))"

The second command prints something like:

anHTiUK7owEqqkep6ZvJ0ijwDUfb-w-3DnVeIeT-FE4

That 43-character string is your public key — it doubles as your key ID. (No OpenSSL? See Authentication for pure Node.js and Python generators.)

⚠️ macOS note: the system /usr/bin/openssl may be LibreSSL. Check with openssl version — if it does not say OpenSSL 3, use the Node.js/Python generators or Homebrew’s OpenSSL.


Step 2 — Register the public key in the Terminal (2 minutes)

  1. Open the LEXX Terminal and go to the API keys section (Settings → API Keys).
  2. Paste the 43-character public key from Step 1 and give the key a label.
  3. Pick a permission tier:
    • read — can read balances, positions, orders. Start here.
    • trade — read + place/cancel orders and manage bots. You will need this for real trading.
  4. (Recommended) Restrict the key to your server’s IP (CIDR whitelist).

You can have up to 10 active keys per account. The private key never leaves your machine — if you lose it, just revoke the key and register a new one.


Step 3 — Ping the API (no signature needed)

The /server/* endpoints are public — use them to check connectivity and clock sync:

curl https://api.lexx-trade.com/api/v2/server/time
{
  "data": { "serverTime": 1743360060000, "timezone": "UTC" },
  "meta": { "requestId": "550e8400-e29b-41d4-a716-446655440000", "timestamp": 1743360060000, "version": "v2.0" }
}

Every successful response has this { data, meta } shape. If serverTime differs from your clock by more than a couple of seconds, fix your clock first (NTP) — signed requests are only valid within ±5 seconds of server time.


Step 4 — Your first signed request

Private endpoints require three headers — X-LEXX-KEY, X-LEXX-SIGN, X-LEXX-TIMESTAMP — where the signature covers timestamp + METHOD + path + body. The scripts below handle that for you; the full rules live on the Authentication page.

We will call GET /v2/exchange/supported — it lists the exchanges available on LEXX and works for every account, so it is the perfect smoke test.

Node.js

// lexx-quickstart.mjs — Node.js 18+, zero dependencies
// Run: LEXX_PUBLIC_KEY=<your 43-char key> node lexx-quickstart.mjs
import crypto from "node:crypto";
import fs from "node:fs";

const BASE = "https://api.lexx-trade.com/api";
const PUBLIC_KEY = process.env.LEXX_PUBLIC_KEY;
const PRIVATE_KEY = fs.readFileSync("lexx-api-key.pem", "utf8");

async function lexx(method, path, { query, body } = {}) {
  const qs = query ? "?" + new URLSearchParams(query) : "";
  const pathWithQuery = `/v2${path}${qs}`;            // path starts at /v2 and INCLUDES the query string
  const timestamp = Date.now().toString();
  const payload = body ? JSON.stringify(body) : "";   // compact JSON — sign exactly what you send
  const message = timestamp + method + pathWithQuery + payload;
  const sign = crypto.sign(null, Buffer.from(message), PRIVATE_KEY).toString("base64url");

  const res = await fetch(BASE + pathWithQuery, {
    method,
    headers: {
      "X-LEXX-KEY": PUBLIC_KEY,
      "X-LEXX-SIGN": sign,
      "X-LEXX-TIMESTAMP": timestamp,
      ...(payload && { "Content-Type": "application/json" }),
    },
    body: payload || undefined,
  });
  const json = await res.json();
  if (!res.ok) throw new Error(`${json.error.code}: ${json.error.message}`);
  return json.data;
}

console.log(await lexx("GET", "/exchange/supported"));

Python

# lexx_quickstart.py — Python 3.9+
# pip install cryptography requests
# Run: LEXX_PUBLIC_KEY=<your 43-char key> python3 lexx_quickstart.py
import base64, json, os, time
import requests
from urllib.parse import urlencode
from cryptography.hazmat.primitives.serialization import load_pem_private_key

BASE = "https://api.lexx-trade.com/api"
PUBLIC_KEY = os.environ["LEXX_PUBLIC_KEY"]
PRIVATE_KEY = load_pem_private_key(open("lexx-api-key.pem", "rb").read(), password=None)

def lexx(method, path, query=None, body=None):
    qs = "?" + urlencode(query) if query else ""
    path_with_query = f"/v2{path}{qs}"                # path starts at /v2 and INCLUDES the query string
    timestamp = str(int(time.time() * 1000))
    payload = json.dumps(body, separators=(",", ":")) if body is not None else ""
    message = f"{timestamp}{method}{path_with_query}{payload}"
    sign = base64.urlsafe_b64encode(PRIVATE_KEY.sign(message.encode())).decode().rstrip("=")

    resp = requests.request(
        method, BASE + path_with_query,
        headers={
            "X-LEXX-KEY": PUBLIC_KEY,
            "X-LEXX-SIGN": sign,
            "X-LEXX-TIMESTAMP": timestamp,
            **({"Content-Type": "application/json"} if payload else {}),
        },
        data=payload or None,                          # send the exact bytes you signed
    )
    data = resp.json()
    if not resp.ok:
        raise RuntimeError(f"{data['error']['code']}: {data['error']['message']}")
    return data["data"]

print(lexx("GET", "/exchange/supported"))

Bash + cURL

#!/usr/bin/env bash
# sign-and-call.sh — requires OpenSSL 3+ (check: openssl version)
set -euo pipefail

BASE="https://api.lexx-trade.com/api"
PUBLIC_KEY="$LEXX_PUBLIC_KEY"
METHOD="GET"
PATH_Q="/v2/exchange/supported"   # path starts at /v2, query string included if any
BODY=""                           # for POST: compact JSON (tip: build it with jq -c)

TS="$(( $(date +%s) * 1000 ))"
MSG="${TS}${METHOD}${PATH_Q}${BODY}"
SIGN="$(openssl pkeyutl -sign -inkey lexx-api-key.pem -rawin -in <(printf '%s' "$MSG") \
  | python3 -c 'import sys,base64;print(base64.urlsafe_b64encode(sys.stdin.buffer.read()).decode().rstrip("="))')"

curl -sS "${BASE}${PATH_Q}" \
  -H "X-LEXX-KEY: ${PUBLIC_KEY}" \
  -H "X-LEXX-SIGN: ${SIGN}" \
  -H "X-LEXX-TIMESTAMP: ${TS}" \
  ${BODY:+-H "Content-Type: application/json" --data "$BODY"} | python3 -m json.tool

A successful run prints the exchange list (illustrative):

[
  { "id": "binance-spot",    "name": "Binance Spot",    "type": "spot" },
  { "id": "binance-futures", "name": "Binance Futures", "type": "futures" },
  { "id": "bybit-futures",   "name": "Bybit Futures",   "type": "futures" }
]

🎉 That’s it — you are authenticated. The helper function you just ran (lexx(...)) is all you need for any other endpoint.


Step 5 — Read your balance

Same helper, different path — note that query parameters are part of the signature and the helper already handles that:

// Node.js
console.log(await lexx("GET", "/portfolio", { query: { exchange: "binance-spot" } }));
# Python
print(lexx("GET", "/portfolio", query={"exchange": "binance-spot"}))
{
  "total": { "usd": "12950.50", "btc": "0.19" },
  "balances": [
    { "asset": "USDT", "free": "10450.50", "wallet": "12950.50", "locked": "2500.00", "usd": "12950.50", "total": "12950.50" }
  ]
}

All monetary values are strings — parse them with a decimal library, never with float. Read why in Conventions.


If something went wrong

Response Most likely cause Fix
401 TIMESTAMP_EXPIRED Your clock is off by more than 5 s Sync NTP, or offset from GET /server/time
401 INVALID_SIGNATURE Signed message ≠ sent request Path must start at /v2 (not /api/v2) and include the query string; body must be compact JSON
401 UNAUTHORIZED Duplicate request signature Same timestamp+signature sent twice Generate a fresh timestamp and signature for every attempt, including retries

The full checklist for every 401/403 lives in Authentication → Troubleshooting.

What’s next

  • Authentication — key management, the exact signing rule, a verified test vector, production-ready helper classes.
  • Orders — place your first real order (start with a tiny amount).
  • Bots — create and start a trading bot via API.
  • WebSocket — real-time order/balance/position streams.
  • Rate limits — how many requests you can make.

Verified against API spec v2.0.0 · 2026-07-11

Cookie Preferences

Manage your cookie preferences below. Necessary cookies are always active as they are essential for the website to function properly.

Strictly Necessary

Essential for the website to function. These cookies cannot be disabled.

Analytics

Help us understand how visitors interact with our website by collecting and reporting information anonymously.

Marketing

Used to track visitors across websites to display relevant advertisements.

Preferences

Allow the website to remember choices you make, such as language or region.