Skip to main content

Changelog

All notable changes to the DLTM Derivatives API.

This changelog follows Keep a Changelog conventions.


2026-10-16 — v2.0.2​

Changed​

  • Documented how unique reference_id is: it is rejected as a duplicate while an order placed with it is still open. Once that order has ended (for example, been cancelled) the same reference may be accepted for a new order, so keeping references unique is up to the client — in particular for retries, which are not guaranteed to be rejected once the first attempt's order has ended.
  • Funding on perpetuals now settles every hour, on the hour (UTC), instead of every 8 hours at 00:00, 08:00 and 16:00 UTC. funding_rate is the rate for one settlement period, so hourly rates are correspondingly smaller (e.g. 0.0000125 per hour where it was 0.0001 per 8 hours).
  • GET /instruments — funding_interval is "1h" for perpetuals (was "8h").
  • GET /funding_rates returns one record per hourly settlement.
  • POST /customers/{code}/deposits and POST /customers/{code}/withdrawals — amount now accepts up to 10 decimal places (previously 8), matching the exchange's transfer precision. More precise amounts are still rejected up front with 400 VALIDATION_ERROR. If the exchange limits an asset to fewer decimals, the request fails with the same error code.

Fixed​

  • liquidations WebSocket channel — margin_deficit now carries the amount needed to restore initial margin (e.g. "2542.39"). It used to send "Y" on a margin call and "N" on liquidation and auto-deleveraging. It is "0" when available margin is not negative, and still null when the risk state is normal.
  • GET /customers/{code}/orders/{reference_id} right after a successful DELETE no longer returns open for up to about 1.5 seconds. A cancelled or filled order now reports its final status, fill amount and average fill price as soon as the cancel or fill is booked.
  • GET /customers/{code}/orders/{reference_id} — an open GTD order now reports its expire_time. It was always null there, while the order list showed it. Once the order is filled, canceled or expired, this endpoint still reports expire_time: null; the order history has it.
  • GET /customers/{code}/orders/{reference_id} no longer fails with 500 EXCHANGE_ERROR when the reference has been used for more than one order. It returns the open order, or the one updated last when none is open; DELETE on the same reference acts on the open order, as before. With none open, its 409 ORDER_NOT_CANCELLABLE names the status of the order GET returns.
  • GET /customers/{code}/orders/{reference_id} now returns 404 NOT_FOUND for the reference of an order that was refused at placement (POST /customers/{code}/orders answered 400 with INSUFFICIENT_FUNDS or ORDER_REJECTED), instead of failing with 500 and an empty body. A refused order is not created as an order, so there is nothing to read back here; the reason is in the 400 response. If the reference is then used for a new order, it returns that order, which could also fail with 500 before.
  • GET /tickers, GET /tickers/{symbol}, the ticker WebSocket channel and GET /funding_rates/{symbol} — next_funding_time is the next whole hour (UTC). It still reported the next 00:00 / 08:00 / 16:00 UTC boundary, which was wrong for 7 of every 8 hours.
  • ticker WebSocket channel — next_funding_time is now documented on the snapshot; it was already sent but missing from the docs.
  • locked on GET /customers/{code}/balances is now reported at the same precision as total and available; it was rounded to two decimals, so total and available + locked could differ by a fraction of a cent.
  • GET /customers/{code}/positions with a symbol or side filter that matches nothing now returns "records": []; it returned "records": null. The same applies to GET /customers/{code}/balances?asset= with no matching asset.
  • POST /customers/{code}/orders, GET /customers/{code}/orders and GET /customers/{code}/orders/{reference_id} — a GTD order now reports time_in_force: "GTD" and its expire_time. It was reported as GTC with no expire_time. expire_time is now always present on an order and null for every other time in force.
  • A GTD order that reaches its expire_time now reads back with status: "expired" (reason: "EXPIRED"). It was reported as canceled.
  • DELETE /customers/{code}/orders/{reference_id} on an order that is already filled, canceled, expired or rejected now returns 409 ORDER_NOT_CANCELLABLE. It returned 404 NOT_FOUND, although GET still returned the order.
  • orders WebSocket channel — expire_time is now an RFC 3339 timestamp, the same as on the REST order endpoints.
  • GET /customers/{code}/orders/{reference_id} — the documented response is now the full Order model, which is what the endpoint already returns (type, post_only, expire_time, reason, reference_id, created, updated were missing from the docs).
  • POST /customers/{code}/orders now rejects a conditional order whose trigger is already reached or on the wrong side of the market, with 400 VALIDATION_ERROR, e.g. trigger_price 117.201 must be above the current last trade price (117.436) for sell take_profit orders. Before, such an order was accepted as open and either never fired or fired in the opposite direction (a take-profit sell below the market behaved like a stop-loss). The trigger is checked against the last trade price for stop_loss, stop_loss_limit, take_profit and take_profit_limit, and against the mark price for both legs of take_profit_stop_loss. A sell take-profit must be above that price and a sell stop-loss below it; for a buy it is the other way round.

