Market Maker API

Complete guide for automated market makers integrating with the sim-trading platform.

Overview

The Market Maker API lets automated trading systems place and dynamically reprice two-sided liquidity orders (bid + ask) as a single atomic pair. The core workflow is:

๐Ÿ” Authenticate
โ†’
๐Ÿ“Œ Place bid + ask
โ†’
โ™ป๏ธ Reprice loop
โ†’
๐Ÿ—‘๏ธ Cancel when done
โ„น๏ธ
All liquidity orders are linked in pairs. Cancelling or filling one side automatically cancels the other. This is by design โ€” the platform enforces two-sided markets.

Authentication

All API calls require a Bearer token in the Authorization header.

Authorization: Bearer <token>

Tokens do not expire by session but may be revoked. Re-authenticate if you receive 401.

Base URL

http://<host>:3001

All paths in this document are relative to the base URL.


Step 1 โ€” Authenticate

POST /api/auth/login

Request

{
  "username": "your_username",
  "password": "your_password"
}

Response

{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "user": { "id": "...", "username": "...", "role": "..." }
}

Step 2 โ€” Place a Liquidity Pair

Place two orders with the same pairId in meta. The pairId is a string you generate (e.g. a UUID). Use it to reprice or cancel the pair later.

POST /api/trading/orders ๐Ÿ”‘ Auth required

Request Body

FieldTypeRequiredDescription
symbolstringrequirede.g. "BTCUSDT"
sidestringrequired"buy" or "sell"
typestringrequiredMust be "liquidity"
quantitynumberrequiredOrder size in base asset
leveragenumberoptionalDefault: 1
liquidityConfig.levelOffsetnumberrequiredDepth level (1โ€“19). Use 1 for tightest spread.
liquidityConfig.ttlSecondsnumberoptionalOrder lifetime in seconds. Default: 300
priceModestringrequiredPricing mode โ€” see below
pricePercentnumberif percentagee.g. 0.5 = ยฑ0.5% from market
priceOffsetnumberif fixedOffsete.g. 500 = ยฑ500 USDT from market
metastring (JSON)requiredMust contain pairId

Pricing Modes

priceModeDescriptionRequired field
percentageMarket price ยฑ N%pricePercent
fixedOffsetMarket price ยฑ N USDTpriceOffset
levelOffsetOrderbook depth slot NliquidityConfig.levelOffset

Example โ€” place a ยฑ0.5% pair

# 1. Generate a pair ID
PAIR_ID="pair_$(date +%s)_$(head /dev/urandom | tr -dc a-z0-9 | head -c6)"

# 2. Place buy side
curl -s -X POST http://HOST:3001/api/trading/orders \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"symbol\": \"BTCUSDT\",
    \"side\": \"buy\",
    \"type\": \"liquidity\",
    \"quantity\": 0.01,
    \"leverage\": 1,
    \"liquidityConfig\": { \"levelOffset\": 1, \"ttlSeconds\": 300 },
    \"priceMode\": \"percentage\",
    \"pricePercent\": 0.5,
    \"meta\": \"{\\\"pairId\\\": \\\"$PAIR_ID\\\"}\"
  }"

# 3. Place sell side (same pairId)
curl -s -X POST http://HOST:3001/api/trading/orders \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d "{
    \"symbol\": \"BTCUSDT\",
    \"side\": \"sell\",
    \"type\": \"liquidity\",
    \"quantity\": 0.01,
    \"leverage\": 1,
    \"liquidityConfig\": { \"levelOffset\": 1, \"ttlSeconds\": 300 },
    \"priceMode\": \"percentage\",
    \"pricePercent\": 0.5,
    \"meta\": \"{\\\"pairId\\\": \\\"$PAIR_ID\\\"}\"
  }"
const BASE = 'http://HOST:3001';
const headers = {
  'Content-Type': 'application/json',
  'Authorization': `Bearer ${token}`,
};

