Perpetuals: trading

Placing and cancelling perp orders, positions, leverage, margin, take-profit/stop-loss and account history.

Every endpoint here is signed — see Authentication. The key is the account binding: there are no wallet or account fields in any request body, and a key can only ever act on the account it was minted for.

Place an order

POST /api/v1/perp/orders

{
  "market": "BTC-USDC",
  "side": "buy",
  "type": "limit",
  "size": "0.01",
  "price": "80000",
  "leverage": 10,
  "timeInForce": "GTC",
  "clientOrderId": "my-order-1"
}
FieldRequiredNotes
market✓BTC-USDC
side✓buy / sell (aliases long / short, case-insensitive)
type✓limit / market
size✓base units, on stepSize grid
pricefor limiton tickSize grid
leverageomit to use the account's setting for that market
timeInForceGTC (default), IOC, FOK, GTX (aliases alo, post_only)
postOnlyreject if the order would take
reduceOnlynever increase the position
positionSidehedge modelong / short; omit in one-way mode
clientOrderIdyour idempotency key — strongly recommended
slippageTolerancemarket orders, fraction: 0.005 = 0.5%
slippageBehaviorpartial_fill / reject_all / warn_only
stpModeself-trade prevention: none, expire_taker, expire_maker, expire_both

The receipt echoes the order's real state:

{
  "orderId": "…", "market": "BTC-USDC", "side": "buy", "type": "limit",
  "size": "0.01", "filledSize": "0", "remainingSize": "0.01",
  "status": "open", "timeInForce": "GTC", "leverage": 10,
  "reduceOnly": false, "postOnly": false, "autoModified": false,
  "createdAt": 1790095097073, "updatedAt": 1790095097073
}

Statuses are created, open, partial, filled, canceled, expired (plus force_cancelled for risk-engine cancellations).

Leverage resolution

When you omit leverage, the server resolves it: the account's stored preference for that market → the platform default of 20x → clamped to the market's current maxLeverage. The receipt's leverage field is the value that actually applied, which may be lower than what you asked for if the market's cap moved. Read it rather than assuming; in batch responses each leg carries its own leverage for the same reason.

Auto-modification in one-way mode

In one-way mode, a reverse-direction order without reduceOnly is interpreted as "close what I have": the size is capped at the existing position and reduceOnly is forced on. The response tells you this happened — autoModified: true, with requestedSize / requestedReduceOnly preserving what you sent next to the size / reduceOnly that applied. Surface that to users; silently filling a smaller order than requested is how reconciliation bugs start.

Batch and cancel

CallEndpoint
Place manyPOST /api/v1/perp/orders/batch — body {"orders": [ …PlaceOrderRequest… ]}
Cancel oneDELETE /api/v1/perp/orders/{id}
Cancel manyDELETE /api/v1/perp/orders/batch — body {"order_ids": [...], "market": "…", "side": "…"}
Cancel allDELETE /api/v1/perp/orders/cancel-all — optional body {"market": "…"}; no body means every market
List openGET /api/v1/perp/orders
Fetch oneGET /api/v1/perp/orders/{id}

Batch place returns per-leg results (successCount, failedCount, and an entry per order carrying either orderId or error) — a batch is not atomic, so always walk the array. Batch cancel's canonical field is order_ids; orderIds is accepted as an alias but the snake_case form is the documented contract. Batch calls cost weight 3 against your rate budget — see Rate limits.

Positions

GET /api/v1/perp/positions            # all, or ?market=BTC-USDC
GET /api/v1/perp/positions/{market}   # array — hedge mode can hold long and short at once
{
  "id": "…", "market": "BTC-USDC", "side": "long", "positionSide": "net",
  "size": "0.01", "entryPrice": "84120.4", "markPrice": "86148.95",
  "leverage": 10, "marginMode": "cross",
  "margin": "84.12", "maintenanceMargin": "3.44",
  "liquidationPrice": null, "bankruptcyPrice": "…",
  "unrealizedPnl": "20.28", "unrealizedPnlPercent": "24.11",
  "realizedPnl": "0", "adlRanking": 2,
  "tpSlStatus": "none", "protection": [], "accountIndex": 0
}

Three fields that mean more than they look:

  • liquidationPrice is null in cross margin — and that is correct, not missing data. In cross mode there is no per-position liquidation price; the account as a whole is what gets liquidated. In isolated mode it is a number.
  • adlRanking (1–5, 5 = first in line) is your queue position for auto-deleveraging if the insurance fund is exhausted on the other side.
  • protection lists every live thing that will close this position at a trigger — TP/SL levels, bracket exits, OCO legs — with the engine that owns each. tpSlStatus (none / tp_only / sl_only / both) is the summary across all of them.

Close, leverage, margin

POST /api/v1/perp/positions/{market}/close      # {"type":"market"} or {"type":"limit","price":"…","size":"…"}
PUT  /api/v1/perp/positions/{market}/leverage   # {"leverage": 10, "position_side": "long"}
POST /api/v1/perp/positions/{market}/margin     # {"type":"add","amount":"50","position_side":"long"}
GET  /api/v1/perp/positions/{market}/leverage

Omitting size on close means a full close. position_side is required in hedge mode, omitted in one-way. Note the snake_case bodies here — see Notation.

Take profit and stop loss

POST   /api/v1/perp/positions/{market}/tpsl   # set
PUT    /api/v1/perp/positions/{market}/tpsl   # modify
DELETE /api/v1/perp/positions/{market}/tpsl   # clear
{ "take_profit_price": "95000", "stop_loss_price": "78000", "replace": false }

TP/SL on perpetuals is market-priced only — the trigger fires a market close. There is no limit-priced TP/SL; fields that once suggested otherwise were removed rather than left as decoration.

If the position is already protected by a bracket or OCO leg, the default replace: false refuses with POS_TPSL_EXISTS (14005) and lists the order ids holding that protection, rather than arming a second engine against the same position. Send replace: true to cancel those and take over.

History

EndpointContents
GET /api/v1/perp/orders/historyterminal orders; market / side / status / start_time / end_time / limit / cursor
GET /api/v1/perp/history/tradesyour fills
GET /api/v1/perp/history/pnlrealized PnL summary
GET /api/v1/perp/history/fundingfunding paid and received
GET /api/v1/perp/history/liquidationsyour liquidations
GET /api/v1/perp/positions/historyclosed positions
GET /api/v1/accounts/me/perp/marginaccount-level margin state
GET /api/v1/accounts/me/perp/trading-statsvolume, fees, tier inputs

Paged endpoints return a cursor; follow it until empty rather than stopping at the first short page.

Before you go live

  • Send a clientOrderId on every order. It is what makes a retry safe after a timeout.
  • Check status and filledSize on the receipt — IOC / FOK orders can come back immediately terminal.
  • Check leverage and autoModified on the receipt before recording the fill in your own books.
  • Test on https://api-testnet-stable.1024ex.com first; keys are separate and the faucet funds test accounts.

Did this page help you?