ZetariumZetariumDex

Known Limitations

Behaviors of the underlying venue and the current API surface that integrators repeatedly trip over. Read before committing to an architecture.

This page lists the API behaviors that surprise integrators most often. None of these are bugs you can work around with a config flag — every one needs an explicit decision in your client code (or a wait for the upstream venue to ship the missing piece).

If you're wiring up a fresh integration, skim this page first. The items here account for ~80 % of the time-to-correctness gap between Zetarium and a more mature venue like Binance or Bybit.


MAKER fill visibility

GET /v2/futures/myTrades only returns TAKER (aggressive) fills. Your own resting LIMIT orders being matched by someone else does not appear here. The WebSocket order_update event has the same gap — it fires for status flips driven by REST/worker activity but the upstream venue does not push fill events for MAKER side, so the worker has nothing to relay.

Reproduction. Place a LIMIT BUY at a price that will cross the book within a minute. After it fills:

  • GET /v2/futures/myTrades?symbol=...&fromId=<last-known>0 new rows
  • WS order_updatenever fired
  • WS position_updatefired (with the new quantity)
  • GET /v2/positions → reflects the fill

Workaround. Reconstruct fills from position_update deltas. Any change in quantity that you didn't initiate via REST in the last ~500 ms is a MAKER fill. For ledger-grade accuracy, also reconcile against GET /v2/futures/positionHistory for closed positions.

let lastKnownQty = 0;
ws.on('message', (raw) => {
  const msg = JSON.parse(raw);
  if (msg.event !== 'position_update') return;
  const { symbol, quantity } = msg.data;
  const delta = Number(quantity) - lastKnownQty;
  if (Math.abs(delta) > 1e-9 && !justSubmittedTakerOrder(symbol)) {
    onMakerFill(symbol, delta);
  }
  lastKnownQty = Number(quantity);
});

This affects market-making, ladder rotation, and any reduce-only chase strategy. Bot fill detection latency is bounded by the worker's position-sync interval (SYNC_POSITIONS_INTERVAL_MS, default 5 s) plus the venue's own delta latency.

Tracked upstream; once the venue exposes MAKER fills via userTrades we will pass them through automatically and remove this section.


positionHistory raw shape

GET /v2/futures/positionHistory is a raw venue passthrough — its field names diverge from every other position / trade endpoint on the API. Parsers written against the "standard" Zetarium camelCase will read null for every value.

What you might expectWhat the endpoint actually returns
side: "LONG" | "SHORT"orderSide: 1 (long) | 2 (short) — integer enum
avgEntryPriceentryPrice
avgClosePriceaverageClosePrice
realizedPnlrealizedPnL (capital L)
qtytotalClosedQuantity

A normalised wrapper is on the roadmap; when it ships it will be added as a sibling endpoint, and this raw shape will continue to be returned for backwards compatibility.


No editOrder endpoint

There is no single-call modify endpoint for a working order's price or quantity. Order updates require a cancel + create round-trip:

await signedRequest('POST', `/v2/orders/${orderId}/cancel`);
const newOrder = await signedRequest('POST', '/v2/orders', {
  body: { symbol, side, type: 'LIMIT', quantity, price: newPrice, clientOrderId: randomUUID() },
});

Implications:

  • 2× the API calls vs a venue with a native amend (Binance, Bybit, OKX). Factor into your rate-limit budget — POST /v2/orders is 300/min and cancel sits in the default 1000/min bucket.
  • Queue position is lost. The new order goes to the back of the book at the new price. If your strategy depends on staying near the front of the queue, frequent re-pricing burns it.
  • Atomicity is not guaranteed. A successful cancel followed by a failed create leaves you with no working order. Wrap in try/catch/retry and assume the worst-case resubmit may fill at a worse price than expected.

If you're driving high-frequency price adjustments, consider widening your tick rather than re-pricing every micro-move.


No GET counterparts for several setters

These endpoints are set-only today; reading the current state from the venue is not exposed:

SetterRead-back
POST /v2/futures/leverageNone — track client-side from your last successful set, or read from Position.leverage once you have an open position.
POST /v2/futures/margin-typeNone — Position.marginMode is the closest, but only available with an open position.
POST /v2/futures/position-modeNone — track client-side. The venue silently rejects further sets if a position is open.

Practical impact. You cannot independently audit "is my account actually in HEDGE mode right now?" — only infer it from downstream behavior (whether positionSide: LONG orders are accepted vs reduced to BOTH). Mode mismatch between your config and the venue is a silent failure mode; assert mode at session start by issuing a POST with the expected value and trusting the response.


TP / SL on the same position via REST

Placing a TAKE_PROFIT and a STOP order against the same open position via POST /v2/orders fails for the second order with:

{ "ok": false, "error": "Close quantity exceeds available position quantity" }

This happens in both orders (TP-first or SL-first), and with reduceOnly: true set. Root cause: each closing order reserves the full position quantity, so the second one sees zero available to close.

The Zetarium web UI does pair TP and SL on the same position — it calls a position-level setter that is not yet exposed in the public REST API. We're working on surfacing this endpoint; until then, options are:

  • Sequential wait — submit TP, wait for it to fill (or be cancelled), then submit SL. Acceptable for OCO-with-far-targets, useless for bracket strategies that need both legs live concurrently.
  • Use the UI for any position that needs a real bracket.
  • Native reduce-only fills — if your TP/SL prices are tight enough that the position can be exited in a single MARKET, send one STOP_MARKET plus one TAKE_PROFIT_MARKET with carefully managed quantities (each ≤ positionQty / 2); the second one then has room.

This page will be updated with the dedicated endpoint as soon as it ships.


No cancel-all-conditional endpoint

DELETE /v2/orders/open cancels every working regular order on the account (or symbol). Conditional / trigger orders (STOP, TAKE_PROFIT, STOP_MARKET, TAKE_PROFIT_MARKET) are not in scope for that wholesale cancel — you have to fan out per-order POST /v2/orders/:id/cancel calls.

For a position with a STOP and a TAKE_PROFIT bracket, that's two calls + the rate-limit cost. Bots that maintain many conditional orders should keep a client-side index and prepare a batch cancel loop, not rely on a single API call.


WebSocket auto-subscribe

The server starts pushing orderbook, trades, ticker, and kline for every active symbol (~144 today) the moment the WS upgrade completes — independent of what you subscribe to. Sustained throughput hovers around 300 – 400 msg/s at idle.

There is no opt-out. You must filter client-side in your message handler. See WebSocket → Auto-subscribe behavior for the operational checklist.

Server-side per-symbol gating is on the roadmap; it would let you declare your symbols at connect time and have the server filter for you. Until then, treat the auto-stream as a fixed bandwidth tax.


HEDGE-mode Position displays as BOTH in the UI

When the account is in HEDGE mode and you place orders with positionSide: LONG or SHORT, the venue accepts them — but the web UI's "Positions" panel often shows the resulting position with Position: BOTH instead of the expected LONG / SHORT label. This is a UI convention quirk on the venue side and does not reflect any mismatch in API behavior; orders and PnL routing still respect the hedge legs correctly.

The practical consequence is that you cannot visually verify your account is in HEDGE mode from the UI — combine this with the missing GET /v2/futures/position-mode (above) and you have to trust the last successful POST /v2/futures/position-mode you made. Bot tests should assert mode from observed downstream behavior, not from UI inspection.


GET /v2/klines ignores startTime / endTime

The validator accepts startTime and endTime query parameters for backwards compatibility with Binance-shaped clients, but the upstream venue cache does not support time-range queries on candles, so they are silently dropped before the upstream call. The endpoint always returns the most-recent limit candles (default 300).

If you need historical candles outside the cache window, request them in shorter intervals and stitch client-side, or use a public mirror — this endpoint is for live charting, not backfill.


Have you found another?

If you trip over a gap that isn't here, drop the smallest reproducer you can produce (curl + expected vs actual) into a support ticket and quote the errorId from the response body. We track these and surface them on this page once confirmed.

On this page