const pairId = `pair_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;

async function placeLiquidityPair({ symbol, quantity, pricePercent, ttlSeconds = 300 }) {
  const base = { symbol, type: 'liquidity', quantity, leverage: 1,
    liquidityConfig: { levelOffset: 1, ttlSeconds },
    priceMode: 'percentage', pricePercent,
    meta: JSON.stringify({ pairId }),
  };

  const [buyRes, sellRes] = await Promise.all([
    fetch(`${BASE}/api/trading/orders`, { method: 'POST', headers, body: JSON.stringify({ ...base, side: 'buy' }) }),
    fetch(`${BASE}/api/trading/orders`, { method: 'POST', headers, body: JSON.stringify({ ...base, side: 'sell' }) }),
  ]);

  const [buy, sell] = await Promise.all([buyRes.json(), sellRes.json()]);
  return { pairId, buyOrderId: buy.order.id, sellOrderId: sell.order.id };
}

const pair = await placeLiquidityPair({
  symbol: 'BTCUSDT',
  quantity: 0.01,
  pricePercent: 0.5,
});

Step 3 โ€” Reprice

Call this to atomically cancel the existing pair and re-place at new absolute prices. Margin is released and re-frozen automatically.

POST /api/liquidity/reprice ๐Ÿ”‘ Auth required

Request Body

FieldTypeRequiredDescription
pairIdstringrequiredThe pair ID used when placing orders
newBidPricenumberrequiredNew absolute buy price (USDT). Must be < newAskPrice.
newAskPricenumberrequiredNew absolute sell price (USDT). Must be > newBidPrice.

Response

{
  "success": true,
  "buyOrderId": "new-buy-order-uuid",
  "sellOrderId": "new-sell-order-uuid",
  "newBidPrice": 68000,
  "newAskPrice": 72000
}
โš ๏ธ
After a successful reprice, the old order IDs are no longer valid. Save the new buyOrderId / sellOrderId from the response if you need to track individual orders. The pairId changes too โ€” use the new buyOrderId's meta to get the new pairId, or generate a new pairId yourself and pass it (coming in a future version).

Example

curl -s -X POST http://HOST:3001/api/liquidity/reprice \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "pairId": "pair_1742123456_abc123",
    "newBidPrice": 68000,
    "newAskPrice": 72000
  }'
async function reprice(pairId, newBidPrice, newAskPrice) {
  const res = await fetch(`${BASE}/api/liquidity/reprice`, {
    method: 'POST',
    headers,
    body: JSON.stringify({ pairId, newBidPrice, newAskPrice }),
  });
  if (!res.ok) {
    const err = await res.json();
    throw new Error(err.error);
  }
  return res.json();
}

// Reprice every 10 seconds based on mid price
setInterval(async () => {
  const mid = await getMidPrice('BTCUSDT');  // your price feed
  const spread = mid * 0.005;               // 0.5% spread
  await reprice(currentPairId, mid - spread, mid + spread);
}, 10_000);

Cancel a Pair

Cancel either order by ID. The other side is automatically cancelled too.

DELETE /api/trading/orders/:orderId ๐Ÿ”‘ Auth required
curl -s -X DELETE http://HOST:3001/api/trading/orders/$BUY_ORDER_ID \
  -H "Authorization: Bearer $TOKEN"

Pair Lifecycle

EventBuy sideSell side
Normal placementpendingpending
Buy side filledfilledcancelled (auto)
Sell side filledcancelled (auto)filled
TTL expiredcancelledcancelled
Manual cancelcancelledcancelled (auto)
Reprice calledcancelled โ†’ new ordercancelled โ†’ new order
๐Ÿ’ก
If a pair is filled before your reprice call arrives, the reprice will return a 400 error (pairId has no pending orders). Always handle this case in your bot logic โ€” it means a trade occurred and you should re-evaluate your inventory before placing a new pair.

Best Practices

Reprice frequency

pairId management

Margin management

Error handling


Error Reference

HTTPError messageCause
400pairId, newBidPrice, newAskPrice ๅฟ…ๅกซMissing required fields
400newBidPrice ๅฟ…้ ˆๅฐๆ–ผ newAskPriceInverted spread
400ๆ‰พไธๅˆฐ pairId=โ€ฆ ็š„ pending ๆตๅ‹•ๆ€ง่จ‚ๅ–ฎPair already filled, cancelled, or invalid pairId
400pairId=โ€ฆ ็š„่จ‚ๅ–ฎไธๅฎŒๆ•ดOnly one side found (should not happen in normal flow)
400ๅฏ็”จไฟ่ฏ้‡‘ไธ่ถณInsufficient margin for new orders after reprice
401Sessionๆ— ๆ•ˆๆˆ–ๅทฒ่ฟ‡ๆœŸToken expired or revoked โ€” re-authenticate
500LiquidityOrderManager ๆœชๅˆๅง‹ๅŒ–Server startup issue โ€” contact platform admin

Market Maker API v1.0  ยท  sim-trading-pro  ยท  Questions? Contact the platform operator.


External Symbol Price Feed

Push a reference price (last trade price) for a custom symbol. The price is broadcast to all connected clients and used for chart display and order matching. bid/ask spreads are formed by your liquidity orders, not this endpoint.

POST /api/broker/price-feed

Request Body

symbolstringSymbol code (e.g. XAUUSD)
pricenumberLast trade price (reference price)

Legacy format: { bid, ask } is also accepted โ€” mid price is used automatically.

# Single push
curl -X POST http://HOST:3001/api/broker/price-feed \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"symbol":"XAUUSD","price":3275.50}'

# Continuous push script (bash)
TOKEN="YOUR_TOKEN"
while true; do
  PRICE=3275.50  # replace with your data source
  curl -s -X POST http://HOST:3001/api/broker/price-feed \
    -H "Authorization: Bearer $TOKEN" \
    -H "Content-Type: application/json" \
    -d "{\"symbol\":\"XAUUSD\",\"price\":$PRICE}" > /dev/null
  sleep 1
done
import requests, time

BASE = "http://HOST:3001"
TOKEN = "YOUR_TOKEN"
HEADERS = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

def push_price(symbol: str, price: float):
    requests.post(f"{BASE}/api/broker/price-feed",
                  headers=HEADERS,
                  json={"symbol": symbol, "price": price})

# Example: push every tick from your data source
while True:
    price = get_price_from_your_source()  # replace with your API
    push_price("XAUUSD", price)
    time.sleep(0.1)  # push every 100ms - backend throttles WS broadcast to 1/sec
const BASE = "http://HOST:3001";
const TOKEN = "YOUR_TOKEN";

async function pushPrice(symbol, price) {
  await fetch(`${BASE}/api/broker/price-feed`, {
    method: "POST",
    headers: { Authorization: `Bearer ${TOKEN}`, "Content-Type": "application/json" },
    body: JSON.stringify({ symbol, price }),
  });
}

// Push every tick - platform throttles broadcast automatically
setInterval(async () => {
  const price = await getFromYourSource(); // replace with your feed
  await pushPrice("XAUUSD", price);
}, 100);
Throttling: You can push at any frequency. The platform automatically throttles WebSocket broadcast to 1 price update/sec per symbol and order matching checks to once every 5 sec. K-line candles are updated on every tick in memory.

Create Custom Symbol

Register a new tradeable symbol on the platform. After creation, the symbol appears in the market list immediately (page refresh required on client).

POST /api/broker/symbols

Request Body

symbolstringSymbol code, e.g. XAUUSD
displayNamestringDisplay name, e.g. ้ปƒ้‡‘/็พŽๅ…ƒ
pricePrecisionnumberDecimal places for price (default: 2)
qtyPrecisionnumberDecimal places for quantity (default: 4)
minOrderSizenumberMinimum order size (default: 0.01)
curl -X POST http://HOST:3001/api/broker/symbols \
  -H "Authorization: Bearer YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "symbol": "XAUUSD",
    "displayName": "้ปƒ้‡‘/็พŽๅ…ƒ",
    "pricePrecision": 2,
    "qtyPrecision": 2,
    "minOrderSize": 0.01
  }'

Broker API v1 โ€” Overview

REST APIs for white-label operators to manage client accounts and balances programmatically. All endpoints use X-API-Key header authentication.

Endpoint Summary

Method Endpoint Description Required Scope
Sub-Broker
POST/api/v1/broker/sub-brokersCreate a sub-broker (broker role)sub-brokers:create
GET/api/v1/broker/sub-brokersList all sub-brokerssub-brokers:list
POST/api/v1/broker/clients/{id}/upgrade-to-brokerUpgrade Trader to broker rolesub-brokers:upgrade
Client
GET/api/v1/broker/balanceGet your broker balancebalance:read
GET/api/v1/broker/clientsList all clients with balancesclients:list
POST/api/v1/broker/clientsCreate a Trader accountclients:create
GET/api/v1/broker/clients/{id}/balanceGet client balanceclients:balance
GET/api/v1/broker/clients/{id}/ordersGet client trade historyclients:orders
GET/api/v1/broker/clients/{id}/positionsGet client open positionsclients:positions
GET/api/v1/broker/clients/{id}/withdrawalsGet client withdrawal historyclients:withdrawals
Transfer
POST/api/v1/broker/clients/{id}/depositDeposit DCT to a clienttransfer:deposit
POST/api/v1/broker/clients/{id}/withdrawCreate withdrawal requesttransfer:withdraw
GET/api/v1/broker/transfer/historyList transfer recordstransfer:history
Withdrawal / OTC
GET/api/v1/broker/withdrawal/{id}Get single withdrawalwithdrawal:read
GET/api/v1/broker/withdrawals/pendingList pending withdrawalswithdrawal:list
POST/api/v1/broker/withdrawal/{id}/lockLock (OTC fulfill)withdrawal:lock
POST/api/v1/broker/withdrawal/{id}/unlockRelease lockwithdrawal:unlock
Use Case: Client registers on your platform → call POST /clients to create their trading account → call POST /deposit after they fund → they trade on your white-label site → call GET /clients/{id}/orders to display their trade history.

Authentication โ€” API Key

All requests must include your API Key:

X-API-Key: bk_xxxxxxxxxxxxxxxxxxxxxxxx

Generate keys in BrokerPanel → 🔑 API Key.

POST /api/v1/broker/sub-brokers

Create a sub-broker account under your broker. The new account has role=broker and can log in to Broker Portal, manage their own clients, and create further sub-brokers.

๐Ÿ’ก Multi-level hierarchy: Your Platform → White-label Broker → Sub-Broker → Traders.
Each level manages only their own downstream accounts.
POST /api/v1/broker/sub-brokers

Request Body

FieldTypeRequiredDescription
usernamestringโœ…Login username (4-32 alphanumeric/underscore)
passwordstringโœ…Login password for Broker Portal
display_namestringoptionalDisplay name shown in the platform
emailstringoptionalEmail address
initial_balancenumberoptionalDCT to transfer from your balance immediately

Request

POST /api/v1/broker/sub-brokers
X-API-Key: bk_your_key_here
Content-Type: application/json

{
  "username": "broker_xyz",
  "password": "SecurePass123",
  "display_name": "XYZ Trading",
  "initial_balance": 10000
}

Response (201 Created)

{
  "ok": true,
  "broker_id": "uuid...",
  "username": "broker_xyz",
  "display_name": "XYZ Trading",
  "role": "broker",
  "parent_broker_id": "your-broker-uuid",
  "created_at": "2026-05-20T00:00:00.000Z",
  "message": "Sub-broker account created. They can now log in to Broker Portal.",
  "broker_portal_url": "/pages/broker-portal.html"
}
After creation, the sub-broker logs in at /pages/broker-portal.html with their username and password.

GET /api/v1/broker/sub-brokers

List all sub-brokers created under your account, including their current balances.

GET /api/v1/broker/sub-brokers

Response

{
  "sub_brokers": [
    {
      "broker_id": "uuid...",
      "username": "broker_xyz",
      "display_name": "XYZ Trading",
      "status": "active",
      "balance": 10000.00,
      "available_balance": 10000.00,
      "created_at": "2026-05-20T00:00:00.000Z"
    }
  ],
  "total": 1
}

POST /api/v1/broker/clients/{user_id}/upgrade-to-broker

Upgrade an existing Trader account to broker role. The account retains its balance, trading history, and existing service relationship. The original service broker becomes the parentBrokerId.

โœ… Safe upgrade: Even if called by an admin, the parentBrokerId is taken from the original BrokerClientConfig.brokerId โ€” preserving the service relationship.
POST /api/v1/broker/clients/{user_id}/upgrade-to-broker

Request Body

FieldTypeRequiredDescription
passwordstringoptionalSet or update login password for Broker Portal

Request

POST /api/v1/broker/clients/usr_12345/upgrade-to-broker
X-API-Key: bk_your_key_here
Content-Type: application/json

{
  "password": "NewBrokerPass123"
}

Response

{
  "ok": true,
  "user_id": "usr_12345",
  "username": "sso_bb6eb7_usr_123",
  "role": "broker",
  "parent_broker_id": "original-service-broker-uuid",
  "brand_root_id": "brand-uuid",
  "message": "User upgraded to broker. They can now log in to Broker Portal.",
  "broker_portal_url": "/pages/broker-portal.html"
}
After upgrade, the user logs in at /pages/broker-portal.html to manage their own clients.

POST /api/v1/broker/clients

Create a Trader account under your broker. The account is immediately bound to your broker and can log in to the trading platform.

POST /api/v1/broker/clients

Request Body

FieldTypeRequiredDescription
user_idstringโœ…Your external user ID (e.g. your DB primary key)
passwordstringoptionalIf set, user can log in with username + password
display_namestringoptionalDisplay name shown in the platform
emailstringoptionalUser email address
initial_balancenumberoptionalDCT to deposit immediately from your broker balance

Request

POST /api/v1/broker/clients
X-API-Key: bk_your_key_here
Content-Type: application/json

{
  "user_id": "usr_12345",
  "display_name": "Alice Wang",
  "email": "[email protected]",
  "password": "SecurePass123",
  "initial_balance": 100
}

Response (201 Created)

{
  "ok": true,
  "user_id": "usr_12345",
  "username": "sso_bb6eb7_usr_123",
  "display_name": "Alice Wang",
  "broker_id": "bb6eb752-...",
  "created_at": "2026-05-20T00:00:00.000Z",
  "message": "Trader account created successfully"
}

Error: Duplicate

// 409 Conflict โ€” account with this user_id already exists
{"error": "Client already exists: user_id=usr_12345", "username": "sso_bb6eb7_usr_123"}
The platform username is auto-generated from your user_id (format: sso_{brokerShort}_{userId}). If a user later logs in via SSO with the same user_id, they will use the same account.

GET /api/v1/broker/clients

List all Trader accounts under your broker, including their current balances.

GET /api/v1/broker/clients

Query Parameters

ParamDefaultDescription
limit50Max results (max 200)
offset0Pagination offset
searchโ€”Filter by username / display name / email

Response

{
  "clients": [
    {
      "user_id": "usr_12345",
      "username": "sso_bb6eb7_usr_123",
      "display_name": "Alice Wang",
      "email": "[email protected]",
      "status": "active",
      "balance": 250.50,
      "available_balance": 200.00,
      "frozen_margin": 50.50,
      "created_at": "2026-05-20T00:00:00.000Z"
    }
  ],
  "total": 1,
  "limit": 50,
  "offset": 0
}

cURL Example

curl -X GET "https://walltrade.cc/api/v1/broker/clients?limit=20&search=alice" \
  -H "X-API-Key: bk_your_key"

POST /api/v1/broker/clients/{user_id}/deposit

Credit DCT balance to a client. Deducts from your broker balance.

POST /api/v1/broker/clients/{user_id}/deposit

Request

{"amount": 100, "note": "Offline payment"}

Response

{"ok": true, "message": "Deposited 100 DCT to user 42", "clientBalance": 110.5}

cURL Example

curl -X POST https://walltrade.cc/api/v1/broker/clients/42/deposit \
  -H "X-API-Key: bk_your_key" \
  -H "Content-Type: application/json" \
  -d '{"amount": 100, "note": "Offline payment"}'

POST /api/v1/broker/clients/{user_id}/withdraw

ไปฃๅฎขๆˆถๆไบคๅ‡บ้‡‘็”ณ่ซ‹ใ€‚ๅ‡็ตๅฎขๆˆถ DCT ้ค˜้ก๏ผŒๅปบ็ซ‹ๅ‡บ้‡‘่จ˜้Œ„๏ผˆstatus: pending๏ผ‰๏ผŒ ไธฆๅ›žๅ‚ณ withdrawalIdใ€‚ๅ†ท้œๆœŸๅ…งๅฏๆถๅ–ฎ๏ผ›็„กไบบๆถๅ‰‡ๅนณๅฐ่‡ชๅ‹•่™•็†ใ€‚

Request

POST /api/v1/broker/clients/{user_id}/withdraw
X-API-Key: bk_your_key_here
Content-Type: application/json

{"amount": 50, "note": "Client withdrawal"}

Response

{
  "ok": true,
  "withdrawalId": "abc-123-def",      // ๅ‡บ้‡‘็”ณ่ซ‹ๅ–ฎ่™Ÿ๏ผˆ็”จๆ–ผๆถๅ–ฎ๏ผ‰
  "status": "pending",
  "coolingUntil": "2026-05-11T10:05:00Z",  // ๆถๅ–ฎๆˆชๆญขๆ™‚้–“
  "coolingMinutes": 5,
  "amount": 50,
  "netAmount": 49.5,
  "clientBalance": 44.5
}
ๅŒๆ™‚ๆœƒ่งธ็™ผ Webhook ไบ‹ไปถ withdrawal_created๏ผˆ่ฆ‹ไธ‹ๆ–น Webhook ็ซ ็ฏ€๏ผ‰๏ผŒ ่ฎ“ไฝ ๅณๆ™‚ๆ”ถๅˆฐ withdrawalId ่€Œไธ้œ€่ผช่ฉขใ€‚

GET /api/v1/broker/withdrawal/{withdrawal_id}

ๆŸฅ่ฉขๅ–ฎ็ญ†ๅ‡บ้‡‘็”ณ่ซ‹็š„็‹€ๆ…‹ใ€‚

GET /api/v1/broker/withdrawal/{withdrawal_id}
X-API-Key: bk_your_key_here
{
  "withdrawalId": "abc-123",
  "amount": 50,
  "netAmount": 49.5,
  "status": "broker_completed",      // pending / broker_locked / broker_completed / processing / completed / cancelled
  "coolingUntil": "2026-05-11T10:05:00Z",
  "brokerFulfillerName": "rock5b",
  "brokerLockedAt": "2026-05-11T10:02:00Z",
  "completedAt": "2026-05-11T10:30:00Z",
  "createdAt": "2026-05-11T10:00:00Z"
}

GET /api/v1/broker/withdrawals/pending

ๆŸฅ่ฉขๆ——ไธ‹ๆ‰€ๆœ‰ๅฎขๆˆถ็š„ pending ๅ‡บ้‡‘็”ณ่ซ‹๏ผˆๅฏๆถๅ–ฎๅˆ—่กจ๏ผ‰ใ€‚

GET /api/v1/broker/withdrawals/pending
X-API-Key: bk_your_key_here
{
  "withdrawals": [
    {
      "withdrawalId": "abc-123",
      "externalUserId": "42",
      "amount": 50,
      "netAmount": 49.5,
      "status": "pending",
      "coolingUntil": "2026-05-11T10:05:00Z",
      "createdAt": "2026-05-11T10:00:00Z"
    }
  ]
}

GET /api/v1/broker/clients/{user_id}/withdrawals

ๆŸฅ่ฉขๆŒ‡ๅฎšๅฎขๆˆถ็š„ๅ‡บ้‡‘่จ˜้Œ„๏ผˆๆœ€่ฟ‘ 20 ็ญ†๏ผ‰ใ€‚

GET /api/v1/broker/clients/{user_id}/withdrawals
X-API-Key: bk_your_key_here
{
  "withdrawals": [
    {
      "withdrawalId": "abc-123",
      "amount": 50,
      "netAmount": 49.5,
      "status": "broker_completed",
      "brokerFulfillerName": "rock5b",
      "completedAt": "2026-05-11T10:30:00Z",
      "createdAt": "2026-05-11T10:00:00Z"
    }
  ]
}

GET /api/v1/broker/clients/{user_id}/balance

Query current balance of a client account.

GET /api/v1/broker/clients/{user_id}/balance
{"user_id": "42", "balance": 110.5, "availableBalance": 110.5, "frozenMargin": 0}

GET /api/v1/broker/clients/{user_id}/orders

Get trade history for a specific client. Returns all orders excluding hedge/system orders.

GET /api/v1/broker/clients/{user_id}/orders

Query Parameters

ParamDescription
limitMax results (default 50, max 200)
offsetPagination offset
statusFilter by status: filled, cancelled, pending, etc.
symbolFilter by symbol, e.g. BTCUSDT

Response

{
  "orders": [
    {
      "id": "order-uuid",
      "symbol": "BTCUSDT",
      "type": "market",
      "side": "buy",
      "quantity": 0.01,
      "averagePrice": 67500.00,
      "status": "filled",
      "filledQty": 0.01,
      "closedQty": 0.00,
      "leverage": 10,
      "isCloseOrder": false,
      "createdAt": "2026-05-20T08:00:00.000Z"
    }
  ],
  "total": 42,
  "limit": 50,
  "offset": 0
}

cURL Example

curl -X GET "https://walltrade.cc/api/v1/broker/clients/usr_12345/orders?status=filled&limit=20"   -H "X-API-Key: bk_your_key"

GET /api/v1/broker/clients/{user_id}/positions

Get open positions for a specific client. Only returns orders with unrealized holdings (filledQty > closedQty).

GET /api/v1/broker/clients/{user_id}/positions

Response

{
  "positions": [
    {
      "order_id": "order-uuid",
      "symbol": "BTCUSDT",
      "side": "buy",
      "open_qty": 0.01,
      "avg_price": 67500.00,
      "leverage": 10,
      "frozen_margin": 67.50,
      "opened_at": "2026-05-20T08:00:00.000Z"
    }
  ],
  "total": 1
}

cURL Example

curl -X GET "https://walltrade.cc/api/v1/broker/clients/usr_12345/positions"   -H "X-API-Key: bk_your_key"

GET /api/v1/broker/transfer/history

List transfer records. Optional query: ?limit=50&externalUserId=42

GET /api/v1/broker/transfer/history
{"records": [{"type": "broker_to_client", "amount": 100, "note": "Offline payment", "createdAt": "2026-05-10T08:00:00Z"}]}

Webhook Events

Set a Webhook URL when creating your API Key. Platform will POST for the following events.

Signature Verification

Every webhook request includes signature headers so you can verify authenticity:

X-Webhook-Signature: sha256=<HMAC-SHA256 hex digest>
X-Webhook-Timestamp: 2026-05-20T08:00:00.000Z

Node.js verification example:

const crypto = require('crypto');

function verifyWebhook(rawBody, signature, secret) {
  const expected = 'sha256=' + crypto
    .createHmac('sha256', secret)
    .update(rawBody)      // raw string body
    .digest('hex');
  return crypto.timingSafeEqual(
    Buffer.from(signature),
    Buffer.from(expected)
  );
}

// In your Express webhook handler:
app.post('/webhook', express.raw({ type: 'application/json' }), (req, res) => {
  const sig = req.headers['x-webhook-signature'];
  if (!verifyWebhook(req.body.toString(), sig, WEBHOOK_SECRET)) {
    return res.status(401).send('Invalid signature');
  }
  const event = JSON.parse(req.body);
  console.log('Event:', event.event);
  res.sendStatus(200);
});
โš ๏ธ Webhook Secret is shown only once when you create an API Key with a Webhook URL. Save it securely โ€” it cannot be retrieved later. If lost, delete and re-create the API Key.

Platform will POST for the following events:

Internal Transfer Events

// Deposit credited
{
  "event": "client_deposit",
  "user_id": "42",
  "amount": 100,
  "note": "Offline payment",
  "timestamp": "2026-05-10T08:00:00.000Z"
}

// Withdrawal created (pending โ€” broker can lock to fulfill)
{
  "event": "withdrawal_created",
  "user_id": "42",
  "withdrawal_id": "abc-123",
  "amount": 50,
  "net_amount": 49.5,
  "status": "pending",
  "cooling_until": "2026-05-11T10:05:00Z",
  "timestamp": "2026-05-11T10:00:00.000Z"
}

OTC Fulfillment Events

// Settlement completed
{
  "event": "withdrawal_fulfill_completed",
  "withdrawal_id": "abc-123",
  "tx_hash": "0x...",
  "dct_amount": 100,
  "timestamp": "2026-05-11T08:00:00.000Z"
}

// Lock expired (auto-released back to pending)
{
  "event": "withdrawal_lock_expired",
  "withdrawal_id": "abc-123",
  "reason": "ๆถๅ–ฎ่ถ…ๆ™‚๏ผˆ24ๅฐๆ™‚ๆœชๅตๆธฌๅˆฐๆ‰“ๆฌพ๏ผ‰",
  "timestamp": "2026-05-11T08:00:00.000Z"
}
Your endpoint must respond HTTP 200 within 5 seconds.

API Key Management & Scopes

Manage keys via Broker Portal or these endpoints (Bearer token required):

MethodEndpointAction
POST/api/broker/api-keysCreate (name, webhookUrl, scopes[])
GET/api/broker/api-keysList (preview only, includes scopes)
PUT/api/broker/api-keys/:idUpdate (enabled, webhookUrl, name)
DELETE/api/broker/api-keys/:idRevoke

Permission Scopes

When creating a key, pass scopes to limit what it can do. Omit scopes (or pass empty) for full access.

POST /api/broker/api-keys
Authorization: Bearer {session_token}

{
  "name": "Read-Only Key",
  "webhookUrl": "https://yoursite.com/webhook",
  "scopes": ["clients:list", "clients:balance", "sub-brokers:create"]
}

If a key has insufficient scope, the API returns:

// 403 Forbidden
{
  "error": "Insufficient scope. Required: transfer:deposit",
  "required_scope": "transfer:deposit",
  "your_scopes": ["clients:list", "clients:balance"]
}
ScopeEndpointDescription
balance:readGET /balanceQuery your broker balance
clients:listGET /clientsList all clients
clients:createPOST /clientsCreate Trader account
clients:balanceGET /clients/:id/balanceQuery client balance
clients:ordersGET /clients/:id/ordersQuery client trade history
clients:positionsGET /clients/:id/positionsQuery client positions
clients:withdrawalsGET /clients/:id/withdrawalsQuery client withdrawal history
transfer:depositPOST /clients/:id/depositDeposit to client
transfer:withdrawPOST /clients/:id/withdrawCreate withdrawal request
transfer:historyGET /transfer/historyList transfer records
withdrawal:readGET /withdrawal/:idGet single withdrawal
withdrawal:listGET /withdrawals/pendingList pending withdrawals
withdrawal:lockPOST /withdrawal/:id/lockLock for OTC fulfillment
withdrawal:unlockPOST /withdrawal/:id/unlockRelease lock
Sub-Broker Management
sub-brokers:createPOST /sub-brokersCreate a sub-broker account
sub-brokers:listGET /sub-brokersList all sub-brokers
sub-brokers:upgradePOST /clients/:id/upgrade-to-brokerUpgrade Trader to broker role

Error Codes

CodeMeaning
401Missing or invalid API key
403Insufficient scope (includes required_scope in response)
404Client not found
400Insufficient balance / invalid amount / invalid scope

OTC Withdrawal Fulfillment

Allows brokers to fulfill a user's pending withdrawal off-platform (P2P OTC). The broker pays the user USDC directly from their own wallet; the platform monitors the chain and releases the equivalent DCT to the broker's account balance.

โš ๏ธ Prerequisites: The broker account must have a walletAddress bound (used as the from address for on-chain verification).

Withdrawal States

StatusDescriptionUser can cancel?
pendingCooling period โ€” broker can lockโœ… Yes
broker_lockedLocked by broker โ€” chain monitoring activeโŒ No
broker_completedChain confirmed, DCT settled to brokerโ€”
processingCooling period ended โ€” platform handles itโŒ No
completedPlatform payout doneโ€”

Flow

1. User submits withdrawal โ†’ status: pending (cooling period, e.g. 5 min)
2. Broker calls LOCK during cooling period โ†’ status: broker_locked
3. Broker sends USDC from their wallet to the user's wallet address
4. Platform polls chain every 30 min (up to 24h)
5. Match found โ†’ DCT credited to broker's platform balance โ†’ broker_completed
6. No match within timeout โ†’ auto-release back to pending

POST /api/v1/broker/withdrawal/{withdrawal_id}/lock

Lock a pending withdrawal. First-come, first-served โ€” atomic operation.

Request

POST /api/v1/broker/withdrawal/{withdrawal_id}/lock
X-API-Key: bk_your_key_here

Response

{
  "ok": true,
  "status": "broker_locked",
  "userWalletAddress": "0xUserWallet...",   // Send USDC here
  "amount": 99,                              // USDC amount to send (netAmount after fee)
  "timeoutHours": 24
}
After locking, send USDC from your broker's bound wallet to userWalletAddress. The platform will detect the transfer automatically.

Chain Verification Rules

Error Responses

{ "error": "ๆญค็”ณ่ซ‹ๅทฒ่ขซๅ…ถไป–ไบคๆ˜“ๅ•†ๆถๅ–ฎ..." }  // 409 โ€” already locked
{ "error": "ๅ†ท้œๆœŸๅทฒ็ตๆŸ..." }              // 400 โ€” cooling period ended, platform took over
{ "error": "ไบคๆ˜“ๅ•†ๅธณ่™Ÿๅฐšๆœช็ถๅฎš้ŒขๅŒ…ๅœฐๅ€..." } // 400 โ€” broker wallet not bound

POST /api/v1/broker/withdrawal/{withdrawal_id}/unlock

Voluntarily release a locked withdrawal back to pending. Only the broker who locked it can unlock.

POST /api/v1/broker/withdrawal/{withdrawal_id}/unlock
X-API-Key: bk_your_key_here
{ "ok": true, "message": "ๅทฒๆ”พๆฃ„๏ผŒๅ‡บ้‡‘็”ณ่ซ‹้€€ๅ›žๅพ…่™•็†" }

SSO Login

If you manage your own users and want to give them access to the trading platform, use SSO (Single Sign-On). Your backend signs a JWT; the user is redirected to our platform which validates it and creates/logs-in the account automatically.

👤 Your user
🔑 Your backend signs JWT
🔗 Redirect to SSO URL
✓ Auto-login

SSO Endpoint

GET https://walltrade.cc/api/auth/sso?token=<JWT>

JWT Payload

{
  "user_id": "42",          // Your user's unique ID (string or integer)
  "tenant_code": "rock-3",  // Your broker code (assigned by us)
  "email": "[email protected]",
  "wallet_address": "0xUserWallet...",       // Optional
  "logout_redirect_url": "https://your-app.com/logout"  // Optional
}

Signing Parameters

ParameterValue
AlgorithmHS256
SecretShared secret provided by us
ExpiryRecommend exp: now + 300 (5 minutes)

Example (Node.js)

const jwt = require('jsonwebtoken');

const token = jwt.sign({
  user_id: "42",
  tenant_code: "rock-3",
  email: "[email protected]",
  wallet_address: "0xabc...",
  logout_redirect_url: "https://your-app.com/logout",
  exp: Math.floor(Date.now() / 1000) + 300
}, YOUR_SHARED_SECRET);

// Redirect the user's browser to:
const url = `https://walltrade.cc/api/auth/sso?token=${token}`;
res.redirect(url);
⚠️
The SSO endpoint performs a browser redirect. Do NOT call it via fetch() โ€” you must do a full browser redirect (window.location.href = url or a server-side 302 redirect).

