Troubleshooting Guide
Comprehensive solutions to common issues, error messages, and debugging techniques for Quub Exchange integrations.
Table of Contents
- Authentication Issues
- API Errors
- WebSocket Problems
- Order Execution Issues
- Rate Limiting
- Network & Connectivity
- Data Synchronization
Authentication Issues
Error: “Invalid API Key”
Symptoms:
{
"error": {
"code": "AUTH_INVALID_KEY",
"message": "Invalid API key"
}
}
Solutions:
- Verify API Key Format
// Correct format
const apiKey = "qk_live_abc123..."; // Starts with qk_live_ or qk_test_
// Check key is not truncated
console.log("API Key length:", process.env.QUUB_API_KEY.length);
// Should be 64+ characters
- Check Environment
# Verify environment variables are set
echo $QUUB_API_KEY
echo $QUUB_API_SECRET
echo $QUUB_ORG_ID
# Check .env file is loaded
node -e "require('dotenv').config(); console.log(process.env.QUUB_API_KEY)"
- Verify Key Status in Dashboard
- Log in to Quub Dashboard
- Navigate to Settings > API Keys
- Confirm key is active and not revoked
Error: “Authentication Token Expired”
Symptoms:
{
"error": {
"code": "AUTH_TOKEN_EXPIRED",
"message": "Authentication token has expired"
}
}
Solution: Implement Token Refresh
class TokenRefreshHandler {
constructor(quubClient) {
this.client = quubClient;
this.token = null;
this.expiresAt = null;
}
async getValidToken() {
// Check if token is expired or about to expire (5-minute buffer)
if (!this.token || Date.now() >= this.expiresAt - 300000) {
await this.refreshToken();
}
return this.token;
}
async refreshToken() {
try {
const response = await this.client.auth.authenticate({
apiKey: process.env.QUUB_API_KEY,
apiSecret: process.env.QUUB_API_SECRET,
orgId: process.env.QUUB_ORG_ID,
});
this.token = response.accessToken;
this.expiresAt = Date.now() + response.expiresIn * 1000;
console.log("✅ Token refreshed, expires at:", new Date(this.expiresAt));
} catch (error) {
console.error("❌ Token refresh failed:", error.message);
throw error;
}
}
}
// Usage with automatic retry
async function makeAuthenticatedRequest(requestFn) {
try {
const token = await tokenHandler.getValidToken();
return await requestFn(token);
} catch (error) {
if (error.code === "AUTH_TOKEN_EXPIRED") {
// Force refresh and retry once
await tokenHandler.refreshToken();
const token = await tokenHandler.getValidToken();
return await requestFn(token);
}
throw error;
}
}
Error: “Insufficient Permissions”
Symptoms:
{
"error": {
"code": "AUTH_INSUFFICIENT_PERMISSIONS",
"message": "API key does not have required scopes"
}
}
Solution: Check API Key Scopes
// Check current key permissions
async function verifyKeyScopes() {
const keyInfo = await quubClient.auth.getKeyInfo();
console.log("API Key Scopes:", keyInfo.scopes);
const requiredScopes = ["trading:read", "trading:write", "accounts:read"];
const missingScopes = requiredScopes.filter(
(scope) => !keyInfo.scopes.includes(scope)
);
if (missingScopes.length > 0) {
console.error("❌ Missing required scopes:", missingScopes);
console.log("📋 Create a new API key with these scopes in the dashboard");
}
}
API Errors
Error: “Invalid Request Parameters”
Symptoms:
{
"error": {
"code": "INVALID_PARAMETERS",
"message": "Invalid request parameters",
"details": {
"quantity": "must be a positive number"
}
}
}
Solution: Validate Parameters
import Joi from "joi";
// Define validation schema
const orderSchema = Joi.object({
symbol: Joi.string()
.pattern(/^[A-Z]+-[A-Z]+$/)
.required(),
side: Joi.string().valid("buy", "sell").required(),
type: Joi.string().valid("market", "limit", "stop").required(),
quantity: Joi.string()
.pattern(/^\d+\.?\d*$/)
.required(),
price: Joi.string()
.pattern(/^\d+\.?\d*$/)
.when("type", {
is: "limit",
then: Joi.required(),
}),
});
// Validate before sending
async function placeOrderSafely(params) {
// Validate
const { error, value } = orderSchema.validate(params);
if (error) {
console.error("❌ Validation failed:", error.details[0].message);
throw new Error(`Invalid parameters: ${error.details[0].message}`);
}
// Send request
try {
return await quubClient.exchange.createOrder(value);
} catch (apiError) {
console.error("❌ API Error:", apiError.message);
if (apiError.details) {
console.error("Details:", JSON.stringify(apiError.details, null, 2));
}
throw apiError;
}
}
Error: “Resource Not Found”
Symptoms:
{
"error": {
"code": "NOT_FOUND",
"message": "Order not found"
}
}
Debugging Steps:
async function debugOrderNotFound(orderId) {
console.log("🔍 Debugging order:", orderId);
// 1. Verify order ID format
if (!orderId || typeof orderId !== "string") {
console.error("❌ Invalid order ID format:", orderId);
return;
}
// 2. Search in recent orders
const recentOrders = await quubClient.exchange.getOrders({
limit: 100,
sortBy: "createdAt",
sortOrder: "desc",
});
const found = recentOrders.find((o) => o.orderId === orderId);
if (found) {
console.log("✅ Order found in recent orders:", found);
return found;
}
// 3. Check if order was cancelled
const cancelledOrders = await quubClient.exchange.getOrders({
status: "cancelled",
limit: 100,
});
const cancelled = cancelledOrders.find((o) => o.orderId === orderId);
if (cancelled) {
console.log("ℹ️ Order was cancelled:", cancelled);
return cancelled;
}
// 4. Check across all statuses
for (const status of ["open", "filled", "cancelled", "rejected"]) {
const orders = await quubClient.exchange.getOrders({ status, limit: 100 });
const match = orders.find((o) => o.orderId === orderId);
if (match) {
console.log(`✅ Order found with status '${status}':`, match);
return match;
}
}
console.error("❌ Order not found in any status");
}
WebSocket Problems
Issue: WebSocket Disconnects Frequently
Symptoms:
- Connection drops every few minutes
- Missing market data updates
- Error: “WebSocket connection closed”
Solution: Implement Reconnection Logic
class RobustWebSocketClient {
constructor(quubClient) {
this.client = quubClient;
this.ws = null;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = 10;
this.reconnectDelay = 1000;
this.subscriptions = [];
}
async connect() {
try {
this.ws = await this.client.exchange.connectWebSocket();
this.ws.on("open", () => {
console.log("✅ WebSocket connected");
this.reconnectAttempts = 0;
this.resubscribe();
});
this.ws.on("close", (code, reason) => {
console.log(`⚠️ WebSocket closed: ${code} - ${reason}`);
this.handleDisconnect();
});
this.ws.on("error", (error) => {
console.error("❌ WebSocket error:", error.message);
});
// Heartbeat to keep connection alive
this.startHeartbeat();
} catch (error) {
console.error("❌ Failed to connect:", error.message);
this.handleDisconnect();
}
}
async handleDisconnect() {
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
console.error("❌ Max reconnection attempts reached");
return;
}
this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);
console.log(
`🔄 Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})...`
);
await this.sleep(delay);
await this.connect();
}
subscribe(channel, symbols, callback) {
this.subscriptions.push({ channel, symbols, callback });
if (this.ws && this.ws.readyState === 1) {
this.ws.subscribe(channel, symbols, callback);
}
}
resubscribe() {
console.log("🔄 Resubscribing to channels...");
for (const sub of this.subscriptions) {
this.ws.subscribe(sub.channel, sub.symbols, sub.callback);
}
}
startHeartbeat() {
this.heartbeatInterval = setInterval(() => {
if (this.ws && this.ws.readyState === 1) {
this.ws.ping();
}
}, 30000); // Ping every 30 seconds
}
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
Issue: Missing Market Data Updates
Debugging:
async function debugMarketData(symbol) {
console.log("🔍 Debugging market data for", symbol);
// 1. Check REST API endpoint
try {
const market = await quubClient.exchange.getMarket(symbol);
console.log("✅ REST API working:", market.lastPrice);
} catch (error) {
console.error("❌ REST API failed:", error.message);
return;
}
// 2. Test WebSocket connection
const ws = await quubClient.exchange.connectWebSocket();
let messageReceived = false;
let messageCount = 0;
ws.subscribe("market.ticker", [symbol], (data) => {
messageReceived = true;
messageCount++;
console.log(`📊 Message ${messageCount}:`, {
symbol: data.symbol,
price: data.price,
timestamp: new Date(data.timestamp).toISOString(),
});
});
// Wait 10 seconds
await new Promise((resolve) => setTimeout(resolve, 10000));
if (!messageReceived) {
console.error("❌ No messages received in 10 seconds");
console.log("Possible issues:");
console.log(" - Symbol may be inactive");
console.log(" - WebSocket subscription failed");
console.log(" - Firewall blocking WebSocket traffic");
} else {
console.log(`✅ Received ${messageCount} messages`);
}
ws.close();
}
Order Execution Issues
Issue: Orders Stuck in “Pending” Status
Symptoms:
- Order created but never fills
- Status remains “pending” for extended time
Debugging:
async function debugPendingOrder(orderId) {
console.log("🔍 Debugging pending order:", orderId);
// 1. Get order details
const order = await quubClient.exchange.getOrder(orderId);
console.log("Order details:", {
status: order.status,
type: order.type,
price: order.price,
quantity: order.quantity,
filled: order.filledQuantity,
});
// 2. Check market conditions
const market = await quubClient.exchange.getMarket(order.symbol);
console.log("Market conditions:", {
lastPrice: market.lastPrice,
bestBid: market.bestBid,
bestAsk: market.bestAsk,
volume24h: market.volume24h,
});
// 3. Analyze why order isn't filling
if (order.type === "limit") {
const isPriceTooFar =
order.side === "buy"
? parseFloat(order.price) < parseFloat(market.bestAsk) * 0.95
: parseFloat(order.price) > parseFloat(market.bestBid) * 1.05;
if (isPriceTooFar) {
console.log("⚠️ Limit price is too far from market");
console.log(` Your ${order.side} price: ${order.price}`);
console.log(
` Market ${order.side === "buy" ? "ask" : "bid"}: ${
order.side === "buy" ? market.bestAsk : market.bestBid
}`
);
}
}
// 4. Check order book depth
const orderBook = await quubClient.exchange.getOrderBook(order.symbol);
const relevantSide = order.side === "buy" ? orderBook.asks : orderBook.bids;
let availableLiquidity = 0;
for (const level of relevantSide) {
availableLiquidity += parseFloat(level.quantity);
if (availableLiquidity >= parseFloat(order.quantity)) {
break;
}
}
if (availableLiquidity < parseFloat(order.quantity)) {
console.log("⚠️ Insufficient liquidity in order book");
console.log(` Required: ${order.quantity}`);
console.log(` Available: ${availableLiquidity}`);
}
// 5. Suggest action
console.log("\n💡 Suggestions:");
if (order.type === "limit") {
console.log(" - Consider adjusting limit price closer to market");
console.log(" - Or convert to market order for immediate execution");
}
console.log(" - Consider splitting into smaller orders");
console.log(" - Check if trading is halted for this market");
}
Error: “Insufficient Balance”
Solution:
async function diagnoseInsufficientBalance(orderParams) {
console.log("🔍 Diagnosing insufficient balance...");
// 1. Check current balance
const currency =
orderParams.side === "buy"
? orderParams.symbol.split("-")[1] // Quote currency
: orderParams.symbol.split("-")[0]; // Base currency
const balance = await quubClient.custodian.getBalance(currency);
console.log(`Balance for ${currency}:`, {
total: balance.total,
available: balance.available,
reserved: balance.reserved,
});
// 2. Calculate required amount
const required =
orderParams.side === "buy"
? parseFloat(orderParams.quantity) *
parseFloat(
orderParams.price || (await getMarketPrice(orderParams.symbol))
)
: parseFloat(orderParams.quantity);
console.log(`Required: ${required} ${currency}`);
console.log(`Available: ${balance.available} ${currency}`);
// 3. Check reserved balance
if (parseFloat(balance.reserved) > 0) {
console.log("\n⚠️ Some balance is reserved in open orders:");
const openOrders = await quubClient.exchange.getOrders({
status: "open",
symbol: orderParams.symbol,
});
for (const order of openOrders) {
console.log(
` Order ${order.orderId}: ${order.quantity} ${order.symbol} @ ${order.price}`
);
}
console.log("\n💡 Consider cancelling some orders to free up balance");
}
// 4. Suggest solution
const shortfall = required - parseFloat(balance.available);
if (shortfall > 0) {
console.log(`\n❌ Shortfall: ${shortfall} ${currency}`);
console.log("💡 Solutions:");
console.log(` - Deposit ${shortfall} ${currency}`);
console.log(
` - Reduce order quantity to ${
parseFloat(balance.available) / parseFloat(orderParams.price || 1)
}`
);
}
}
Rate Limiting
Error: “Rate Limit Exceeded”
Symptoms:
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Retry after 60 seconds",
"retryAfter": 60
}
}
Solution: Implement Rate Limit Handling
class RateLimitHandler {
async callWithRateLimit(fn, retries = 3) {
for (let attempt = 0; attempt <= retries; attempt++) {
try {
return await fn();
} catch (error) {
if (error.code === "RATE_LIMIT_EXCEEDED") {
const retryAfter = error.retryAfter || 60;
if (attempt < retries) {
console.log(
`⏳ Rate limited. Retrying after ${retryAfter}s (attempt ${
attempt + 1
}/${retries})`
);
await this.sleep(retryAfter * 1000);
continue;
}
}
throw error;
}
}
}
sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
}
// Usage
const rateLimitHandler = new RateLimitHandler();
const markets = await rateLimitHandler.callWithRateLimit(() =>
quubClient.exchange.getMarkets()
);
Network & Connectivity
Issue: Timeout Errors
Solution: Configure Timeouts
import axios from "axios";
const quubClient = new QuubClient({
apiKey: process.env.QUUB_API_KEY,
apiSecret: process.env.QUUB_API_SECRET,
timeout: 30000, // 30 seconds
// Retry configuration
retry: {
attempts: 3,
delay: 1000,
onRetry: (attempt, error) => {
console.log(`Retry attempt ${attempt}: ${error.message}`);
},
},
});
// Custom axios instance with detailed logging
const httpClient = axios.create({
timeout: 30000,
headers: {
"User-Agent": "MyApp/1.0",
},
});
httpClient.interceptors.request.use((config) => {
config.metadata = { startTime: Date.now() };
console.log(`→ ${config.method.toUpperCase()} ${config.url}`);
return config;
});
httpClient.interceptors.response.use(
(response) => {
const duration = Date.now() - response.config.metadata.startTime;
console.log(`← ${response.status} ${response.config.url} (${duration}ms)`);
return response;
},
(error) => {
if (error.code === "ECONNABORTED") {
console.error("❌ Request timeout");
} else if (error.code === "ENOTFOUND") {
console.error("❌ DNS resolution failed");
} else if (error.code === "ECONNREFUSED") {
console.error("❌ Connection refused");
}
throw error;
}
);
Data Synchronization
Issue: Balance Mismatch
Solution: Reconciliation Tool
class BalanceReconciliation {
async reconcile() {
console.log("🔍 Starting balance reconciliation...");
// 1. Get Quub balances
const quubBalances = await quubClient.custodian.getBalances();
// 2. Get internal balances
const internalBalances = await this.getInternalBalances();
// 3. Compare
const discrepancies = [];
for (const quubBal of quubBalances) {
const internalBal = internalBalances.find(
(b) => b.currency === quubBal.currency
);
if (!internalBal) {
discrepancies.push({
currency: quubBal.currency,
issue: "missing_internal",
quubBalance: quubBal.total,
});
continue;
}
const diff = Math.abs(
parseFloat(quubBal.total) - parseFloat(internalBal.total)
);
if (diff > 0.00000001) {
// Account for floating point precision
discrepancies.push({
currency: quubBal.currency,
issue: "mismatch",
quubBalance: quubBal.total,
internalBalance: internalBal.total,
difference: diff,
});
}
}
// 4. Report
if (discrepancies.length === 0) {
console.log("✅ All balances match");
} else {
console.error("❌ Discrepancies found:");
console.table(discrepancies);
// 5. Auto-fix if possible
await this.attemptAutoFix(discrepancies);
}
return discrepancies;
}
async attemptAutoFix(discrepancies) {
for (const disc of discrepancies) {
if (disc.issue === "mismatch") {
console.log(`🔧 Attempting to fix ${disc.currency}...`);
// Fetch transaction history
const transactions = await quubClient.custodian.getTransactions({
currency: disc.currency,
limit: 1000,
});
// Recalculate balance
const recalculated =
this.calculateBalanceFromTransactions(transactions);
// Update internal DB
await this.updateInternalBalance(disc.currency, recalculated);
console.log(`✅ Fixed ${disc.currency} balance`);
}
}
}
}
Getting Additional Help
Enable Debug Logging
// Enable verbose logging
const quubClient = new QuubClient({
apiKey: process.env.QUUB_API_KEY,
apiSecret: process.env.QUUB_API_SECRET,
debug: true,
logLevel: "verbose",
});
// Custom logger
quubClient.on("request", (config) => {
console.log("API Request:", {
method: config.method,
url: config.url,
params: config.params,
});
});
quubClient.on("response", (response) => {
console.log("API Response:", {
status: response.status,
data: response.data,
});
});
quubClient.on("error", (error) => {
console.error("API Error:", {
code: error.code,
message: error.message,
stack: error.stack,
});
});
Contact Support
When contacting support, include:
async function generateSupportReport() {
const report = {
timestamp: new Date().toISOString(),
environment: process.env.NODE_ENV,
sdkVersion: quubClient.version,
nodeVersion: process.version,
platform: process.platform,
// Recent errors
recentErrors: await this.getRecentErrors(),
// API key info (DO NOT include the actual key)
apiKeyInfo: {
keyIdPrefix: process.env.QUUB_API_KEY?.substring(0, 10),
orgId: process.env.QUUB_ORG_ID,
},
// System health
healthCheck: await this.performHealthCheck(),
};
console.log("Support Report:", JSON.stringify(report, null, 2));
return report;
}
Still Having Issues? Contact support@quub.fi with your support report