Luzia Crypto API

Data & APIs

Use this skill whenever the user wants to fetch cryptocurrency prices, stream real-time market data, list exchanges or markets, or retrieve historical OHLCV candlestick data using the Luzia API (luzia.dev). Triggers include: any mention of "Luzia", "crypto price", "BTC/USDT", "exchange ticker", "OHLCV", "real-time price stream", or requests to connect to wss://api.luzia.dev. Also use when the user wants to build trading bots, portfolio trackers, or price alert tools on top of the Luzia platform. Do NOT use for non-Luzia crypto APIs (e.g. CoinGecko, Binance direct) unless the user explicitly wants Luzia as the data source.

Install

openclaw skills install luzia-crypto-api

Luzia API Integration Skill

Overview

Luzia (luzia.dev) provides real-time cryptocurrency pricing from multiple exchanges through a unified REST and WebSocket API.

  • Base REST URL: https://api.luzia.dev
  • WebSocket URL: wss://api.luzia.dev/v1/ws
  • Swagger UI: https://api.luzia.dev/docs
  • Docs: https://luzia.dev/docs

Core Principles

  1. Always authenticate — every endpoint requires Authorization: Bearer lz_<key>.
  2. Choose REST vs WebSocket correctly — REST for on-demand lookups; WebSocket for streaming updates (Pro plan required).
  3. Respect rate limits — Free: 100 req/min, 5 000 req/day. Pro: 1 000 req/min, unlimited/day.
  4. Use the right symbol format — REST paths use BTC-USDT (hyphen); channel subscriptions and response payloads use BTC/USDT (slash).
  5. Handle errors explicitly — inspect HTTP status codes for REST; handle error message type for WebSocket.

Authentication

All requests require an API key in the Authorization header:

Authorization: Bearer lz_your_api_key

API keys follow the format lz_ + 32 random characters. Get one at: https://luzia.dev/signup


Tier Summary

FeatureFreePro ($29.99/mo)Enterprise
REST requests/min1001 000Custom
REST requests/day5 000UnlimitedUnlimited
WebSocket access✅ (5 conns, 50 sub)✅ (25, 500 sub)
History lookback30 days90 daysUnlimited

REST API Reference

1. List Exchanges

GET /v1/exchanges

Returns all supported exchanges with their status.

Example response:

{
  "exchanges": [
    { "id": "binance", "name": "Binance", "status": "active" },
    { "id": "coinbase", "name": "Coinbase", "status": "active" }
  ]
}

2. List Markets for an Exchange

GET /v1/markets/:exchange

Returns all trading pairs available on the given exchange.

Path params: exchange — e.g. binance, coinbase, kraken

Example response:

{
  "exchange": "binance",
  "markets": [
    { "symbol": "BTC-USDT", "base": "BTC", "quote": "USDT" }
  ]
}

3. Get Ticker (single pair)

GET /v1/ticker/:exchange/:symbol

Fetches the latest price for one trading pair.

Path params:

  • exchange — e.g. binance
  • symbol — hyphen-separated, e.g. BTC-USDT

Query params:

  • maxAge (optional, ms) — max acceptable data age. Default: 120000 (2 min).

Example response:

{
  "symbol": "BTC/USDT",
  "exchange": "binance",
  "last": 67432.50,
  "bid": 67430.00,
  "ask": 67435.00,
  "high": 68500.00,
  "low": 66800.00,
  "open": 67000.00,
  "close": 67432.50,
  "volume": 12345.678,
  "quoteVolume": 832456789.50,
  "change": 432.50,
  "changePercent": 0.65,
  "timestamp": "2024-01-20T12:00:00.000Z"
}

4. Get All Tickers for an Exchange

GET /v1/tickers/:exchange

Fetches prices for all pairs on an exchange. Supports pagination.

Query params:

  • maxAge (optional, ms) — Default: 120000
  • limit (optional) — Default: 20, max: 50
  • offset (optional) — Default: 0

Example response:

{
  "tickers": [ { /* same shape as single ticker */ } ],
  "total": 150,
  "limit": 20,
  "offset": 0
}

5. Historical OHLCV Data

GET /v1/history/:exchange/:symbol

Returns candlestick data for a trading pair.

Path params: exchange, symbol (hyphen format, e.g. BTC-USDT)

Query params:

  • interval1m | 5m | 15m | 1h | 1d
  • limit — default 300, max 500
  • start — Unix ms timestamp (default: 24h ago)
  • end — Unix ms timestamp (default: now)

Example response:

{
  "exchange": "binance",
  "symbol": "BTC-USDT",
  "interval": "1h",
  "candles": [
    {
      "open": 67000.00,
      "high": 67450.00,
      "low": 66950.00,
      "close": 67432.50,
      "volume": 1234.56,
      "timestamp": "2024-01-20T12:00:00.000Z"
    }
  ],
  "count": 24,
  "start": "2024-01-20T00:00:00.000Z",
  "end": "2024-01-21T00:00:00.000Z"
}

REST: Code Patterns

TypeScript / fetch

const API_KEY = "lz_your_api_key";
const BASE = "https://api.luzia.dev";

async function getTicker(exchange: string, symbol: string) {
  const res = await fetch(`${BASE}/v1/ticker/${exchange}/${symbol}`, {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return res.json();
}

async function getHistory(exchange: string, symbol: string, interval = "1h", limit = 24) {
  const url = new URL(`${BASE}/v1/history/${exchange}/${symbol}`);
  url.searchParams.set("interval", interval);
  url.searchParams.set("limit", String(limit));
  const res = await fetch(url.toString(), {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });
  if (!res.ok) throw new Error(`HTTP ${res.status}: ${await res.text()}`);
  return res.json();
}

Python / httpx

import httpx

API_KEY = "lz_your_api_key"
BASE = "https://api.luzia.dev"
HEADERS = {"Authorization": f"Bearer {API_KEY}"}

def get_ticker(exchange: str, symbol: str) -> dict:
    r = httpx.get(f"{BASE}/v1/ticker/{exchange}/{symbol}", headers=HEADERS)
    r.raise_for_status()
    return r.json()

def get_history(exchange: str, symbol: str, interval: str = "1h", limit: int = 24) -> dict:
    r = httpx.get(
        f"{BASE}/v1/history/{exchange}/{symbol}",
        headers=HEADERS,
        params={"interval": interval, "limit": limit}
    )
    r.raise_for_status()
    return r.json()

WebSocket API Reference

Requires Pro plan or above.

Connection

wss://api.luzia.dev/v1/ws
Header: Authorization: Bearer lz_your_api_key

Channel Format

ChannelDescription
ticker:binance:BTC/USDTSingle pair from one exchange
ticker:coinbase:ETH/USDTSingle pair from another exchange
ticker:binanceAll tickers from Binance (1 sub slot)

⚠️ Symbol format in channels is slash (BTC/USDT), not hyphen.

Client → Server Messages

// Subscribe
{ "type": "subscribe", "channels": ["ticker:binance:BTC/USDT"] }

// Unsubscribe
{ "type": "unsubscribe", "channels": ["ticker:binance:BTC/USDT"] }

// Heartbeat (send every 30s)
{ "type": "ping" }

Server → Client Messages

// After connect
{ "type": "connected", "tier": "pro", "limits": { "maxSubscriptions": 50 } }

// Subscription confirmed
{ "type": "subscribed", "channel": "ticker:binance:BTC/USDT" }

// Heartbeat response
{ "type": "pong", "timestamp": "2024-01-23T10:13:20.000Z" }

// Price update
{
  "type": "ticker",
  "exchange": "binance",
  "symbol": "BTC/USDT",
  "data": { /* same fields as REST ticker response */ },
  "timestamp": "2024-01-23T10:13:20.050Z"
}

// Error
{ "type": "error", "code": "SUBSCRIPTION_LIMIT", "message": "..." }

WebSocket Error Codes

CodeMeaning
CONNECTION_REJECTEDWrong tier or connection limit exceeded
SUBSCRIPTION_LIMITMax subscriptions reached for your tier
INVALID_CHANNELBad channel format
INVALID_JSONMessage is not valid JSON
INVALID_REQUESTMissing required fields
UNKNOWN_TYPEUnrecognized message type
SERVER_SHUTDOWNServer shutting down — reconnect shortly

WebSocket: Code Patterns

Node.js / Native WebSocket (no SDK)

import WebSocket from "ws"; // npm install ws

const API_KEY = "lz_your_api_key";

function createLuziaStream(channels: string[]) {
  const ws = new WebSocket("wss://api.luzia.dev/v1/ws", {
    headers: { Authorization: `Bearer ${API_KEY}` },
  });

  // Heartbeat every 30s
  const heartbeat = setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
      ws.send(JSON.stringify({ type: "ping" }));
    }
  }, 30_000);

  ws.on("open", () => console.log("WebSocket open"));

  ws.on("message", (raw: string) => {
    const msg = JSON.parse(raw);
    switch (msg.type) {
      case "connected":
        console.log("Connected — tier:", msg.tier);
        ws.send(JSON.stringify({ type: "subscribe", channels }));
        break;
      case "subscribed":
        console.log("Subscribed to:", msg.channel);
        break;
      case "ticker":
        console.log(`[${msg.exchange}] ${msg.symbol}: $${msg.data.last}`);
        break;
      case "pong":
        // heartbeat acknowledged
        break;
      case "error":
        console.error(`WS error [${msg.code}]:`, msg.message);
        break;
    }
  });

  ws.on("close", (code, reason) => {
    clearInterval(heartbeat);
    console.log(`Disconnected: ${code} ${reason}`);
    // implement exponential backoff reconnect here
  });

  ws.on("error", (err) => console.error("WS error:", err));

  return ws;
}