2026-10-05 — v2.0.1​

Changed​

  • amount, entry_price, mark_price and unrealised_pnl on GET /customers/{code}/positions, GET /customers/{code}/positions/{position_code} and the positions WebSocket channel are no longer padded to 16 decimal places. amount is rendered with at least the instrument's lot_size decimals, prices with at least its tick_size decimals, and unrealised_pnl with at least the settlement currency's ledger precision (10 decimals for USDC); values with more places keep them (a position opened by a notional order can be finer than the lot, an average entry price can be off-tick) and nothing is rounded.

Fixed​

  • GET /customers/{code}/balances now reports total as the exact ledger balance at the asset's ledger precision (10 decimals for USDC and USDT, the precision fees and funding post with), so it reconciles to the digit against the deposits, fees and funding in GET /customers/{code}/transactions. It was previously rounded to 6 decimals. locked is the initial margin currently reserved on the account (for an open position amount × entry_price × initial_margin_requirement, which does not move with mark price); it was previously rounded to 2 decimals. available is total − locked, so available + locked + pending equals total exactly, as documented. All three are rendered at the same precision ("0.0000000000" when flat).
  • total_equity on GET /customers/{code}/account-summary and on the margin WebSocket channel now includes unrealised PnL, as documented ("balance + unrealised PnL"). It previously reported the collateral balance only. used_margin and unrealised_pnl are unchanged; available_margin is collateral − used margin on the same exact ledger basis as the balances endpoint (differs from before only beyond the 6th decimal); the deprecated margin_ratio keeps its definition (used margin over the collateral balance).
  • total_equity, available_margin, used_margin and unrealised_pnl on GET /customers/{code}/account-summary and the margin WebSocket channel are now reported at the currency's ledger precision (10 decimals for USDC), the same as the balances endpoint, instead of 2 decimals, so the summary adds up against balances and transactions without rounding. margin_ratio and leverage_ratio stay at 4 decimals.
  • updated_at on GET /customers/{code}/account-summary (and timestamp on the margin and positions channels) is now the newest valuation time across the account's collateral assets. It could previously report the stale time of an asset with no activity.

2026-09-24 — v2.0.0​

Breaking changes​

  • leverage on POST /customers/{code}/orders now selects an account-wide margin profile instead of a per-instrument setting: it applies to every instrument and open position of the customer. Only leverages available to the customer are accepted — currently up to "2"; values above max_leverage are rejected with 400 and the order is not placed.
  • max_leverage on GET /customers/{code}/margin-profile now reflects the highest leverage the customer may actually select (currently "2"); it was a fixed "13".
  • GET /funding_rates and GET /funding_rates/{symbol} are now served from our own settlement archive. History starts where the archive starts; settlements before that point are no longer available.
  • GET /funding_rates pagination cursors issued before this release are no longer valid and are rejected with 400 VALIDATION_ERROR; restart the listing from the first page. A cursor must be used with the same start and end it was issued for.
  • GET /funding_rates/{symbol} returns 404 NOT_FOUND when no funding data has been recorded for the instrument yet, or when none has been received in the running interval; previously any perpetual known to the venue returned a rate.