After Login โ€” Get Session Token

Once the user lands on the chart page, the session token is available in localStorage under key auth_token. Use it for all subsequent API calls.

const token = localStorage.getItem('auth_token');

fetch('/api/auth/me', {
  headers: { 'Authorization': `Bearer ${token}` }
}).then(r => r.json()).then(console.log);

Demo Account & Virtual Funds

Demo accounts use virtual (simulated) funds โ€” perfect for competitions, games, and training scenarios. No real money is involved.

💡
Every SSO user automatically gets a demo account. Top up their virtual balance using the Broker deposit API โ€” no real USDC needed.

Top Up Virtual Balance

POST /api/v1/broker/clients/{user_id}/deposit X-API-Key
POST /api/v1/broker/clients/42/deposit
X-API-Key: bk_your_key_here
Content-Type: application/json

{
  "amount": 10000,
  "note": "Competition starting balance"
}
{ "ok": true, "newBalance": 10000 }

Query User Balance

GET /api/v1/broker/clients/{user_id}/balance X-API-Key
{
  "userId": "uuid...",
  "externalUserId": "42",
  "balance": 10000,
  "availableBalance": 9800,
  "frozenMargin": 200
}

Competition Flow

🏁 Start competition
Top up all participants
Users trade freely
Poll balances → rank by P&L

