Quickstart
This page walks through your first successful Sparky FAPI call end-to-end: wallet login → API key → signed request → order. All examples use the Avalanche deployment; swap the base URL for another chain.
1. Log in with your wallet and get a JWT
API keys are created through the native API, which needs a JWT from the EIP-712 login.
import time, requests
from eth_account import Account
from eth_account.messages import encode_typed_data
BASE = "https://api-avax.<sparky-domain>"
acct = Account.from_key("0xYourPrivateKey")
addr = acct.address.lower()
nonce_data = requests.get(f"{BASE}/api/v1/auth/nonce/{addr}").json()
typed = nonce_data["typed_data"] # domain + Login type + message
signed = acct.sign_message(encode_typed_data(full_message=typed))
sig = signed.signature.hex()
sig = sig if sig.startswith("0x") else "0x" + sig
jwt = requests.post(f"{BASE}/api/v1/auth/login", json={
"address": addr,
"signature": sig,
"timestamp": typed["message"]["timestamp"],
}).json()["token"]
2. Create an API key
r = requests.post(f"{BASE}/api/v1/api-keys",
headers={"Authorization": f"Bearer {jwt}"},
json={"label": "my-bot", "ip_whitelist": ""})
key = r.json()
API_KEY = key["api_key"] # 64 hex chars → X-MBX-APIKEY header
API_SECRET = key["secret_key"] # 64 hex chars → returned ONCE; store it now
You may hold up to 30 keys per account per chain. See API Keys for list / update / delete.
3. Sign a request
Every signed endpoint needs X-MBX-APIKEY, a timestamp (ms) query parameter and a signature query parameter. The rule is Binance's, with one detail to get right:
- GET / DELETE — sign the URL-encoded query string.
- POST / PUT — parameters go in a JSON body; sign
query_string + raw_body(the body bytes appended verbatim, not URL-encoded), then send the body unchanged.
import hashlib, hmac, json, urllib.parse
def _sign(payload: str) -> str:
return hmac.new(API_SECRET.encode(), payload.encode(), hashlib.sha256).hexdigest()
def signed(method: str, path: str, params: dict | None = None, body: dict | None = None):
params = dict(params or {})
params["timestamp"] = int(time.time() * 1000)
qs = urllib.parse.urlencode(params, safe="")
body_str = "" if body is None else json.dumps(body, separators=(",", ":"))
sig = _sign(qs + body_str) # POST/PUT: body appended to the payload
url = f"{BASE}{path}?{qs}&signature={sig}"
headers = {"X-MBX-APIKEY": API_KEY, "Content-Type": "application/json"}
r = requests.request(method, url, data=body_str or None, headers=headers)
return r.status_code, r.json()
print(signed("GET", "/fapi/v2/balance"))
If your clock is right (Sparky enforces |now − timestamp| ≤ 60 000 ms in both directions), the secret is right and the key is active, you get 200 with an array of balance rows.
4. Set leverage and place your first order
signed("POST", "/fapi/v1/leverage", body={"symbol": "BTCUSDT", "leverage": 10})
status, order = signed("POST", "/fapi/v1/order", body={
"symbol": "BTCUSDT",
"side": "BUY",
"type": "LIMIT",
"timeInForce": "GTC",
"quantity": "0.010",
"price": "60000",
"newClientOrderId": "my-order-1",
})
print(status, order) # {"orderId": "...", "status": "NEW", ...}
# query and cancel by client id (GET/DELETE: params in the query string)
signed("GET", "/fapi/v1/order", params={"symbol": "BTCUSDT", "origClientOrderId": "my-order-1"})
signed("DELETE", "/fapi/v1/order", params={"symbol": "BTCUSDT", "origClientOrderId": "my-order-1"})
5. Same thing with curl
API_KEY="a1b2c3d4..."; SECRET="f6e5d4c3..."; BASE="https://api-avax.<sparky-domain>"
# check server time first
curl -s "$BASE/fapi/v1/time"
# LIMIT BUY: body is appended to the signed payload
TS=$(($(date +%s%3N)))
BODY='{"symbol":"BTCUSDT","side":"BUY","type":"LIMIT","timeInForce":"GTC","quantity":"0.010","price":"60000","newClientOrderId":"my-order-1"}'
QS="timestamp=$TS"
SIG=$(echo -n "${QS}${BODY}" | openssl dgst -sha256 -hmac "$SECRET" | sed 's/^.*= //')
curl -s -X POST "$BASE/fapi/v1/order?$QS&signature=$SIG" \
-H "X-MBX-APIKEY: $API_KEY" -H "Content-Type: application/json" -d "$BODY"
6. Common errors
-1021 Timestamp outside recv window— clock drift. Sync withGET /fapi/v1/time; note Sparky's window is fixed at 60 s andrecvWindowcannot widen it.SIGNATURE_INVALID(HTTP 401,{"success":false,"error":{...}}envelope) — wrong secret, or you URL-encoded the body / forgot to append it for POST/PUT.INVALID_API_KEY/API_KEY_DISABLED(HTTP 401) — key does not exist on this chain's deployment, or was disabled.IP_NOT_ALLOWED(HTTP 403) — your IP is not in the key'sip_whitelist.-2019 Margin is insufficient— top up via the Vault deposit flow.-1013— quantity belowlot_size, or notional outside[min_order_size_usd, max_order_size_usd].
Full table at Error Codes.