Added​

  • PUT /customers/{code}/margin-profile with body {"leverage": <integer>} selects the account's margin profile without placing an order. Returns the profile now in effect; a no-op when the leverage is already active. 400 when the leverage is out of range, not available, or would put an open position into margin call.

  • Optional customer_ip on POST /customers/{code}/orders. Send the IP address of the end customer who placed the order, in IPv4 or IPv6 form — "203.0.113.7" or "2001:db8::1". A port, a CIDR suffix or an IPv6 zone is rejected with a 400.

    The field is optional for now (see Deprecated above). It is recorded for compliance and is never echoed back — no endpoint returns it.

    Cancelling an order does not take a customer_ip: DELETE .../orders/{reference_id} has no request body.

  • leverage_ratio on GET /customers/{code}/account-summary and on the margin WebSocket channel. The venue-reported (collateral + unrealised PnL) / position notional as a decimal fraction (e.g. 5.1316 = 513%). This is the value the venue compares against margin_call_threshold and liquidation_threshold, so clients can read distance to margin call directly; higher is safer. Rounded down to 4 decimal places ("1.0000" when the account has no open positions); "0" when the venue reports no value.

  • GET /funding_rates accepts a before cursor for backward pagination, and response_metadata now also carries start_cursor and has_previous, like the other paginated lists.

  • GET /funding_rates documents its 404 NOT_FOUND response for an unknown symbol.

  • notifications WebSocket channel (private, broker-scoped). A generic channel for customer notifications, distinguished by type with the type-specific fields in a nested data object. First type: position_depreciation — a MiFID II notice that a customer's open position has lost a further 10% of its initial value (evaluated periodically; one notice per threshold per position baseline).

  • GET /customers/notifications — the durable record of every pushed notification, with customer_code, type, symbol and creation-time filters and the usual cursor pagination. Same record shape as the WebSocket payload, so one parser serves both.

Changed​

  • GET /customers/{code}/margin-profile: leverage is the leverage of the assigned profile (nearest integer to 1 / initial_margin), and maintenance_margin / liquidation_threshold are now defined by the assigned profile rather than being platform-fixed. instrument_margins is normally empty. The endpoint may return 404 (unknown customer) and 502 (margin profile not yet provisioned at the exchange).
  • leverage on order entry accepts any positive integer string (^[1-9][0-9]*$); the effective bound is the customer's max_leverage.
  • Order created / updated are now always rendered in UTC (2026-09-15T15:23:56Z). They previously carried the server's local offset. Both remain RFC3339, so any conformant client is unaffected.
  • leverage_ratio on the liquidations WebSocket channel is rounded down to 4 decimal places, the same rendering as the new margin-channel and account-summary field. Previously the venue's 8-decimal value was passed through unmodified.
  • funding_time on GET /funding_rates/{symbol} is the time of the latest settlement as recorded, not a computed 8-hour boundary. An instrument with no settlement recorded yet (freshly listed) reports the preliminary rate as funding_rate with funding_time set to the start of the running interval.
  • predicted_rate is null in the short window right after a settlement, until the first preliminary rate of the new interval is received, and null when no preliminary has been received in the running interval.
  • GET /funding_rates: a window with start >= end returns an empty page; a negative start or an end beyond year 9999 is rejected with 400 VALIDATION_ERROR.
  • stop_loss, stop_loss_limit, take_profit, take_profit_limit and take_profit_stop_loss orders are now placed through the exchange's unified order entry, the same path as market and limit orders. Visible effects on these order types:
    • created / updated are now populated (previously empty), so they sort and paginate by time in GET /customers/{code}/orders like every other order instead of always floating to the top.

    • time_in_force is now returned: IOC for stop_loss, take_profit and take_profit_stop_loss, GTC for stop_loss_limit and take_profit_limit. On POST /customers/{code}/orders the field may be supplied for these types but must match those values; anything else is rejected with 400 VALIDATION_ERROR.

    • A price that the order type does not use is now rejected with 400 VALIDATION_ERROR instead of being silently dropped. This covers limit_price on a stop_loss, take_profit or take_profit_stop_loss, trigger_price on a market, limit or take_profit_stop_loss, take_profit_price / stop_loss_price on anything but take_profit_stop_loss, and trigger_price_type on market or limit. Previously such a field was accepted and discarded, so an order could come back without the protection the caller thought it had asked for.

    • GET /customers/{code}/orders/{reference_id} now reports type stop_market / stop_limit and trigger_price for stop-loss and take-profit orders (previously inferred as market / limit), and take_profit_price / stop_loss_price for take_profit_stop_loss.

    • Like market and limit orders, these orders are readable through GET .../orders/{reference_id} immediately after the create response for as long as they are open, and appear in the order list immediately.

    • The orders WebSocket channel now emits updates for stop and take-profit orders (placement, cancel, trigger, fills), with order_type stop_market / stop_limit / take_profit_stop_loss, trigger_price and trigger_price_type. These order types previously produced no WebSocket updates at all.