Your application controls competition logic (start/end/ranking). The trading engine handles all order matching and P&L calculation.


Trading API

These endpoints are called by the user's frontend. All require a Bearer token (from localStorage.auth_token after SSO login).

Get Account Info

GET /api/auth/me Bearer
{
  "id": "uuid...",
  "username": "sso_abc123_42",
  "role": "trader",
  "balance": 10000,
  "availableBalance": 9800,
  "frozenMargin": 200,
  "unrealizedPnl": 42.5
}

Get Available Symbols

GET /api/symbols
[
  {
    "symbol": "BTCUSDT",
    "name": "Bitcoin / USDT",
    "isEnabled": true,
    "defaultLeverage": 10,
    "maxLeverage": 100,
    "tradingStatus": null    // null = open, "closed" = market closed
  }
]

Place an Order

POST /api/trading/orders Bearer
// Market order
{
  "symbol": "BTCUSDT",
  "type": "market",
  "side": "buy",       // "buy" or "sell"
  "quantity": 0.01,
  "leverage": 10
}

// Limit order
{
  "symbol": "BTCUSDT",
  "type": "limit",
  "side": "buy",
  "quantity": 0.01,
  "price": 60000,
  "leverage": 10
}

// With stop-loss / take-profit
{
  "symbol": "BTCUSDT",
  "type": "market",
  "side": "buy",
  "quantity": 0.01,
  "leverage": 10,
  "stopLoss": 58000,
  "takeProfit": 65000
}

