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_update→ never fired - WS
position_update→ fired (with the newquantity) 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 expect | What the endpoint actually returns |
|---|---|
side: "LONG" | "SHORT" | orderSide: 1 (long) | 2 (short) — integer enum |
avgEntryPrice | entryPrice |
avgClosePrice | averageClosePrice |
realizedPnl | realizedPnL (capital L) |
qty | totalClosedQuantity |
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/ordersis 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/retryand 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:
| Setter | Read-back |
|---|---|
POST /v2/futures/leverage | None — track client-side from your last successful set, or read from Position.leverage once you have an open position. |
POST /v2/futures/margin-type | None — Position.marginMode is the closest, but only available with an open position. |
POST /v2/futures/position-mode | None — 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_MARKETplus oneTAKE_PROFIT_MARKETwith 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.