Skip to content

Getting Started

Quick start guide for integrating with the Synthetix Trading API.

Prerequisites

  • Ethereum wallet with private key
  • Node.js 18+ or Python 3.8+
  • Basic understanding of EIP-712 signatures
  • Funded account via the app (currently for select users only)

For API endpoints and connection details, see Environments.

Order Types

The Synthetix API uses a flat structure for order types:

Order TypeDescriptionRequired FieldsExample
marketMarket order-{orderType: "market"}
limitGtcLimit Good Till Canceledprice{orderType: "limitGtc", price: "50000"}
limitIocLimit Immediate or Cancelprice{orderType: "limitIoc", price: "50000"}
limitAloLimit Add Liquidity Only (Post-only)price{orderType: "limitAlo", price: "50000"}
triggerSlTrigger Stop LosstriggerPrice{orderType: "triggerSl", triggerPrice: "45000", isTriggerMarket: true}
triggerTpTrigger Take ProfittriggerPrice{orderType: "triggerTp", triggerPrice: "55000", isTriggerMarket: true}

Order Structure

{
  symbol: "BTC-USDT",
  side: "buy" | "sell",
  quantity: "0.01",
  orderType: "limitGtc",     // Flat enum value
  price: "50000",            // Required for limit orders, empty string for market
  triggerPrice: "",          // Required for trigger orders, empty string otherwise
  isTriggerMarket: false,    // true for market execution on trigger
  reduceOnly: false          // Position reduction only
}

Field Requirements by Order Type

Order TypepricetriggerPriceisTriggerMarketNotes
market"" (empty)"" (empty)falseExecutes immediately at market price
limitGtcRequired"" (empty)falseLimit order, good till canceled
limitIocRequired"" (empty)falseImmediate or cancel
limitAloRequired"" (empty)falsePost-only (maker only)
triggerSl"" or priceRequiredtrue/falseStop loss: true = market, false = limit
triggerTp"" or priceRequiredtrue/falseTake profit: true = market, false = limit

Quick Start Example

JavaScript/TypeScript

npm install ethers axios
const { ethers } = require('ethers');
const axios = require('axios');
 
const PRIVATE_KEY = process.env.PRIVATE_KEY;
 
class SynthetixClient {
  constructor(privateKey) {
    this.wallet = new ethers.Wallet(privateKey);
 
    // EIP-712 domain
    this.domain = {
      name: "Synthetix",
      version: "1",
      chainId: 1,
      verifyingContract: "0x0000000000000000000000000000000000000000"
    };
 
    // API base URL
    this.apiBaseUrl = 'https://papi.synthetix.io';
  }
 
  getNonce() {
    return Date.now();
  }
 
  // Get subaccount IDs for a wallet (public endpoint, no auth)
  async getSubAccountIds(walletAddress) {
    const response = await axios.post(`${this.apiBaseUrl}/v1/info`, {
      params: {
        action: 'getSubAccountIds',
        walletAddress: walletAddress
      }
    });
    return response.data.response;
  }
 
  // Place orders
  async placeOrders(subAccountId, orders) {
    const types = {
      PlaceOrders: [
        { name: "orders", type: "Order[]" },
        { name: "nonce", type: "uint256" },
        { name: "expiresAfter", type: "uint256" }
      ],
      Order: [
        { name: "symbol", type: "string" },
        { name: "side", type: "string" },
        { name: "quantity", type: "string" },
        { name: "orderType", type: "string" },
        { name: "price", type: "string" },
        { name: "triggerPrice", type: "string" },
        { name: "isTriggerMarket", type: "bool" },
        { name: "reduceOnly", type: "bool" }
      ]
    };
 
    const message = {
      orders: orders,
      nonce: this.getNonce(),
      expiresAfter: 0
    };
 
    const signature = await this.wallet.signTypedData(this.domain, types, message);
    const { v, r, s } = ethers.Signature.from(signature);
 
    const response = await axios.post(`${this.apiBaseUrl}/v1/trade`, {
      params: { action: "placeOrders", orders },
      subAccountId,
      nonce: message.nonce,
      signature: { v, r, s }
    });
 
    return response.data.response;
  }
}
 
// Usage
async function main() {
  const client = new SynthetixClient(PRIVATE_KEY);
 
  // Get subaccount IDs and use the master (first) subaccount
  const subAccountIds = await client.getSubAccountIds(client.wallet.address);
 
  if (subAccountIds.length === 0) {
    throw new Error('No subaccounts found. Please fund your account via the app first.');
  }
 
  const subAccountId = subAccountIds[0]; // Use master subaccount
  console.log(`Using subaccount: ${subAccountId}`);
 
  // Place a limit order
  const result = await client.placeOrders(subAccountId, [{
    symbol: "BTC-USDT",
    side: "buy",
    orderType: "limitGtc",
    quantity: "0.01",
    price: "50000.00",
    triggerPrice: "",
    isTriggerMarket: false,
    reduceOnly: false
  }]);
 
  console.log('Order placed:', result);
}
 