Order Response

{
  "id": "order-uuid",
  "symbol": "BTCUSDT",
  "type": "market",
  "side": "buy",
  "quantity": 0.01,
  "status": "filled",       // pending | filled | partially_filled | cancelled
  "filledQty": 0.01,
  "avgPrice": 62000,
  "frozenMargin": 62,
  "createdAt": "2026-01-01T00:00:00.000Z"
}

Get Open Orders

GET /api/trading/orders?status=active Bearer

Cancel an Order

DELETE /api/trading/orders/{order_id} Bearer

Close a Position (Market)

POST /api/trading/orders/{order_id}/close Bearer
// Full close at market price
{}

// Partial close
{ "closeQuantity": 0.005 }

K-Line (Candlestick) Data

GET /api/market/klines?symbol=BTCUSDT&interval=1h&limit=200
// interval options: 1m | 5m | 15m | 30m | 1h | 4h | 1d | 1w | 1M
// Returns: [[time, open, high, low, close, volume], ...]
[[1700000000, 60000, 61000, 59500, 60500, 1234.5], ...]

Current Price (Ticker)

GET /api/market/ticker?symbol=BTCUSDT
{
  "symbol": "BTCUSDT",
  "lastPrice": 62000,
  "change24h": 1.23,
  "high24h": 63000,
  "low24h": 61000
}

