Skip to content

Get Order History (WebSocket)

Retrieve order history for the authenticated subaccount through the WebSocket connection with comprehensive filtering capabilities, providing access to orders in any status (started, placed, partially filled, filled, cancelled, rejected) with flexible query options.

Request-Response vs Subscriptions

This method provides on-demand snapshots of order data with flexible filtering. For real-time updates when orders change, use SubAccount Updates Subscription.

MethodPurposeWhen to Use
getOrderHistoryComprehensive order queryingInitial load, order history, filtered searches, reconciliation
Order Updates SubscriptionReal-time notificationsLive UI updates, event handling

Endpoint

ws.send() wss://api.synthetix.io/v1/ws/trade

Request

Request Format

{
  "id": "getorders-1",
  "method": "post",
  "params": {
    "action": "getOrderHistory",
    "subAccountId": "1867542890123456789",
    "status": ["filled", "cancelled"],
    "fromTime": 1704067200000,
    "toTime": 1704672000000,
    "limit": 50,
    "expiresAfter": 1704067500,
    "signature": {
      "v": 28,
      "r": "0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef",
      "s": "0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890"
    }
  }
}

Parameters

Request Parameters

ParameterTypeRequiredDescription
idstringYesClient-generated unique request identifier
methodstringYesMust be "post"
paramsobjectYesContains all parameters for the request

Params Object

ParameterTypeRequiredDescription
actionstringYesMust be "getOrderHistory"
subAccountIdstringYesSubaccount identifier
statusstring[]NoFilter by order status: ["started", "placed", "partially filled", "filled", "cancelling", "cancelled", "rejected", "modifying", "modified", "unknown"]
startTimeintegerNoStart timestamp in milliseconds (inclusive, max 7-day range). Preferred over fromTime
endTimeintegerNoEnd timestamp in milliseconds (inclusive, max 7-day range). Preferred over toTime
fromTimeintegerNoStart timestamp in milliseconds (inclusive). Deprecated — use startTime instead
toTimeintegerNoEnd timestamp in milliseconds (inclusive). Deprecated — use endTime instead
limitintegerNoMaximum number of orders to return (default: 50, max: 1000)
clientOrderIdstringNoFilter by client-provided order ID. Must not contain leading or trailing whitespace
expiresAfterintegerNoOptional expiration timestamp in seconds
signatureobjectYesEIP-712 signature using SubAccountAction type
Important Notes:
  • This endpoint uses SubAccountAction EIP-712 type (no nonce required, only expiresAfter is optional)
  • Maximum time range between startTime and endTime is 7 days (604,800,000 milliseconds)
  • fromTime and toTime are deprecated aliases for startTime and endTime

EIP-712 Signature

const domain = {
  name: "Synthetix",
  version: "1",
  chainId: 1,
  verifyingContract: "0x0000000000000000000000000000000000000000"
};
 
const types = {
  SubAccountAction: [
    { name: "subAccountId", type: "uint256" },
    { name: "action", type: "string" },
    { name: "expiresAfter", type: "uint256" }
  ]
};
 
const message = {
  subAccountId: "1867542890123456789",
  action: "getOrderHistory",
  expiresAfter: 0
};
 
const signature = await signer._signTypedData(domain, types, message);

Filter Combinations

The endpoint supports powerful filter combinations:

  • All Orders: Omit status to get orders in any status
  • Placed Orders Only: "status": ["placed"]
  • Historical Orders: "status": ["filled", "cancelled"] with time range
  • Recent Activity: Use startTime without endTime for orders since a specific time
  • Client Order Lookup: Use clientOrderId to retrieve a specific order by its client-assigned ID

Response Format

Success Response