Fixed​

  • Removed the special case that rejected leverage "11".
  • Order reads returned a different shape depending on which endpoint answered. The same order now looks the same from POST /customers/{code}/orders, GET .../orders, GET .../orders/{reference_id} and DELETE .../orders/{reference_id}. Specifically:
    • GET .../orders/{reference_id} now returns created and updated. Both are schema-required, but the read served from the exchange's order-status index left them out — so an order carried timestamps for about a second after being placed and lost them once that index caught up.
    • GET .../orders/{reference_id} now returns cash_amount and filled_quote for orders placed with cash_amount. Same cause: the exchange returns them on every endpoint, we only read them from some.
    • customer_code is now returned by every order endpoint. DELETE .../orders/{reference_id} and each record in GET .../orders never included it, and POST /customers/{code}/orders only echoed it if the request body happened to carry a customer_code — which is not a field of the documented request schema, so in practice it was always absent. It is now always the customer from the request path.
    • reason is now populated on cancelled and expired orders (for example USER_INITIATED when you cancelled it, TIME_IN_FORCE when an IOC order expired unfilled). It was always null. This includes the response to DELETE .../orders/{reference_id} itself, which reports USER_INITIATED — cancelling an order and then reading it back give the same status and the same reason. Note that on that one response the value is derived from the request rather than reported by the exchange: the exchange states a reason only on a subsequent read.
    • post_only is now echoed on POST /customers/{code}/orders. Placing a maker-only order previously came back post_only: false. Reads still report false for every order — the exchange does not report the flag back on any read endpoint.
  • leverage_ratio on the liquidations WebSocket channel was documented as total notional / equity. The venue reports the inverse, (collateral + unrealised PnL) / position notional, matching the new margin-channel field.
  • GET /funding_rates/{symbol} no longer stalls and fails with 500 EXCHANGE_ERROR on perpetuals with thin history; both funding-rate endpoints answer in well under a second regardless of the range requested.
  • DELETE /customers/{code}/orders/{reference_id} returned status: open for the cancelled market, limit, stop_loss and stop_loss_limit order it echoed back. It now returns canceled.
  • GET /customers/{code}/orders without a status filter mixed open orders with filled and cancelled take-profit orders. It is now open-only, the same as status=open (the only supported value).
  • GET /ohlc — interval=1h is accepted again. The parameter's allowed values listed 60m but not 1h, even though the example said 1h and the venue serves both as the same hourly candle; once request validation became strict, 1h was rejected with 400 VALIDATION_ERROR. 60m keeps working.

Deprecated​

  • Sending POST /customers/{code}/orders without customer_ip is deprecated. The field stays optional for now but will become required in a future release. Start sending it on every order.
  • margin_ratio on GET /customers/{code}/account-summary and on the margin and liquidations WebSocket channels. It is derived locally as used_margin / total_equity, does not move with mark price, and is not comparable to the margin thresholds. The value is unchanged and the field remains present; it will be removed in a future release. Use leverage_ratio.

Known limitations​

  • created is the time of the last event on an order, not its placement time. The exchange exposes no creation timestamp on any order endpoint, so both created and updated are filled from the one timestamp a record carries. For a resting order that has had no events the two are the same instant and created is exact; for an order that was later filled or cancelled, created reports that later event. Do not use it to compute order age, and do not rely on it to order an order history. We have asked the exchange to expose a real creation timestamp.

    One consequence is visible across two calls: DELETE .../orders/{id} reports the placement instant (the cancel ack still carries it), while a GET of that same order afterwards reports the cancel instant. Verified on UAT 2026-09-16 — the two were 4s apart. status and reason agree; only the timestamps differ.

  • type on the response to POST /customers/{code}/orders is the type you asked for (stop_loss, take_profit, …), while every read of the same order reports the mechanical type the exchange stores (stop_market, stop_limit). A stop_loss order therefore reads back as stop_market. This is the only field that differs by endpoint.


2026-09-11 — v1.15.0​

Added​

  • positions WebSocket channel (positions:{customer_code}, private). Pushes a full snapshot of the customer's open positions every ~5 seconds, in the same shape as GET /customers/{code}/positions, so clients no longer need to poll the REST endpoint for live position updates.
  • initial_margin_requirement, margin_call_threshold and liquidation_threshold on GET /customers/{code}/account-summary and on the margin WebSocket channel. The margin rates the venue currently applies to the account, as decimal fractions (e.g. 0.12 = 12%), passed through unmodified; "0" when the venue reports no value.
  • liquidation_threshold on GET /customers/{code}/margin-profile — the liquidation margin level as a decimal fraction, alongside initial_margin and maintenance_margin.

Changed​

  • margin WebSocket channel updates are now pushed every ~5 seconds (previously ~10). The margin and positions channels are refreshed together, so for a given customer both always describe the same instant.