WebSocket Events

Connect to the WebSocket server for real-time data. All messages are JSON with a type field.

Connect & Authenticate

const ws = new WebSocket('wss://walltrade.cc/ws');

ws.onopen = () => {
  // 1. Authenticate
  ws.send(JSON.stringify({
    type: 'auth',
    token: localStorage.getItem('auth_token')
  }));

  // 2. Subscribe to price feed
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'price:BTCUSDT' }));

  // 3. Subscribe to account updates
  ws.send(JSON.stringify({ type: 'subscribe', channel: 'account' }));
};

Available Channels

ChannelDescriptionAuth Required
price:SYMBOLReal-time last price tickNo
kline:SYMBOL:INTERVALLive candlestick updatesNo
orderbook:SYMBOLOrder book depth snapshotNo
accountBalance & unrealized P&L updatesYes
ordersOrder status changesYes

Event: price_update

{
  "type": "price_update",
  "symbol": "BTCUSDT",
  "price": 62000,
  "timestamp": 1700000000000
}

Event: kline_update

{
  "type": "kline_update",
  "symbol": "BTCUSDT",
  "interval": "1h",
  "candle": {
    "time": 1700000000,
    "open": 61000,
    "high": 63000,
    "low": 60500,
    "close": 62000,
    "volume": 123.4
  }
}