{
  "id": "getorders-1",
  "status": 200,
  "result": {
    "status": "success",
    "response": [
      {
        "order": {
          "venueId": "1958787130134106112",
          "clientId": "cli-1958787130134106112"
        },
        "orderId": "1958787130134106112",
        "symbol": "BTC-USDT",
        "side": "buy",
        "type": "LIMIT",
        "quantity": "10.0",
        "price": "45000.00",
        "status": "filled",
        "timeInForce": "GTC",
        "createdTime": 1755846234000,
        "updateTime": 1755846290000,
        "filledQuantity": "10.0",
        "filledPrice": "45000.00",
        "triggeredByLiquidation": false,
        "reduceOnly": false,
        "postOnly": false
      },
      {
        "order": {
          "venueId": "1958787130134106113",
          "clientId": "cli-1958787130134106113"
        },
        "orderId": "1958787130134106113",
        "symbol": "ETH-USDT",
        "side": "sell",
        "type": "LIMIT",
        "quantity": "5.0",
        "price": "2800.00",
        "status": "partially filled",
        "timeInForce": "GTC",
        "createdTime": 1755846235000,
        "updateTime": 1755846291000,
        "filledQuantity": "2.0",
        "filledPrice": "2800.00",
        "triggeredByLiquidation": false,
        "reduceOnly": false,
        "postOnly": true
      }
    ]
  }
}

Migration Note: Order Type Structure

Important: The API is transitioning from a nested order type structure to a flattened one for better EIP-712 compatibility:

Old StructureNew Structure
"type": "limit", "timeInForce": "gtc""orderType": "limitGtc"
"type": "limit", "timeInForce": "ioc""orderType": "limitIoc"
"type": "limit", "timeInForce": "alo""orderType": "limitAlo"
"type": "market""orderType": "market"
"type": "trigger", "triggerType": "stopLoss""orderType": "triggerSl"
"type": "trigger", "triggerType": "takeProfit""orderType": "triggerTp"

During the migration period, the API may return orders in either format. Clients should be prepared to handle both structures.

Order ID Migration Note: Use order.venueId as canonical order ID. orderId is deprecated compatibility output.

Error Response

{
  "id": "getorders-1",
  "status": 400,
  "result": null,
  "error": {
    "code": 400,
    "message": "Failed to get orders"
  }
}

Implementation Example

class OrderQuery {
  constructor(ws) {
    this.ws = ws;
    this.pendingRequests = new Map();
  }
 
  async getOrderHistory(options = {}) {
    const {
      status = null,
      fromTime = null,
      toTime = null,
      limit = 50,
      subAccountId
    } = options;
 
    // Validate 7-day time range if both times provided
    if (fromTime && toTime) {
      const maxRange = 7 * 24 * 60 * 60 * 1000; // 7 days in milliseconds
      if (toTime - fromTime > maxRange) {
        throw new Error('Time range cannot exceed 7 days');
      }
    }
 
    // Build request (signature required — SubAccountAction EIP-712 type)
    const request = {
      id: `getorders-${Date.now()}`,
      method: "post",
      params: {
        action: "getOrderHistory",
        subAccountId: subAccountId.toString()
      }
    };
 
    // Add optional filters
    if (status) request.params.status = status;
    if (fromTime) request.params.fromTime = fromTime;
    if (toTime) request.params.toTime = toTime;
    if (limit) request.params.limit = limit;
 
    // Send and wait for response
    return this.sendRequest(request);
  }
 
  sendRequest(request) {
    return new Promise((resolve, reject) => {
      this.pendingRequests.set(request.id, { resolve, reject });
      this.ws.send(JSON.stringify(request));
 
      // Handle timeout
      setTimeout(() => {
        if (this.pendingRequests.has(request.id)) {
          this.pendingRequests.delete(request.id);
          reject(new Error('Request timeout'));
        }
      }, 10000); // 10 second timeout
    });
  }
 
  handleResponse(response) {
    const pending = this.pendingRequests.get(response.id);
    if (pending) {
      this.pendingRequests.delete(response.id);
      if (response.error) {
        pending.reject(new Error(response.error.message));
      } else {
        pending.resolve(response.result.response);
      }
    }
  }
}
 
