Trading

Place market, limit and stop orders — the order body, riskPercentage sizing, and every response shape.

POST /bot/v1/spot/orders

Wellat has pairs that it supports. Find them here before you execute your first trade.

Signed, with a JSON body and a fresh idempotency-key header — a UUID you generate, one per trade intent. Reuse a key only to retry the same trade (see Signal Lifecycle & Recovery).

Order body

Common fields (all order types):

There is no strategy identifier in the body — the strategy is resolved from the API key the request is signed with (see Strategies & Your API Key).

FieldTypeRules
symbolstringrequired, e.g. "BTCUSDT"
sidestring"BUY" or "SELL"
tradeTypestring"MARKET" | "LIMIT" | "STOP_LOSS" | "STOP_LOSS_LIMIT"
riskPercentagenumberrequired, > 0 and ≤ 100 — see sizing below
quantitynumberoptional and advisory only — never used for sizing

Type-specific fields:

tradeTypeRequired fieldsMeaning
MARKETfills immediately at market
LIMITpricerests at price until filled
STOP_LOSSstopPricemarket order armed when stopPrice is crossed
STOP_LOSS_LIMITprice + stopPricelimit order at price, armed at stopPrice
{ "symbol": "BTCUSDT", "side": "BUY", "tradeType": "MARKET", "riskPercentage": 10 }
{ "symbol": "BTCUSDT", "side": "BUY", "tradeType": "LIMIT", "riskPercentage": 10, "price": 60000 }
{ "symbol": "BTCUSDT", "side": "SELL", "tradeType": "STOP_LOSS", "riskPercentage": 25, "stopPrice": 58000 }
{ "symbol": "BTCUSDT", "side": "SELL", "tradeType": "STOP_LOSS_LIMIT", "riskPercentage": 25, "price": 57900, "stopPrice": 58000 }
🚧

Notes

USDT must be the quote side of the pair (BTCUSDT yes; USDT-base pairs are rejected). On LIMIT orders the OCO fields (takeProfitLimit, stopLossTrigger, stopLossLimit) are accepted by validation but rejected at execution until OCO support ships — place the protective stop as a separate signal instead. Body limit: 10 KB (413 beyond).

How sizing works — riskPercentage

riskPercentage is the percentage of the free (undeployed) balance committed to the signal:

  • For the strategy's track record: a percentage of the strategy's free virtual USDT (buys) or free base asset (sells).
  • For each copier: the same percentage of that copier's free balance.
📘

It is not a fraction of total equity

If the strategy is 30% deployed, a riskPercentage: 10 signal commits 10% of the remaining 70% — i.e. 7% of equity. quantity, if sent, is validated for shape but never consulted for sizing. Sizing by percentage is what keeps copier accounts of different sizes proportional to the track record.

Responses

Executed (market order)200:

{
  "status": true,
  "message": "Trade executed successfully",
  "data": {
    "orderId": "…",
    "clientOrderId": "DEMO-<your idempotency-key>",
    "symbol": "BTCUSDT",
    "side": "BUY",
    "quantity": 0.001,
    "price": 60000,
    "fee": 0.000001,
    "feeAsset": "BTC",
    "replayed": false,
    "orderStatus": "FILLED"
  }
}

orderStatus is "FILLED" for market signals and "OPEN" for resting placements (limit/stop). replayed: true means this response was served from a previously executed identical signal (same idempotency-key).

Executed placements also carry a collectiveId — the handle for POST /bot/v1/spot/orders/cancel. If you don't store it from the response, you can always recover it from GET /bot/v1/spot/orders (see Signal Lifecycle & Recovery).

Accepted but pending202:

{
  "status": false,
  "message": "Signal accepted, execution pending",
  "data": { "signalId": "0198f9…" }
}
❗️

202 is not a failure

Your signal was durably recorded but the execution result could not be returned in time. Do not send a new idempotency key. Either poll GET /bot/v1/spot/signals/<signalId> or re-send the same request with the same idempotency-key — both converge on the one and only execution.

Rejected400 with a reason: no LIVE strategy bound to your API key, missing price on a LIMIT, riskPercentage out of range, or a pair that cannot be priced.

Next: Signal Lifecycle & Recovery.


Did this page help you?