API and events

Every futures endpoint — trader and admin — with its method, path, permission key and the fields that matter, plus the three WebSocket routes and what they stream.

3 min readUpdated 6 August 2026api, websocket, permissions, reference

All futures routes live under /api/futures (trader) and /api/admin/futures (operator), with one exception: the admin orders table is served from /api/admin/futures/order but presented under Finance in the menu.

Admin routes carry a permission key; trader routes are gated by sign-in and, for order placement only, by the futures_trading KYC feature.

Markets

Every market whose status is enabled, each with a symbol of CURRENCY/PAIR
One market by id — returns id, currency, pair and status only, not the metadata

The list endpoint returns the full row including the metadata blob, which is what the trading ticket reads for precision, limits, leverage rungs and fee rates. The by-id endpoint deliberately does not.

Orders

The caller's orders. currency + pair filter to one symbol; type=OPEN returns only resting orders
Place an order. Requires the futures_trading KYC feature
Cancel one order and refund the unfilled share of its margin and fee. timestamp is required
Cancel every open order for the caller

Placement body

{
  "currency": "BTC",
  "pair": "USDT",
  "type": "LIMIT",
  "side": "BUY",
  "amount": 0.5,
  "price": 100000,
  "leverage": 20,
  "stopLossPrice": 95000,
  "takeProfitPrice": 110000
}

currency, pair, type, side, amount and leverage are required.

  • price is required for LIMIT and ignored for MARKET — a market order is priced by walking the book at placement.
  • stopLossPrice / takeProfitPrice are optional and are carried onto the position that the fill creates. Note the field names: a ticket sending stopLoss / takeProfit sets no stop at all.

The response echoes the stored order with isTaker and feeRate, so a ticket can label the charge rather than guess at it from the order type.

Failure codes worth handling

Code When
400 Market disabled, bad side or type, amount or price outside the market's limits, leverage not offered, insufficient balance, missing fee configuration
404 No such market, or the market has no metadata
422 A market order cannot be priced — no resting liquidity, or not enough depth to fill the amount
503 The Ecosystem extension is not available

Every rejection keeps its real status code and names the thing to change. A 500 from this route means something genuinely broke.

Positions

The caller's positions. type=OPEN_POSITIONS filters to open; type=POSITIONS_HISTORY returns everything that is not open
Close an open position at the live mark. Body carries currency, pair and side

Each position in the list carries two computed fields beyond the stored row:

  • mode — always "HEDGE". Long and short on the same symbol are independent positions, each posting its own margin.
  • liquidationPriceentry × (1 ∓ 0.9 ÷ leverage), derived from the engine's own full-liquidation threshold rather than stored, so the two can never drift apart.

The close response returns markPrice, margin, realizedPnl and credited.

Market data

Tickers for every futures market, from the matching engine
Historical candles. symbol, from, to and interval are all required

Candles come back as openTime, closeTime, open, high, low, close, volume.

WebSocket routes

Three sockets, all under the same /api prefix your proxy already forwards.

Route Auth Payload
/api/futures/market none { type, symbol } where type is orderbook, ticker or trades
/api/futures/ticker none Broadcasts all tickers on demand
/api/futures/order required The caller's own order and position updates

The market socket validates the symbol against the database on subscribe and refuses a market that does not exist or is disabled — so switching a market off also stops its data stream for new subscribers. Subscribed streams are pushed every 500 ms.

Subscribing to trades on the market socket returns an empty array. Order book and ticker are the live streams.

Admin — dashboard

Open interest and side skew per market, positions closest to liquidation, leverage concentration and period activity

Takes timeRange of 24h, 7d or 30d (default 7d). The payload's source object reports whether the position store answered, whether the scan was truncated, how many rows it read and what the cap was. Read it — a truncated scan is a sample, not a total.

Admin — markets

Paginated, filterable list of every market
Create a market from two active Ecosystem token ids
Bulk delete markets by id — permanent
Bulk enable or disable markets
One market with its full metadata
Update flags and metadata. Currency and pair cannot be changed
Delete one market — forced hard delete, no restore
Enable or disable one market

Create takes token ids, not symbols, and resolves them to currency codes itself. Both tokens must exist and be active, or the call is a 404; a duplicate pair is a 409.

Admin — orders and positions

Paginated futures orders, read from ScyllaDB
Paginated futures positions, read from ScyllaDB

Both accept the standard list parameters (page, size, sort, filter, search). Neither has a write counterpart: there is no admin route that cancels a trader's order or closes their position.

Permission keys

Nine keys matter, and they are not all enforced in the same place. Seven are route metadata — the permission field on the handler, checked by the API before it runs, and the value printed beside each admin endpoint above. The other two, access.futures.position and access.futures.order, appear on no route at all: they are frontend page gates, exported from (ext)/admin/futures/position/permission.ts and (dashboard)/admin/finance/order/futures/permission.ts. They decide whether the screen opens, not whether a request succeeds.

Key Enforced as Guards
access.futures.market Route and page gate The dashboard endpoint, plus the Futures and Markets screens
view.futures.market Route Reading market rows
create.futures.market Route Creating a market
edit.futures.market Route Editing a market, and status toggles
delete.futures.market Route Deleting markets
view.futures.position Route Reading position rows
view.futures.order Route Reading order rows
access.futures.position Page gate only Opening the Positions screen
access.futures.order Page gate only Opening the Futures Orders screen under Finance

Grant the pairs together. A role holding view.futures.position without access.futures.position can read positions over the API but cannot open the screen; the reverse opens a screen whose first request comes back 403.

Admin → Roles lists fifteen futures rows rather than nine. create, edit and delete for both .order and .position are seeded and grantable, but neither table is rendered with canCreate, canEdit or canDelete, so those buttons never appear and the six keys gate nothing on any screen or route. There is no admin write path for orders or positions to grant.

Super Admin short-circuits every check by role name and needs none of them. See core permissions for how keys are derived and where each one is enforced.

Numbers on the wire

Orders and positions are stored in ScyllaDB as VARINT columns holding fixed-point values scaled by 10^18, and are de-scaled on the way out. Two consequences for anyone consuming these endpoints:

  • Amounts, prices, costs and fees arrive as strings on the raw rows and as numbers on the formatted routes. Do not assume one shape from the other.
  • leverage is a plain integer, not a scaled value. A 10x position stores 10. Anything that de-scales it produces 1e-17 and renders as 0.00x.