main().catch(console.error);

Python

pip install eth-account requests
import os
import time
from eth_account import Account
from eth_account.messages import encode_structured_data
import requests
 
class SynthetixClient:
    def __init__(self, private_key):
        self.account = Account.from_key(private_key)
 
        # API base URL
        self.api_base_url = "https://papi.synthetix.io"
 
        # EIP-712 domain - chainId matches the blockchain network
        self.domain = {
            "name": "Synthetix",
            "version": "1",
            "chainId": 1,
            "verifyingContract": "0x0000000000000000000000000000000000000000"
        }
 
    def get_nonce(self):
        return int(time.time() * 1000)
 
    def sign_typed_data(self, primary_type, types, message):
        typed_data = {
            "types": {
                "EIP712Domain": [
                    {"name": "name", "type": "string"},
                    {"name": "version", "type": "string"},
                    {"name": "chainId", "type": "uint256"},
                    {"name": "verifyingContract", "type": "address"}
                ],
                **types
            },
            "primaryType": primary_type,
            "domain": self.domain,
            "message": message
        }
 
        encoded = encode_structured_data(typed_data)
        signed = self.account.sign_message(encoded)
 
        return {"v": signed.v, "r": hex(signed.r), "s": hex(signed.s)}
 
    def get_subaccount_ids(self, wallet_address):
        response = requests.post(
            f"{self.api_base_url}/v1/info",
            json={"params": {"action": "getSubAccountIds", "walletAddress": wallet_address}}
        )
        return response.json()["response"]
 
    def place_orders(self, sub_account_id, orders):
        nonce = self.get_nonce()
        types = {
            "PlaceOrders": [
                {"name": "orders", "type": "Order[]"},
                {"name": "nonce", "type": "uint256"},
                {"name": "expiresAfter", "type": "uint256"}
            ],
            "Order": [
                {"name": "symbol", "type": "string"},
                {"name": "side", "type": "string"},
                {"name": "quantity", "type": "string"},
                {"name": "orderType", "type": "string"},
                {"name": "price", "type": "string"},
                {"name": "triggerPrice", "type": "string"},
                {"name": "isTriggerMarket", "type": "bool"},
                {"name": "reduceOnly", "type": "bool"}
            ]
        }
        message = {"orders": orders, "nonce": nonce, "expiresAfter": 0}
        signature = self.sign_typed_data("PlaceOrders", types, message)
 
        response = requests.post(
            f"{self.api_base_url}/v1/trade",
            json={"params": {"action": "placeOrders", "orders": orders}, "subAccountId": str(sub_account_id), "nonce": nonce, "signature": signature}
        )
        return response.json()["response"]
 
# Usage
client = SynthetixClient(os.getenv("PRIVATE_KEY"))
 
# Get subaccount IDs and use the master (first) subaccount
subaccount_ids = client.get_subaccount_ids(client.account.address)
 
if not subaccount_ids:
    raise Exception("No subaccounts found. Please fund your account via the app first.")
 
sub_account_id = subaccount_ids[0]  # Use master subaccount
print(f"Using subaccount: {sub_account_id}")
 
# Place order
result = client.place_orders(sub_account_id, [{
    "symbol": "BTC-USDT",
    "side": "buy",
    "orderType": "limitGtc",
    "quantity": "0.01",
    "price": "50000.00",
    "triggerPrice": "",
    "isTriggerMarket": False,
    "reduceOnly": False
}])
print("Order placed:", result)

Additional Order Examples

// Market order
{ orderType: "market", price: "", triggerPrice: "" }
 
// Limit IOC (Immediate or Cancel)
{ orderType: "limitIoc", price: "50000.00", triggerPrice: "" }
 
// Limit ALO (Add Liquidity Only / Post-only)
{ orderType: "limitAlo", price: "50000.00", triggerPrice: "" }
 
// Stop Loss (market execution on trigger)
{ orderType: "triggerSl", price: "", triggerPrice: "45000.00", isTriggerMarket: true }
 
// Take Profit (limit execution on trigger)
{ orderType: "triggerTp", price: "55000.00", triggerPrice: "54000.00", isTriggerMarket: false }

WebSocket Real-Time Updates

For real-time order updates and market data streaming, see the WebSocket API documentation.

Available subscriptions:

  • Order updates - Real-time fills, cancellations, rejections
  • Market prices - Live price feeds
  • Orderbook - Depth updates
  • Trades - Public trade execution events

Next Steps