Introduction
Open Trade provides Quotes and Limit Order book for SPOT Crypto. This document describes Open Trade WebSocket communication with Open Trade trading services.
Open Trade Taker WEBSOCKET API provides the ability to connect to the venue server, subscribe to market data, place and control orders, receive executions and updates. The API is based on WEBSOCKET specification.
WebSocket Server URL
Available Channels
- ASSET_LIST
- INSTRUMENT_LIST
- ORDER_BOOK_PUBLIC
- TRADE_PUBLIC
- ACTIVE_SUBSCRIPTIONS
- BALANCE
- TRADE_PRIVATE
Precision & Symbols
Price Precision
The precision level of all trading prices is based on significant figures. All pairs on Bitfinex use up to 7 significant digits and up to 8 decimals (e.g., 1.234567, 123.4567, 1234.567, 0.0001234567). Prices submitted with a precision larger than 7 will be cut by the API.
Amount Precision
The amount field allows up to 8 decimals. Anything exceeding this will be rounded to the 8th decimal.
Symbols
A symbol identifies a trading pair (e.g., BTC/USD).
Connection
Establishing Connection
Connect to the WebSocket endpoint using any standard WebSocket client. Upon connection, you'll receive a confirmation message.
const ws = new WebSocket('wss://wss.opentrade.exchange/ws');
ws.onopen = () => {
console.log('Connected to Open Trade');
// Send ping every 30 seconds to keep connection alive
setInterval(() => {
ws.send(JSON.stringify({
command: "PING",
channel: "PING"
}));
}, 30000);
};
ws.onmessage = (event) => {
const data = JSON.parse(event.data);
console.log('Received:', data);
};
ws.onerror = (error) => {
console.error('WebSocket error:', error);
};
ws.onclose = () => {
console.log('Connection closed');
};
Subscription
Subscription is required to establish a data channel the server will push updates to. If private channels are required, the valid API key must be provided in every Subscribe request.
{
"requestId": "123",
"command": "SUBSCRIBE",
"signature": "put your token here for private channels",
"channel": "CHANNEL_NAME",
"channelArgs": [
{
"name": "parameter_name",
"value": "parameter_value"
}
]
}
Example: Subscribe to Balance (Private Channel)
{
"command": "SUBSCRIBE",
"signature": "{{BEARER_TOKEN}}",
"channelArgs": [
{
"name": "currency",
"value": "[BTC, USDT]"
}
],
"channel": "BALANCE"
}
{
"command": "SUBSCRIBE",
"event": "SNAPSHOT",
"channel": "BALANCE",
"channelArgs": [
{
"name": "currency",
"value": "[BTC, USDT]"
}
],
"data": [
{
"class": "Balance",
"currencyCode": "USDT",
"balance": 313037.3739066097,
"available": 313037.3739066097,
"locked": 0.0
},
{
"class": "Balance",
"currencyCode": "BTC",
"balance": 3.28944117,
"available": 2.7886285699999998,
"locked": 0.5008126
}
]
}
Rate Limits
To ensure fair usage and system stability, rate limits are enforced on WebSocket connections. Exceeding these limits may result in temporary connection rejection.
Heartbeat / Ping
Ping is a service that users need to upkeep to notify that WebSocket connection is still open.
Maintains the WebSocket connection active. Must be sent at least once every 2 minutes.
{
"command": "PING",
"channel": "PING"
}
{
"event": "PONG",
"channel": "PING"
}
Public Channels
Public channels provide market data and instrument information. All API access — including public channels — requires authentication with your API key. Complete the authentication flow below before subscribing to any channel.
Provides a list of supported assets and their trading specifications.
{
"command": "GET",
"channel": "ASSET_LIST"
}
{
"event": "GET",
"channel": "ASSET_LIST",
"data": [
{
"class": "Currency",
"code": "USC",
"name": null
},
...
]
}
Provides a list of supported Instruments and their trading specifications. When no symbol is provided, all Instruments are returned, a specific Instrument is provided only selected is returned.
{
"command": "GET",
"channel": "INSTRUMENT_LIST"
}
{
"event": "GET",
"channel": "INSTRUMENT_LIST",
"data": [
{
"class": "Instrument",
"quoteCurrencyCode": "BAT",
"code": "BAT/ETH",
"name": null
},
...
]
}
Provides streaming services an order book for selected symbol, user needs to provide levels of order book to receive. MAX is 20. MIN is 1. Order books are sorted based on NBBO price: BIDs best (Max) first then descending, ASKs best (MIN) first then ascending. The whole Order book is updated every 20MS regardless of changes.
{
"command": "SUBSCRIBE",
"channel": "ORDER_BOOK_PUBLIC",
"channelArgs": [
{
"name": "instrument",
"value": "[USD/ADA,ETH/BTC]"
}
]
}
{
"command": "SUBSCRIBE",
"event": "ACK",
"channel": "ORDER_BOOK_PUBLIC"
}
{
"command": "SUBSCRIBE",
"event": "UPDATE",
"channel": "ORDER_BOOK_PUBLIC",
"data": [
{
"class": "OrderBook",
"exchange": "CROSSTOWER",
"symbol": "BTC/USD",
"bids": [
[19292.21, 0.0124],
[19242.45, 3.0516],
[10000.0, 0.0002]
],
"asks": [
[85100.0, 0.01009],
[19397.85, 21.6067],
[84300.0, 0.00854]
],
"eventTime": 1664651366635
},
...
]
}
Provides streaming services a trading book (public trades) for selected symbol. Once subscribed updates will be pushed to user as they appear at Open Trade.
{
"command": "SUBSCRIBE",
"channel": "TRADE_PUBLIC",
"channelArgs": [
{
"name": "instrument",
"value": "[USD/ADA,ETH/BTC]"
}
]
}
{
"command": "SUBSCRIBE",
"event": "ACK",
"channel": "TRADE_PUBLIC"
}
{
"command": "SUBSCRIBE",
"event": "UPDATE",
"channel": "TRADE_PUBLIC",
"data": [
{
"class": "PublicTrade",
"exchange": "OKCOIN",
"symbol": "BTC/USD",
"price": 19308.6,
"quantity": 0.0103,
"tradeSide": "BUY",
"eventTime": 1664651920827
},
...
]
}
Authentication
All API access requires authentication with an API key. Obtain a JWT token through a two-step process
and include it as the signature parameter in every request.
- POST your API key credentials to the login endpoint to obtain a
SESSIONcookie - POST to the JWT endpoint with the cookie and your client secret to obtain a JWT
- Include the JWT as the
signaturefield in every channel request
Authentication Flow
Step 1: Login with API Key
Send a POST request to the login endpoint with your API key as the username and your API secret as the password. Credentials must be sent as form-data with lowercase keys.
POST https://auth.opentrade.exchange/login
// Form Data (lowercase keys!)
username: {your_api_key_username}
password: {your_api_secret}
username and password MUST be lowercase. After successful
login, the response sets a SESSION cookie that you'll use in Step 2.
Step 2: Obtain JWT Token
Send a POST request to the token endpoint. Pass the SESSION cookie from Step 1 and your
client secret in the clientSecret request header. The body must be empty.
POST https://auth.opentrade.exchange/auth/jwt/clients/{client-api-username}/token
// Headers
Cookie: SESSION={session_cookie_from_step_1}
clientSecret: {your_api_secret}
// Body
(empty - send Content-Length: 0)
- Response
Content-Typeistext/plain— body is the raw token string prefixed withBearer - Token lifetime is returned in the
jwt-expire-atresponse header (currently 1 hour) - Token scope is returned in the
jwt-scoperesponse header (e.g.,api.access) - To refresh, repeat the full two-step flow before expiry
Complete Authentication Flow with cURL
curl -s --cookie-jar cookies.txt -X POST \
"https://auth.opentrade.exchange/login" \
-d "username=[API_USER_NAME]&password=[API_SECRET]" \
--next -s -b cookies.txt -X POST \
"https://auth.opentrade.exchange/auth/jwt/clients/[API_USER_NAME]/token" \
-H "accept: */*" \
-H "clientSecret: [API_SECRET]" \
-d ""
Python Example
import requests
BASE = "https://auth.opentrade.exchange"
USERNAME = "your_api_key_username"
SECRET = "your_api_secret"
session = requests.Session()
# Step 1: Login -> SESSION cookie
session.post(
f"{BASE}/login",
data={"username": USERNAME, "password": SECRET},
allow_redirects=False,
)
# Step 2: Exchange SESSION cookie for JWT
resp = session.post(
f"{BASE}/auth/jwt/clients/{USERNAME}/token",
headers={"clientSecret": SECRET},
)
jwt = resp.text # 'Bearer eyJ...'
print(jwt)
Bearer ). Use the full string —
including the Bearer prefix — as the signature parameter in all channel
requests.
HTTP 400on token request — missingclientSecretheader, missing cookie, or wrong HTTP method (must be POST, not GET)HTTP 404on token request — path uses/clients/(plural), not/client/- Login returns no
SESSIONcookie — verify form keys are lowercase and sent asapplication/x-www-form-urlencoded
Using Your Token
Include the JWT in every channel request as the signature field — this applies to both
public and private channels.
{
"command": "SUBSCRIBE",
"signature": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"channel": "BALANCE",
"channelArgs": [
{
"name": "currency",
"value": "[BTC,USD]"
}
]
}
- Store API keys and tokens securely — never expose them in client-side code
- Tokens expire after 1 hour — check the
jwt-expire-atresponse header from Step 2 - Implement refresh logic that re-runs the full two-step flow before expiry to maintain continuous connectivity
- Use environment variables or a secret manager to handle credentials across environments
- Pass the token verbatim, including the
Bearerprefix returned by the auth server