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/opensslmay be LibreSSL. Check withopenssl version— if it does not sayOpenSSL 3, use the Node.js/Python generators or Homebrew’s OpenSSL.
Step 2 — Register the public key in the Terminal (2 minutes)
- Open the LEXX Terminal and go to the API keys section (Settings → API Keys).
- Paste the 43-character public key from Step 1 and give the key a label.
- 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.
- (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