Fixed​

  • POST /customers/{code}/deposits and POST /customers/{code}/withdrawals — the venue asset id is now resolved from the venue's asset reference list (synced by posttrade) instead of from instrument symbol legs. On a venue where every instrument is a perpetual, the old lookup found no match even for USDC and the request was rejected with 400 VALIDATION_ERROR — carrying the venue's "Asset not found" text on older builds, "unknown asset" since v0.1.0.
  • Both endpoints may now return 503 EXCHANGE_ERROR ("temporarily unavailable, retry later"), declared on both operations. Retry after a moment; the transfer is never sent to the venue in that state.

2026-09-08 — v1.14.1​

Changed​

  • Exchange error messages. When the exchange rejects a request, the message in the error response is now taken from a fixed catalogue of DLTM wording instead of forwarding the venue's own text. code values are unchanged; only the wording is stabilised, so clients should match on code, not on message.
  • POST /customers/{customer_code}/orders — the 400 response now documents how code distinguishes the cause: VALIDATION_ERROR for a malformed request, ORDER_REJECTED when a well-formed order is refused by the exchange (invalid price or quantity for the instrument, notional above the venue maximum), INSUFFICIENT_FUNDS when collateral is insufficient. A 404 NOT_FOUND is returned when the symbol does not exist or is not available to the broker.
  • DELETE /customers/{customer_code}/orders/{reference_id} — cancelling an order the exchange does not know returns 404 NOT_FOUND instead of a 400.
  • POST /customers/{code}/deposits and POST /customers/{code}/withdrawals — amount accepts at most 8 decimal places. More precise amounts are rejected up front with 400 VALIDATION_ERROR ("amount must have at most 8 decimal places") instead of an opaque exchange error. An unknown asset is likewise rejected with 400 VALIDATION_ERROR.
  • PUT /customers/{code}/fee-profile and PUT /customers/{code}/fee-profile/{symbol} — at least one of maker_fee / taker_fee is required, and each supplied value must be a decimal number in basis points (an optional leading - marks a rebate).
  • POST /customers/{customer_code}/orders — limit_price is required and must be positive for limit orders and is rejected on market orders; trigger, stop-limit, take-profit and stop-loss prices must be positive decimals. Violations return 400 VALIDATION_ERROR before the order reaches the exchange.
  • balance-changes channel — pending events are no longer pushed. Each movement is now emitted exactly once, on its transition into a terminal status (completed or cancelled), so consumers that dedup by code always receive the terminal event. The pending state itself is unchanged and remains visible in the withdrawal create response and on GET /customers/transactions.

Fixed​

  • leverage on POST /customers/{customer_code}/orders — the documented pattern now matches the enforced range 1–13 (it previously allowed values up to 999 that were rejected at runtime).
  • reference_id path parameter on GET/DELETE /customers/{customer_code}/orders/{reference_id} — now carries the same constraints as the field on order creation (max 40 characters, ^[a-zA-Z0-9_\-]+$).

2026-08-31 — v1.14.0​

Added​

  • symbol on GET /trades and GET /customers/{code}/trades — filter the list by instrument symbol (exact match on the internal form, e.g. BTCUSDC_PERP). An unknown symbol returns an empty page.