// Usage
createLuziaStream(["ticker:binance:BTC/USDT", "ticker:coinbase:ETH/USDT"]);

Python / websockets

import asyncio, json, websockets

API_KEY = "lz_your_api_key"
WS_URL = "wss://api.luzia.dev/v1/ws"

async def stream_prices(channels: list[str]):
    headers = {"Authorization": f"Bearer {API_KEY}"}
    async with websockets.connect(WS_URL, additional_headers=headers) as ws:
        async def heartbeat():
            while True:
                await asyncio.sleep(30)
                await ws.send(json.dumps({"type": "ping"}))

        asyncio.create_task(heartbeat())

        async for raw in ws:
            msg = json.loads(raw)
            match msg["type"]:
                case "connected":
                    print(f"Connected — tier: {msg['tier']}")
                    await ws.send(json.dumps({"type": "subscribe", "channels": channels}))
                case "ticker":
                    d = msg["data"]
                    print(f"[{msg['exchange']}] {msg['symbol']}: ${d['last']}")
                case "error":
                    print(f"Error [{msg['code']}]: {msg['message']}")

asyncio.run(stream_prices(["ticker:binance:BTC/USDT"]))

Reconnection with Exponential Backoff (TypeScript)

async function connectWithRetry(channels: string[], maxAttempts = 10) {
  let attempt = 0;
  const delay = (ms: number) => new Promise(r => setTimeout(r, ms));

  while (attempt < maxAttempts) {
    try {
      await createLuziaStream(channels); // resolves on close
    } catch (err) {
      attempt++;
      const backoff = Math.min(1000 * 2 ** attempt + Math.random() * 500, 30_000);
      console.log(`Reconnecting in ${Math.round(backoff)}ms (attempt ${attempt})`);
      await delay(backoff);
    }
  }
  throw new Error("Max reconnect attempts reached");
}

SDK Quick Reference (Official)

Both SDKs wrap REST and WebSocket with TypeScript types and auto-reconnect:

TypeScript SDK

npm install @luziadev/sdk
import { Luzia } from "@luziadev/sdk";
const luzia = new Luzia({ apiKey: "lz_your_api_key" });

// REST
const ticker = await luzia.getTicker("binance", "BTC-USDT");

// WebSocket
const ws = luzia.createWebSocket({ autoReconnect: true });
ws.on("connected", () => ws.subscribe(["ticker:binance:BTC/USDT"]));
ws.on("ticker", (data) => console.log(data));
ws.connect();

Python SDK

Docs: https://luzia.dev/docs/python-sdk


Decision Tree: REST vs WebSocket

Need data?
│
├─ One-off / on-demand lookup?
│   └─ Use REST → GET /v1/ticker/:exchange/:symbol
│
├─ All pairs on an exchange?
│   └─ Use REST → GET /v1/tickers/:exchange  (paginate with limit/offset)
│
├─ Historical chart / backtesting?
│   └─ Use REST → GET /v1/history/:exchange/:symbol  (choose interval)
│
└─ Continuous stream / sub-second updates?
    ├─ Free tier? → Poll REST every N seconds (respect rate limits)
    └─ Pro tier?  → WebSocket → subscribe to specific channels

Common Mistakes to Avoid

MistakeFix
Using BTC/USDT in REST URL pathUse BTC-USDT (hyphen) in path params
Using BTC-USDT in WS channel nameUse BTC/USDT (slash) in channel strings
Sending WS messages before connectedWait for { type: "connected" } before subscribing
No heartbeat on native WSSend { type: "ping" } every 30 seconds
Subscribing to exchange-level channel unnecessarilyUse symbol-level channels to save sub slots
Forgetting maxAge for latency-sensitive appsSet maxAge=5000 (5s) for fresher REST data
Not handling SERVER_SHUTDOWN errorReconnect with backoff when you receive this code

Useful Links

ResourceURL
Docs homehttps://luzia.dev/docs
Swagger UIhttps://api.luzia.dev/docs
WebSocket docshttps://luzia.dev/docs/websocket
TypeScript SDKhttps://luzia.dev/docs/sdk
Python SDKhttps://luzia.dev/docs/python-sdk
MCP Serverhttps://luzia.dev/docs/mcp-server
Get API keyhttps://luzia.dev/signup
Manage API keyshttps://luzia.dev/keys