REST API
REST API Reference
Complete endpoint reference for the Liquid Trading API — authentication, market data, account, orders, positions, and errors.
Overview
LinkThe Liquid Trading API provides authenticated access to market data, account state, order entry, and position management for perpetual futures markets.
| Property | Value |
|---|---|
| Base URL | https://api.liquid.trade |
| Version | /v1 |
| Auth | HMAC-SHA256 signed headers |
| Format | JSON request and response bodies |
| Rate limits | Per-key, per-second (see Rate Limits) |
Authentication
LinkAll endpoints except GET /v1/health require four HMAC authentication headers on every request.
| Header | Description |
|---|---|
| X-Liquid-Key | Your API key (lq_...) |
| X-Liquid-Timestamp | Current unix timestamp in milliseconds |
| X-Liquid-Nonce | Unique per-request nonce (8-64 alphanumeric characters) |
| X-Liquid-Signature | HMAC-SHA256 hex signature of the canonical payload |
The signing payload is a newline-delimited string:
{timestamp_ms}
{nonce}
{METHOD}
{canonical_path}
{canonical_query}
{body_hash}The signature is: hex(hmac_sha256(api_secret, payload))
Method is uppercased. Path is lowercased with trailing slash stripped. Query params are sorted by key and percent-encoded. JSON bodies are serialized with sorted keys and compact separators before SHA-256 hashing.
Timestamp skew must be within 5,000ms. Nonces are single-use with a 15-second replay window.
curl https://api.liquid.trade/v1/account \
-H "X-Liquid-Key: lq_..." \
-H "X-Liquid-Timestamp: 1700000000000" \
-H "X-Liquid-Nonce: abc123xyz789" \
-H "X-Liquid-Signature: <hex-hmac>"The Python SDK handles signing automatically. You do not need to manually assemble HMAC headers unless building a custom client.
Permission Scopes
LinkAPI keys are assigned permission scopes that control which endpoints they can access.
| Scope | Value | Access |
|---|---|---|
| READ | 1 | Market data, account info, order and position queries |
| TRADE | 2 | Place and cancel orders, close and modify positions |
A key with value 3 has both READ and TRADE access. Most integrations should use a read+trade key.
Response Envelope
LinkEvery response uses a consistent envelope:
{
"success": true,
"data": { ... }
}{
"success": false,
"data": null,
"error": {
"code": "INSUFFICIENT_SCOPE",
"message": "API key does not have trade permission"
}
}Rate Limits
Link| Tier | Requests/sec | Order mutations/sec | Max keys |
|---|---|---|---|
| Free | 10 | 2 | 3 |
Order mutation limits apply to POST /v1/orders, DELETE /v1/orders, and all position mutation routes. Rate limit state is returned in response headers:
| Header | Description |
|---|---|
| X-RateLimit-Limit | Maximum requests per second |
| X-RateLimit-Remaining | Requests remaining in current window |
| X-RateLimit-Reset | Unix timestamp when the window resets |
When throttled, the API returns HTTP 429 with Retry-After: 1.
GET /v1/health
LinkUnauthenticated health check. Use this to verify connectivity.
curl https://api.liquid.trade/v1/health{
"status": "ok",
"version": "1.0.0"
}GET /v1/markets
LinkList all tradeable symbols. Scope: READ.
curl https://api.liquid.trade/v1/markets \
-H "X-Liquid-Key: lq_..." \
-H "X-Liquid-Timestamp: ..." \
-H "X-Liquid-Nonce: ..." \
-H "X-Liquid-Signature: ..."{
"success": true,
"data": [
{
"symbol": "BTC-PERP",
"ticker": "BTC",
"exchange": "hyperliquid",
"max_leverage": 40,
"sz_decimals": 5
},
{
"symbol": "ETH-PERP",
"ticker": "ETH",
"exchange": "hyperliquid",
"max_leverage": 25,
"sz_decimals": 4
}
]
}| Field | Type | Description |
|---|---|---|
| symbol | string | Liquid symbol identifier (e.g. BTC-PERP) |
| ticker | string | Base asset ticker |
| exchange | string | Underlying exchange |
| max_leverage | integer | Maximum allowed leverage |
| sz_decimals | integer | Size decimal precision |
GET /v1/markets/{symbol}/ticker
LinkGet current mark price and 24h metadata for a symbol. Scope: READ.
curl https://api.liquid.trade/v1/markets/BTC-PERP/ticker \
-H "X-Liquid-Key: lq_..." \
-H "X-Liquid-Timestamp: ..." \
-H "X-Liquid-Nonce: ..." \
-H "X-Liquid-Signature: ..."{
"success": true,
"data": {
"symbol": "BTC-PERP",
"mark_price": "69420.50",
"volume_24h": "1234567.89",
"change_24h": "2.34",
"funding_rate": "0.0001"
}
}| Field | Type | Description |
|---|---|---|
| symbol | string | Symbol identifier |
| mark_price | string | Current mark price |
| volume_24h | string | 24-hour trading volume in USD |
| change_24h | string | 24-hour price change percentage |
| funding_rate | string | Current funding rate |
GET /v1/markets/{symbol}/orderbook
LinkGet an L2 order book snapshot. Scope: READ.
| Query Param | Type | Default | Description |
|---|---|---|---|
| depth | integer | 20 | Number of levels per side (1-500) |
{
"success": true,
"data": {
"symbol": "BTC-PERP",
"bids": [
{ "price": "69400.00", "size": "1.25", "count": 3 }
],
"asks": [
{ "price": "69420.00", "size": "0.80", "count": 2 }
],
"timestamp": null
}
}The orderbook timestamp is null in the current implementation. Use local request time if you need to track freshness.
GET /v1/markets/{symbol}/candles
LinkGet OHLCV candle data. Scope: READ.
| Query Param | Type | Default | Description |
|---|---|---|---|
| interval | string | 1h | 1m, 5m, 15m, 30m, 1h, 4h, 1d |
| limit | integer | 100 | Number of candles (1-1000) |
| start | integer | — | Start unix timestamp in seconds (optional) |
| end | integer | — | End unix timestamp in seconds (optional) |
{
"success": true,
"data": [
{
"timestamp": 1700000000000,
"open": "68000.0",
"high": "68200.0",
"low": "67850.0",
"close": "68120.0",
"volume": "1234.56"
}
]
}Account Endpoints
LinkAll account endpoints require SCOPE_READ.
| Method | Endpoint | Description |
|---|---|---|
| GET | /v1/account | Margin overview: equity, margin_used, available_balance, account_value |
| GET | /v1/account/balances | Detailed breakdown including cross_margin (account_value, total_margin_used, total_ntl_pos) |
| GET | /v1/account/positions | Open positions: symbol, side, size, entry_price, mark_price, leverage, unrealized_pnl, liquidation_price, margin_used |
{
"success": true,
"data": {
"equity": "10523.45",
"margin_used": "2100.00",
"available_balance": "8423.45",
"account_value": "10523.45"
}
}{
"success": true,
"data": [
{
"symbol": "BTC-PERP",
"side": "long",
"size": "0.05",
"entry_price": "69000.00",
"mark_price": "69420.50",
"leverage": 5,
"unrealized_pnl": "21.00",
"liquidation_price": "55200.00",
"margin_used": "690.00"
}
]
}POST /v1/orders
LinkPlace a market or limit order. Scope: TRADE.
| Field | Type | Required | Description |
|---|---|---|---|
| symbol | string | Yes | Market symbol (e.g. BTC-PERP) |
| side | string | Yes | buy or sell |
| type | string | No | market (default) or limit |
| size | number | Yes | Order size in USD notional (must be > 0) |
| price | number | Limit only | Limit price (required for limit orders) |
| leverage | integer | No | 1-200 (default 1) |
| time_in_force | string | No | gtc (default) or ioc |
| tp | number | No | Take-profit trigger price |
| sl | number | No | Stop-loss trigger price |
| reduce_only | boolean | No | Only reduce existing position |
{
"symbol": "BTC-PERP",
"side": "buy",
"type": "market",
"size": 100.0,
"leverage": 5,
"tp": 72000.0,
"sl": 68000.0
}{
"success": true,
"data": {
"order_id": "ord_abc123",
"symbol": "BTC-PERP",
"side": "buy",
"type": "market",
"size": "100.0",
"price": "69420.50",
"leverage": 5,
"status": "filled",
"exchange": "hyperliquid",
"tp": "72000.0",
"sl": "68000.0",
"reduce_only": false,
"created_at": "2025-01-15T12:00:00Z"
}
}The size field is always in USD, not token count. For example, size: 100 with BTC at $69,000 buys roughly 0.00145 BTC.
Other Order Endpoints
Link| Method | Endpoint | Scope | Description |
|---|---|---|---|
| GET | /v1/orders | READ | List all open orders |
| GET | /v1/orders/{order_id} | READ | Fetch a specific order (404 if not open) |
| DELETE | /v1/orders | TRADE | Cancel all open orders |
| DELETE | /v1/orders/{order_id} | TRADE | Cancel a single order (404 if not open) |
{
"success": false,
"data": [
{ "order_id": "ord_abc123", "status": "cancelled" },
{ "order_id": "ord_def456", "status": "failed", "error": "Order not found" }
],
"error": {
"code": "PARTIAL_FAILURE",
"message": "1 of 2 order cancellations failed; 1 succeeded."
}
}DELETE /v1/orders returns 207 PARTIAL_FAILURE if some cancellations succeed and others fail. Always check individual statuses in the data array.
Position Endpoints
LinkAll position endpoints require SCOPE_TRADE.
| Method | Endpoint | Description |
|---|---|---|
| POST | /v1/positions/{symbol}/close | Close full position or partial close |
| PATCH | /v1/positions/{symbol}/tp-sl | Set or update TP and/or SL |
| PATCH | /v1/positions/{symbol}/margin | Adjust isolated margin |
| PATCH | /v1/positions/{symbol}/leverage | Update leverage and margin mode |
// Omit size to close fully, or provide size in coin units for partial close
{
"size": 0.025
}{
"tp": 72000.0,
"sl": 68000.0
}{
"amount": 500.0
}{
"leverage": 10,
"is_cross": false
}Response examples:
{
"success": true,
"data": {
"symbol": "BTC-PERP",
"closed_size": 0.025,
"status": "ok",
"message": "Partial close executed"
}
}{
"success": true,
"data": {
"tp": { "status": "set", "price": 72000.0 },
"sl": { "status": "set", "price": 68000.0 }
}
}{
"success": true,
"data": {
"symbol": "BTC-PERP",
"leverage": 10,
"is_cross": false
}
}{
"success": true,
"data": {
"symbol": "BTC-PERP",
"margin_adjusted": 500.0
}
}When partially closing a position, the size field is in coin units (e.g. 0.025 BTC), not USD notional. Omit size to close the entire position.
Error Codes
Link| HTTP | Code | Description |
|---|---|---|
| 400 | BAD_REQUEST | Invalid request parameters |
| 401 | MISSING_AUTH_HEADERS | One or more required HMAC headers were omitted |
| 401 | INVALID_NONCE | Nonce format invalid (must be 8-64 alphanumeric characters) |
| 401 | INVALID_TIMESTAMP | Timestamp format invalid or not a valid integer |
| 401 | INVALID_API_KEY | API key does not exist or is disabled |
| 401 | INVALID_SIGNATURE | Signature mismatch — wrong secret or canonicalization error |
| 401 | EXPIRED_TIMESTAMP | Request timestamp outside the 5-second skew window |
| 401 | REPLAYED_NONCE | Nonce was already used within the replay window |
| 403 | INSUFFICIENT_SCOPE | API key does not have the required permission scope |
| 403 | IP_FORBIDDEN | Request originated from a non-whitelisted IP address |
| 404 | NOT_FOUND | Resource does not exist (e.g. unknown order ID) |
| 207 | PARTIAL_FAILURE | Some operations succeeded and some failed (e.g. cancel all orders) |
| 422 | VALIDATION_ERROR | Invalid request payload (missing fields, bad types) |
| 429 | RATE_LIMIT_EXCEEDED | Per-second request or mutation rate exceeded |
| 500 | INTERNAL_ERROR | Unexpected server error |
| 502 | EXCHANGE_ERROR | Upstream exchange (Hyperliquid) returned an error |
| 503 | AUTH_UNAVAILABLE | Auth infrastructure temporarily unavailable |
| 504 | TIMEOUT | Request timed out waiting for upstream response |
{
"success": false,
"data": null,
"error": {
"code": "EXPIRED_TIMESTAMP",
"message": "Request timestamp is outside the allowed 5000ms skew window"
}
}