// Usage Examples
 
// Get All Placed Orders
async function getAllPlacedOrders(query, subAccountId) {
  try {
    const result = await query.getOrderHistory({
      subAccountId,
      status: ["placed"]
    });
    console.log(`Found ${result.length} placed orders`);
    return result;
  } catch (error) {
    console.error('Failed to get placed orders:', error);
    throw error;
  }
}
 
// Get Order History with Time Range
async function getOrderHistoryWithTimeRange(query, subAccountId, fromTime, toTime) {
  try {
    const result = await query.getOrderHistory({
      subAccountId,
      fromTime,
      toTime,
      limit: 100
    });
    console.log(`Found ${result.length} historical orders`);
    return result;
  } catch (error) {
    console.error('Failed to get order history:', error);
    throw error;
  }
}
 
// Filter by Status
async function getOrderHistoryByStatus(query, subAccountId, status) {
  try {
    const result = await query.getOrderHistory({
      subAccountId,
      status,
      limit: 50
    });
    console.log(`Found ${result.length} ${status} orders`);
    return result;
  } catch (error) {
    console.error(`Failed to get orders with status ${status}:`, error);
    throw error;
  }
}
 
// Recent Orders
async function getRecentOrders(query, subAccountId, hoursBack = 24) {
  const fromTime = Date.now() - (hoursBack * 60 * 60 * 1000);
 
  try {
    const result = await query.getOrderHistory({
      subAccountId,
      fromTime,
      limit: 25
    });
    console.log(`Found ${result.length} recent orders`);
    return result;
  } catch (error) {
    console.error('Failed to get recent orders:', error);
    throw error;
  }
}

Integration with Subscriptions

Combine request-response queries with subscriptions for complete order management:

class OrderManager {
  constructor(ws, query, subAccountId) {
    this.ws = ws;
    this.query = query;
    this.subAccountId = subAccountId;
    this.orders = new Map();
  }
 
  async initialize() {
    // 1. Get initial state (placed orders only)
    const orders = await this.query.getOrderHistory({
      subAccountId: this.subAccountId,
      status: ["placed"]
    });
 
    orders.forEach(order => {
      const key = order.order?.venueId || order.orderId;
      this.orders.set(key, order);
    });
 
    // 2. Subscribe to real-time updates
    this.ws.send(JSON.stringify({
      id: "subscribe-orders",
      method: "subscribe",
      params: {
        type: "orders",
        subAccountId: this.subAccountId
      }
    }));
  }
 
  handleOrderUpdate(update) {
    // Handle subscription events
    const { eventType } = update.data;
    const orderKey = update.data.order?.venueId || update.data.orderId;
 
    if (eventType === 'cancelled' || eventType === 'filled') {
      this.orders.delete(orderKey);
    } else {
      this.orders.set(orderKey, update.data);
    }
  }
 
  async reconcile() {
    // Periodic verification
    const orders = await this.query.getOrderHistory({
      subAccountId: this.subAccountId,
      status: ["placed"]
    });
 
    // Sync any discrepancies
    this.orders.clear();
    orders.forEach(order => {
      const key = order.order?.venueId || order.orderId;
      this.orders.set(key, order);
    });
  }
}

Code Examples

Get All Open Orders

{
  "id": "getorders-open",
  "method": "post",
  "params": {
    "action": "getOrderHistory",
    "status": ["placed"],
    "subAccountId": "1867542890123456789"
  }
}

Get Order History

{
  "id": "getorders-history",
  "method": "post",
  "params": {
    "action": "getOrderHistory",
    "startTime": 1704067200000,
    "endTime": 1704153600000,
    "limit": 100,
    "subAccountId": "1867542890123456789"
  }
}

Use Cases

Trading Interface

  • Active Orders Management: Use "status": ["placed"] to display orders that can be modified or cancelled
  • Order History: Use time ranges to show historical trading activity