Event: account_update

{
  "type": "account_update",
  "balance": 10000,
  "availableBalance": 9800,
  "frozenMargin": 200,
  "unrealizedPnl": 42.5
}

Event: order_update

{
  "type": "order_update",
  "order": {
    "id": "order-uuid",
    "symbol": "BTCUSDT",
    "side": "buy",
    "status": "filled",    // pending | filled | partially_filled | cancelled
    "filledQty": 0.01,
    "avgPrice": 62000,
    "unrealizedPnl": 42.5
  }
}

Reconnection (Required)

let retryDelay = 3000;

function connect() {
  const ws = new WebSocket('wss://walltrade.cc/ws');

  ws.onopen = () => {
    retryDelay = 3000;
    authenticate(ws);
    resubscribeAll(ws);
  };

  ws.onclose = () => {
    setTimeout(connect, retryDelay);
    retryDelay = Math.min(retryDelay * 2, 30000);  // max 30s
  };
}

connect();
⚠️
Always implement reconnection logic. On reconnect, re-send auth and all subscribe messages โ€” the server does not remember previous subscriptions.

Widget Embed โ€” Overview

The ST Trading Widget is a self-contained Web Component that can be embedded in any website. It provides a full trading UI โ€” market data, order placement, order management, and account info โ€” scoped to a specific broker.