Changed​

  • Nonce rules for REST and WebSocket authentication. X-Nonce (and the WS authenticate nonce) is now validated as a nanosecond Unix timestamp against server time instead of against the last value we accepted. A nonce is accepted when it is no more than 30s behind server time, no more than 5s ahead of it, and has not been used before by the same public key.

    Nonces no longer have to increase. Requests may be signed concurrently and arrive in any order, so no cross-request coordination is needed — previously a request that overtook another in flight was rejected even though both nonces were valid.

    Action required if your nonce is not a real timestamp. A persisted counter, a small increasing integer (1, 2, …) or a millisecond timestamp used to be accepted and now returns 401 on every request. Send time.Now().UnixNano() (or your language's equivalent) and keep the host clock synced; the Date response header carries server time to compare against.

    REST and WebSocket still share one nonce namespace per public key, so do not reuse a value across the two.

Fixed​

  • GET /trades — unsupported query parameters were silently ignored, returning the unfiltered broker-wide list. They are now rejected with 400 VALIDATION_ERROR naming the offending parameter and the supported set. In particular, start/end senders get pointed at from/to, the range names both trade endpoints use.

2026-08-19 — v1.13.0​

Added​

  • GET /trades — broker-scoped trade list across all of the broker's customers, with the same filters and pagination as GET /customers/{code}/trades, plus an optional customer_code query parameter to scope results to a single customer.
  • customer_code on trade records — returned by GET /trades, GET /customers/{code}/trades and GET /customers/{code}/trades/{trade_code}.
  • sort query parameter on GET /customers/{code}/trades — asc (oldest-first) or desc (newest-first, the default). Results were previously always newest-first regardless of any supplied sort value; the default is unchanged.

Changed​

  • A withdrawal is now reported as pending before it transitions to completed — in the create response, on GET /customers/transactions and on the balance-changes channel. This is the lifecycle deposits have always had. Its code is stable across the whole lifecycle, so a balance-changes event still joins back to its ledger entry by code.

Fixed​

  • POST /customers/{code}/withdrawals — withdrawals were failing with 400 VALIDATION_ERROR after an upstream rule change; they succeed again.

2026-08-03 — v1.12.3​

Fixed​

  • GET /customers/transactions and GET /customers/{code}/transactions — created_from/created_to now also accept milliseconds since epoch, the format every other date-range query parameter (start/end) uses, in addition to the previously accepted RFC 3339 date-time. Millisecond values were previously rejected with 400 VALIDATION_ERROR.
  • GET /customers/{code}/trades — from/to likewise now accept milliseconds since epoch in addition to an RFC 3339 date-time.

2026-07-23 — v1.12.2​

Fixed​

  • POST /customers/{code}/withdrawals and POST /customers/{code}/deposits — a transfer the exchange rejects due to a business rule violation (e.g. an amount above the available balance) now returns 400 with INSUFFICIENT_FUNDS (or VALIDATION_ERROR) instead of 500 "unexpected exchange error".
  • POST /customers/{code}/withdrawals and POST /customers/{code}/deposits — the amount field is now validated against the documented format and must be strictly positive. Zero-amount transfers were previously accepted (201) and created a meaningless transfer record; they are now rejected with 400 VALIDATION_ERROR.

2026-07-16 — v1.12.1​

Fixed​

  • balance-changes WebSocket channel — mark-to-market movements now carry transaction_type: "settlement", matching the type the same ledger row carries on GET /transactions. They were previously published under the internal name remarking; clients matching on remarking must switch to settlement.

2026-07-13 — v1.12.0​

Added​

  • GET /v1/customers/transactions — broker-scoped transaction ledger across all customers, with an optional customer_code query parameter to scope to one customer. Responses cover all transaction types (deposit, withdrawal, trade, settlement, funding) newest-first with cursor pagination (after/before, populated response_metadata); liquidation/auto-deleveraging fills are reported as plain trade rows and status is limited to pending/completed/cancelled.
  • balance-changes WebSocket channel — transaction_type gains liquidation and auto_deleveraging: forced fills (exchange liquidation engine / auto-deleveraging) are now labeled instead of arriving as plain trade movements. Same message shape as trade (total_amount = realised_pnl - fee).
  • balance-changes WebSocket channel — symbol is now also present on trade-family movements (trade, liquidation, auto_deleveraging), so a movement can be tied to its instrument/position without joining against the trades feed. Forced fills carry no reference_id (there is no client order), making symbol the position reference.

Changed​

  • GET /v1/customers/{code}/transactions is served from the same internal ledger as the new broker-scoped endpoint: identical row semantics, and the previously ignored after/before cursor parameters now paginate.

Deprecated​

  • GET /v1/customers/{code}/transactions — superseded by GET /v1/customers/transactions?customer_code={code}. The endpoint keeps working but will be removed in a future major version.

2026-07-09 — v1.11.0​

Changed​

  • Breaking — GET /v1/customers/{code}/transactions: on trade, liquidation and auto_deleveraging rows, total_amount (and its deprecated duplicate amount) is now the fill's net cash movement (realised_pnl − fee, absolute value, at full precision) — the same quantity the balance-changes WebSocket publishes as total_amount. It was previously the traded notional (price × quantity) rounded to 2 decimals. direction on those rows now carries the sign of the cash movement (previously it encoded the order side: buy = credit, sell = debit).
  • GET /v1/customers/{code}/transactions no longer returns a second trade row per fill sourced from the exchange's collateral journal; each fill appears exactly once.

Added​

  • GET /v1/customers/{code}/transactions — total_amount, the authoritative movement field going forward, named after its balance-changes WebSocket counterpart (signed there; absolute here, with direction carrying the sign). It currently duplicates amount, which is deprecated.
  • GET /v1/customers/{code}/transactions — new fields mirroring the balance-changes WebSocket: realised_pnl and reference_id (the client-supplied order id) on trade-type rows, mark_price on settlement rows, and funding_rate on funding rows.
  • max_leverage on GET /v1/customers/{code}/margin-profile — the maximum leverage a customer may select when placing an order. Orders requesting a higher leverage are rejected with a validation error, as are in-range values with no exact integer-percent initial-margin representation (currently only leverage 11). Currently a platform-wide constant ("13"); per-customer limits are planned.

Deprecated​

  • amount on transaction rows — replaced by total_amount (currently an exact duplicate); it will be removed in an upcoming release. Migrate to total_amount.
  • max_leverage on GET /v1/instruments rows — never populated (always an empty string); it will be removed in an upcoming release. Use max_leverage on GET /v1/customers/{code}/margin-profile instead, which carries the leverage cap enforced at order entry.

2026-07-06 — v1.10.0​

Added​

  • PUT /v1/customers/{code}/fee-profile — set a customer's fee schedule across all instruments in one request. Accepts the same maker_fee and taker_fee body as the per-instrument PUT /v1/customers/{code}/fee-profile/{symbol} endpoint (both in basis points) and returns the customer's full FeeProfileList with the updated schedule for every instrument.

2026-06-17 — v1.9.0​

Added​

  • WebSocket balance-changes — real-time balance ledger movements, the push counterpart of GET /v1/customers/{code}/transactions. Private, broker-scoped channel: an authenticated connection receives movements for every customer under its broker. Each message's code equals the matching /transactions row's code (and the /trades/trades WS code for trade and trade-fee movements), so a balance change can be joined back to its ledger entry. balance and balance_as_of carry the resulting end balance and are present only on completed events. Subscribe with {"op": "subscribe", "args": ["balance-changes"]}.

2026-06-15 — v1.8.0​

Changed​

  • Breaking — the order status enum was reworked on both the REST order schema and the WebSocket orders channel:
    • Added partially_filled — an order that has been partially executed and is still resting on the book. Such orders previously reported open.
    • Removed canceled_partially_filled — an order canceled after a partial execution now reports canceled. Inspect filled_amount (and filled_quote) to tell whether any quantity filled before the cancellation.

2026-05-25 — v1.7.0​

Added​

  • cash_amount on order entry (POST /v1/customers/{customer_code}/orders) — place market and limit orders by notional value in the quote currency instead of contract size. Mutually exclusive with amount (exactly one is required for market and limit orders), must be a positive number, and requires time_in_force: IOC (the default when omitted; FOK and GTC are rejected). Not supported for stop/take-profit order types. The notional is resolved into a contract quantity and filled immediately.
  • cash_amount and filled_quote on order responses — both the REST order schema and the WebSocket orders channel now echo the requested notional (cash_amount, present only for cash orders) and report the cumulative filled notional (filled_quote).
  • PUT /v1/customers/{code}/fee-profile/{symbol} — set a customer's per-instrument fee schedule. Accepts maker_fee and taker_fee in the request body and returns the updated FeeProfileItem for the symbol.

Changed​

  • amount is no longer required on order entry — supply cash_amount instead for market/limit orders. It remains required for all other order types.
  • FeeProfileItem.maker_fee and taker_fee (returned by GET /v1/customers/{code}/fee-profile) are documented as basis points (e.g. "2" = 0.02%). The previous "decimal fraction" wording and the "0.0002"/"0.0005" examples were inaccurate; the underlying values have always been bps. Behaviour is unchanged.

2026-05-11 — v1.6.0​

Added​

  • WebSocket orderbook-stream:{symbol} — multi-level orderbook snapshot followed by atomic update batches. Each update message carries one or more changes ({action: new|change|delete, type: bid|ask, price, amount}) that should be applied together. amount is "0" on delete. Public channel, no authentication required.
  • WebSocket market-trades:{symbol} — real-time anonymous trade tape. One envelope per trade. Field set mirrors the REST PublicTrade schema returned by GET /v1/market-trades, so the same parser can be used for both. Public channel, no authentication required.
  • WebSocket error codes: ORDERBOOK_STREAM_UPSTREAM_ERROR, ORDERBOOK_STREAM_UPSTREAM_REJECT, MARKET_TRADES_UPSTREAM_ERROR, MARKET_TRADES_UPSTREAM_REJECT. Channel-scoped — only the affected subscription is torn down, not the whole client connection. Clients should re-issue subscribe to recover.

Changed​

  • WebSocket documentation reorganised into three sections in the sidebar: Connection, Public channels, and Private channels.

2026-05-05 — v1.5.0​

Breaking changes​

  • Removed GET /v1/instruments/{symbol}. Use GET /v1/instruments?symbol={symbol} instead — it returns the same instrument inside a single-record list (or an empty list / 400 for unknown symbols, mirroring the /v1/tickers filter semantics).
  • Removed GET /v1/instruments/{symbol}/leverage_brackets. The single-tier brackets it returned were derivable from the init_margin_req, maint_margin_req, and max_leverage fields already present on each instrument record from GET /v1/instruments.
  • Instrument.status enum changed: removed settled; added post_only (limit orders only, matching disabled) and trade_halt (new orders rejected). The same enum applies to the status query filter on /v1/instruments.

Added​

  • symbol query parameter on /v1/instruments. Supports the same pipe-separated literal and * wildcard syntax as /v1/tickers.

2026-05-04 — v1.4.0​

Breaking changes​

  • Removed GET /v1/customers/{code}/margin-requirements/{symbol} — subsumed by the extended margin-profile response (see Added below).

Added​

  • Optional leverage field on POST /v1/customers/{code}/orders — positive integer (^[1-9][0-9]*$) in [1, 13]. The broker translates it to a per-instrument initial-margin override on the subaccount before placing the order. The override persists across subsequent orders on the same instrument until a different leverage is supplied.
  • instrument_margins array on the MarginProfile response (GET /v1/customers/{code}/margin-profile) — one entry per instrument with a non-default initial margin, each carrying symbol, leverage, and initial_margin.

Changed​

  • Margin / IM string fields (MarginProfile.initial_margin / maintenance_margin, MarginProfile.instrument_margins[].initial_margin, Instrument.init_margin_req / maint_margin_req, LeverageBracket.init_margin_req / maint_margin_req) are now formatted with 2-decimal precision (e.g. "0.12") instead of 6-decimal ("0.120000"). Numerically equivalent — clients parsing as decimal are unaffected.

2026-04-29 — v1.3.0​

Added​

  • Wildcard support in symbol, underlying, and quote_currency filters on /v1/tickers and /v1/instruments — values may use * (matches any sequence of characters, including empty). Matching is case-insensitive and anchored to the full string. Existing exact and pipe-separated values continue to work.

2026-04-09 — v1.2.0​

Breaking changes​

  • Removed stop_price field from all order schemas — replaced by trigger_price, stop_loss_price, take_profit_price, and trigger_price_type
  • Removed stop_price from amend order request
  • stop_loss and take_profit order types now use trigger_price instead of the removed stop_price

Added​

  • New order types: stop_loss_limit, take_profit_limit, take_profit_stop_loss
  • trigger_price field — trigger price for stop_loss, stop_loss_limit, take_profit, take_profit_limit orders
  • stop_loss_price field — stop-loss price for take_profit_stop_loss combo orders
  • take_profit_price field — take-profit price for take_profit_stop_loss combo orders
  • trigger_price_type field — trade_price (for stop/TP types) or mark_price (for combo type)
  • Mechanical types stop_market and stop_limit in order responses and WebSocket updates
  • Advanced orders now included in list open orders response
  • Server-side validation of required fields and allowed trigger price types per order type

Fixed​

  • WebSocket orders and trades topics no longer require a symbol suffix — aligned implementation with documented per-account behavior

2026-04-03 — v1.1.0​

Breaking changes​

  • All enum values are now snake_case (e.g. MarginCall → margin_call, StopLoss → stop_loss, Buy → buy)
  • Renamed /customers/{code}/leverage to /customers/{code}/margin-profile
  • Removed /customers/{code}/risk-limits endpoint
  • Removed realised_pnl from account summary response
  • Removed expiry field and Future/Option from instrument types
  • Removed type query parameter from /instruments
  • Transaction types changed: removed funding_payment, realised_pnl, adjustment; added settlement, funding, auto_deleveraging

Added​

  • type field on customer trades (trade, liquidation, auto_deleveraging)
  • initial_margin and maintenance_margin fields on margin profile
  • settlement and funding transaction types from collateral adjustments
  • before pagination parameter for historical trades

Fixed​

  • Historical trades now default to sort=asc and validate required from/to parameters
  • Currency fields now correctly show USDC instead of USD
  • WebSocket trades topic is now per-account (no symbol suffix required)

2026-03-31 — v1.0.0​

Added​

  • Initial public release