Analytics and Reporting

  • Performance Analysis: Filter by time range and status to calculate trading metrics
  • Audit Trail: Retrieve complete order history for compliance and reporting
  • Risk Management: Monitor open orders across all symbols

WebSocket Integration

  • Legacy Compatibility: Replace getOpenOrders requests with appropriate filters
  • Real-time Updates: Combine with WebSocket subscriptions for live order status updates

Advanced Filtering

Status Combinations

// Get all non-open orders
{ status: ["filled", "cancelled", "rejected", "partially filled"] }
 
// Get only successful orders
{ status: ["filled", "partially filled"] }
 
// Get failed orders
{ status: ["cancelled", "rejected"] }
 
// Get orders in transition states
{ status: ["cancelling", "modifying"] }

Time-based Queries

// Orders from last 24 hours
{ startTime: Date.now() - 86400000 }
 
// Orders in specific date range
{ startTime: 1704067200000, endTime: 1704153600000 }
 
// Orders since specific time (no end)
{ startTime: 1704067200000 }

fromTime and toTime are deprecated aliases for startTime and endTime respectively and are still accepted.

Order Object Structure

FieldTypeDescription
orderobjectCanonical order identifier object
order.venueIdstringCanonical venue-generated order ID
order.clientIdstringOptional client-provided order ID
orderIdstringDeprecated legacy order identifier
symbolstringTrading pair symbol (e.g., "BTC-USDT")
sidestringOrder side: "buy" or "sell"
typestringOrder type: "LIMIT", "MARKET", "STOP_LOSS", "TAKE_PROFIT"
quantitystringOriginal order quantity
pricestringOrder price (empty for market orders)
statusstringOrder status (e.g., "placed", "filled", "cancelled")
timeInForcestringTime in force: "GTC", "IOC", or "FOK"
createdTimeintegerOrder creation timestamp (Unix milliseconds)
updateTimeintegerLast update timestamp (Unix milliseconds)
filledQuantitystringQuantity that has been filled
filledPricestringAverage fill price (VWAP for partial fills)
triggeredByLiquidationbooleanWhether order was triggered by liquidation
reduceOnlybooleanWhether order only reduces existing position
postOnlybooleanWhether order must be maker (no immediate match)

Multiple Fills Handling

When an order has multiple partial fills at different prices:

  • filledPrice: Volume-weighted average price (VWAP) of all fills
  • filledQuantity: Total quantity filled across all partial fills
  • Example: Order for 10 units fills 3 @ $100 and 5 @ $102:
    • filledPrice: "101.25" (VWAP: (3×100 + 5×102) ÷ 8 = $101.25)
    • filledQuantity: "8"

Implementation Notes

  • Cache Strategically: Cache order data but validate freshness
  • Monitor Changes: Use real-time updates for order state changes
  • Handle Timeouts: Implement reasonable timeout values
  • Error Recovery: Retry failed requests with backoff

Validation Rules

  • Subaccount ID must be valid and accessible
  • Time range must be valid (startTimeendTime)
  • Maximum time range between startTime and endTime is 7 days (604,800,000 milliseconds)
  • Limit must be between 1 and 1000 (default: 50)
  • Status values must be valid: ["started", "placed", "partially filled", "filled", "cancelling", "cancelled", "rejected", "modifying", "modified", "unknown"]
  • clientOrderId must not contain leading or trailing whitespace
Error CodeDescriptionRetryable
UNAUTHORIZEDEIP-712 signature validation failedNo
VALIDATION_ERRORRequest validation failedNo
MISSING_REQUIRED_FIELDRequired field is missingNo
INVALID_FORMATField format is invalidNo
INVALID_VALUEInvalid parameter valueNo
RATE_LIMIT_EXCEEDEDToo many requests in time windowYes
INSUFFICIENT_MARGINNot enough margin for tradeNo
ORDER_NOT_FOUNDOrder does not existNo
OPERATION_TIMEOUTOperation timed outYes

Next Steps