Users who trade through the widget are automatically associated with the embedding broker's account.

Quick Start

Two lines of HTML are all you need:

<script src="https://walltrade.cc/widget/st-trading-widget.js"></script>
<st-trading-widget
  broker-uid="YOUR_BROKER_UID"
  api-url="https://walltrade.cc"
  default-symbol="BTCUSDT"
  width="400px"
></st-trading-widget>
ℹ️
You can find your pre-filled embed code in Broker Panel โ†’ Management โ†’ ๐Ÿ“ฆ Widget Embed Code. Just copy and paste it into your website.
No CORS configuration needed. The API server allows cross-origin requests from any domain, so the widget works on any external website out of the box. Sensitive endpoints are protected by JWT bearer tokens, not by CORS origin restrictions.

Attributes

AttributeRequiredDefaultDescription
broker-uidโœ…โ€”Your broker UUID. All user accounts created via this widget are tied to this broker.
api-urlโœ…โ€”Backend API URL (e.g. https://walltrade.cc). Also used to resolve brand config.
default-symbolโ€”BTCUSDTThe symbol shown when the widget first loads.
widthโ€”400pxWidget width. Accepts any CSS value: 360px, 480px, 100%.
themeโ€”darkInitial theme. Options: dark, light.
sso-tokenโ€”โ€”JWT for silent SSO login. If provided, the user is logged in automatically without seeing the login form.
brand-domainโ€”hostname of api-urlOverride domain used to fetch brand config. Normally not needed.

SSO Integration

If your platform already has a login system, pass a signed JWT via sso-token to log the user in silently. The widget calls POST /api/auth/sso/token with the token.

<st-trading-widget
  broker-uid="YOUR_BROKER_UID"
  api-url="https://walltrade.cc"
  sso-token="eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
></st-trading-widget>

The JWT must be signed with your SSO shared secret configured in Admin โ†’ Brand Management โ†’ SSO Settings.

Required JWT fields: user_id, tenant_code. Optional: wallet_address, logout_redirect_url.

Example JWT Payload

{
  "user_id": "12345",
  "tenant_code": "your_brand_code",
  "wallet_address": "0xabc...",
  "iat": 1716100000,
  "exp": 1716103600
}

Sign with HS256 using the shared secret provided by the platform operator.

Brand Config

The widget automatically fetches brand config from GET /api/brand/config?domain=<hostname> using the hostname of api-url. This controls:

All brand settings are managed in Admin โ†’ Brand Management. The widget respects them automatically with no extra config needed.

⚠️
Brand config is resolved by domain name, not by broker-uid. Make sure the api-url hostname matches the domain registered in Brand Management.