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_idis: 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_rateis the rate for one settlement period, so hourly rates are correspondingly smaller (e.g.0.0000125per hour where it was0.0001per 8 hours). GET /instruments—funding_intervalis"1h"for perpetuals (was"8h").GET /funding_ratesreturns one record per hourly settlement.POST /customers/{code}/depositsandPOST /customers/{code}/withdrawals—amountnow accepts up to 10 decimal places (previously 8), matching the exchange's transfer precision. More precise amounts are still rejected up front with400 VALIDATION_ERROR. If the exchange limits an asset to fewer decimals, the request fails with the same error code.
Fixed
liquidationsWebSocket channel —margin_deficitnow 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 stillnullwhen the risk state isnormal.GET /customers/{code}/orders/{reference_id}right after a successfulDELETEno longer returnsopenfor 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 itsexpire_time. It was alwaysnullthere, while the order list showed it. Once the order is filled, canceled or expired, this endpoint still reportsexpire_time: null; the order history has it.GET /customers/{code}/orders/{reference_id}no longer fails with500 EXCHANGE_ERRORwhen the reference has been used for more than one order. It returns the open order, or the one updated last when none is open;DELETEon the same reference acts on the open order, as before. With none open, its409 ORDER_NOT_CANCELLABLEnames the status of the orderGETreturns.GET /customers/{code}/orders/{reference_id}now returns404 NOT_FOUNDfor the reference of an order that was refused at placement (POST /customers/{code}/ordersanswered400withINSUFFICIENT_FUNDSorORDER_REJECTED), instead of failing with500and an empty body. A refused order is not created as an order, so there is nothing to read back here; the reason is in the400response. If the reference is then used for a new order, it returns that order, which could also fail with500before.GET /tickers,GET /tickers/{symbol}, thetickerWebSocket channel andGET /funding_rates/{symbol}—next_funding_timeis 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.tickerWebSocket channel —next_funding_timeis now documented on the snapshot; it was already sent but missing from the docs.lockedonGET /customers/{code}/balancesis now reported at the same precision astotalandavailable; it was rounded to two decimals, sototalandavailable + lockedcould differ by a fraction of a cent.GET /customers/{code}/positionswith asymbolorsidefilter that matches nothing now returns"records": []; it returned"records": null. The same applies toGET /customers/{code}/balances?asset=with no matching asset.POST /customers/{code}/orders,GET /customers/{code}/ordersandGET /customers/{code}/orders/{reference_id}— a GTD order now reportstime_in_force: "GTD"and itsexpire_time. It was reported asGTCwith noexpire_time.expire_timeis now always present on an order andnullfor every other time in force.- A GTD order that reaches its
expire_timenow reads back withstatus: "expired"(reason: "EXPIRED"). It was reported ascanceled. DELETE /customers/{code}/orders/{reference_id}on an order that is already filled, canceled, expired or rejected now returns409 ORDER_NOT_CANCELLABLE. It returned404 NOT_FOUND, althoughGETstill returned the order.ordersWebSocket channel —expire_timeis 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 fullOrdermodel, which is what the endpoint already returns (type,post_only,expire_time,reason,reference_id,created,updatedwere missing from the docs).POST /customers/{code}/ordersnow rejects a conditional order whose trigger is already reached or on the wrong side of the market, with400 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 asopenand 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 forstop_loss,stop_loss_limit,take_profitandtake_profit_limit, and against the mark price for both legs oftake_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_priceandunrealised_pnlonGET /customers/{code}/positions,GET /customers/{code}/positions/{position_code}and thepositionsWebSocket channel are no longer padded to 16 decimal places.amountis rendered with at least the instrument'slot_sizedecimals, prices with at least itstick_sizedecimals, andunrealised_pnlwith 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}/balancesnow reportstotalas 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 inGET /customers/{code}/transactions. It was previously rounded to 6 decimals.lockedis the initial margin currently reserved on the account (for an open positionamount × entry_price × initial_margin_requirement, which does not move with mark price); it was previously rounded to 2 decimals.availableistotal − locked, soavailable + locked + pendingequalstotalexactly, as documented. All three are rendered at the same precision ("0.0000000000"when flat).total_equityonGET /customers/{code}/account-summaryand on themarginWebSocket channel now includes unrealised PnL, as documented ("balance + unrealised PnL"). It previously reported the collateral balance only.used_marginandunrealised_pnlare unchanged;available_marginis collateral − used margin on the same exact ledger basis as the balances endpoint (differs from before only beyond the 6th decimal); the deprecatedmargin_ratiokeeps its definition (used margin over the collateral balance).total_equity,available_margin,used_marginandunrealised_pnlonGET /customers/{code}/account-summaryand themarginWebSocket 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_ratioandleverage_ratiostay at 4 decimals.updated_atonGET /customers/{code}/account-summary(andtimestampon themarginandpositionschannels) 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
leverageonPOST /customers/{code}/ordersnow 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 abovemax_leverageare rejected with400and the order is not placed.max_leverageonGET /customers/{code}/margin-profilenow reflects the highest leverage the customer may actually select (currently"2"); it was a fixed"13".GET /funding_ratesandGET /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_ratespagination cursors issued before this release are no longer valid and are rejected with400 VALIDATION_ERROR; restart the listing from the first page. A cursor must be used with the samestartandendit was issued for.GET /funding_rates/{symbol}returns404 NOT_FOUNDwhen 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-profilewith 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.400when the leverage is out of range, not available, or would put an open position into margin call. -
Optional
customer_iponPOST /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_ratioonGET /customers/{code}/account-summaryand on themarginWebSocket channel. The venue-reported(collateral + unrealised PnL) / position notionalas a decimal fraction (e.g.5.1316= 513%). This is the value the venue compares againstmargin_call_thresholdandliquidation_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_ratesaccepts abeforecursor for backward pagination, andresponse_metadatanow also carriesstart_cursorandhas_previous, like the other paginated lists. -
GET /funding_ratesdocuments its404 NOT_FOUNDresponse for an unknown symbol. -
notificationsWebSocket channel (private, broker-scoped). A generic channel for customer notifications, distinguished bytypewith the type-specific fields in a nesteddataobject. 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, withcustomer_code,type,symboland 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:leverageis the leverage of the assigned profile (nearest integer to1 / initial_margin), andmaintenance_margin/liquidation_thresholdare now defined by the assigned profile rather than being platform-fixed.instrument_marginsis normally empty. The endpoint may return404(unknown customer) and502(margin profile not yet provisioned at the exchange).leverageon order entry accepts any positive integer string (^[1-9][0-9]*$); the effective bound is the customer'smax_leverage.- Order
created/updatedare 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_ratioon theliquidationsWebSocket channel is rounded down to 4 decimal places, the same rendering as the newmargin-channel and account-summary field. Previously the venue's 8-decimal value was passed through unmodified.funding_timeonGET /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 asfunding_ratewithfunding_timeset to the start of the running interval.predicted_rateisnullin the short window right after a settlement, until the first preliminary rate of the new interval is received, andnullwhen no preliminary has been received in the running interval.GET /funding_rates: a window withstart >= endreturns an empty page; a negativestartor anendbeyond year 9999 is rejected with400 VALIDATION_ERROR.stop_loss,stop_loss_limit,take_profit,take_profit_limitandtake_profit_stop_lossorders are now placed through the exchange's unified order entry, the same path asmarketandlimitorders. Visible effects on these order types:-
created/updatedare now populated (previously empty), so they sort and paginate by time inGET /customers/{code}/orderslike every other order instead of always floating to the top. -
time_in_forceis now returned:IOCforstop_loss,take_profitandtake_profit_stop_loss,GTCforstop_loss_limitandtake_profit_limit. OnPOST /customers/{code}/ordersthe field may be supplied for these types but must match those values; anything else is rejected with400 VALIDATION_ERROR. -
A price that the order type does not use is now rejected with
400 VALIDATION_ERRORinstead of being silently dropped. This coverslimit_priceon astop_loss,take_profitortake_profit_stop_loss,trigger_priceon amarket,limitortake_profit_stop_loss,take_profit_price/stop_loss_priceon anything buttake_profit_stop_loss, andtrigger_price_typeonmarketorlimit. 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 reportstypestop_market/stop_limitandtrigger_pricefor stop-loss and take-profit orders (previously inferred asmarket/limit), andtake_profit_price/stop_loss_pricefortake_profit_stop_loss. -
Like
marketandlimitorders, these orders are readable throughGET .../orders/{reference_id}immediately after the create response for as long as they are open, and appear in the order list immediately. -
The
ordersWebSocket channel now emits updates for stop and take-profit orders (placement, cancel, trigger, fills), withorder_typestop_market/stop_limit/take_profit_stop_loss,trigger_priceandtrigger_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}andDELETE .../orders/{reference_id}. Specifically:GET .../orders/{reference_id}now returnscreatedandupdated. 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 returnscash_amountandfilled_quotefor orders placed withcash_amount. Same cause: the exchange returns them on every endpoint, we only read them from some.customer_codeis now returned by every order endpoint.DELETE .../orders/{reference_id}and each record inGET .../ordersnever included it, andPOST /customers/{code}/ordersonly echoed it if the request body happened to carry acustomer_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.reasonis now populated on cancelled and expired orders (for exampleUSER_INITIATEDwhen you cancelled it,TIME_IN_FORCEwhen an IOC order expired unfilled). It was alwaysnull. This includes the response toDELETE .../orders/{reference_id}itself, which reportsUSER_INITIATED— cancelling an order and then reading it back give the samestatusand the samereason. 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_onlyis now echoed onPOST /customers/{code}/orders. Placing a maker-only order previously came backpost_only: false. Reads still reportfalsefor every order — the exchange does not report the flag back on any read endpoint.
leverage_ratioon theliquidationsWebSocket channel was documented astotal notional / equity. The venue reports the inverse,(collateral + unrealised PnL) / position notional, matching the newmargin-channel field.GET /funding_rates/{symbol}no longer stalls and fails with500 EXCHANGE_ERRORon 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}returnedstatus: openfor the cancelledmarket,limit,stop_lossandstop_loss_limitorder it echoed back. It now returnscanceled.GET /customers/{code}/orderswithout astatusfilter mixed open orders with filled and cancelled take-profit orders. It is now open-only, the same asstatus=open(the only supported value).GET /ohlc—interval=1his accepted again. The parameter's allowed values listed60mbut not1h, even though the example said1hand the venue serves both as the same hourly candle; once request validation became strict,1hwas rejected with400 VALIDATION_ERROR.60mkeeps working.
Deprecated
- Sending
POST /customers/{code}/orderswithoutcustomer_ipis deprecated. The field stays optional for now but will become required in a future release. Start sending it on every order. margin_ratioonGET /customers/{code}/account-summaryand on themarginandliquidationsWebSocket channels. It is derived locally asused_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. Useleverage_ratio.
Known limitations
-
createdis the time of the last event on an order, not its placement time. The exchange exposes no creation timestamp on any order endpoint, so bothcreatedandupdatedare filled from the one timestamp a record carries. For a resting order that has had no events the two are the same instant andcreatedis exact; for an order that was later filled or cancelled,createdreports 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 aGETof that same order afterwards reports the cancel instant. Verified on UAT 2026-09-16 — the two were 4s apart.statusandreasonagree; only the timestamps differ. -
typeon the response toPOST /customers/{code}/ordersis 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). Astop_lossorder therefore reads back asstop_market. This is the only field that differs by endpoint.
2026-09-11 — v1.15.0
Added
positionsWebSocket channel (positions:{customer_code}, private). Pushes a full snapshot of the customer's open positions every ~5 seconds, in the same shape asGET /customers/{code}/positions, so clients no longer need to poll the REST endpoint for live position updates.initial_margin_requirement,margin_call_thresholdandliquidation_thresholdonGET /customers/{code}/account-summaryand on themarginWebSocket 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_thresholdonGET /customers/{code}/margin-profile— the liquidation margin level as a decimal fraction, alongsideinitial_marginandmaintenance_margin.
Changed
marginWebSocket channel updates are now pushed every ~5 seconds (previously ~10). Themarginandpositionschannels are refreshed together, so for a given customer both always describe the same instant.
Fixed
POST /customers/{code}/depositsandPOST /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 forUSDCand the request was rejected with400 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
messagein the error response is now taken from a fixed catalogue of DLTM wording instead of forwarding the venue's own text.codevalues are unchanged; only the wording is stabilised, so clients should match oncode, not onmessage. POST /customers/{customer_code}/orders— the400response now documents howcodedistinguishes the cause:VALIDATION_ERRORfor a malformed request,ORDER_REJECTEDwhen a well-formed order is refused by the exchange (invalid price or quantity for the instrument, notional above the venue maximum),INSUFFICIENT_FUNDSwhen collateral is insufficient. A404 NOT_FOUNDis 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 returns404 NOT_FOUNDinstead of a400.POST /customers/{code}/depositsandPOST /customers/{code}/withdrawals—amountaccepts at most 8 decimal places. More precise amounts are rejected up front with400 VALIDATION_ERROR("amount must have at most 8 decimal places") instead of an opaque exchange error. An unknownassetis likewise rejected with400 VALIDATION_ERROR.PUT /customers/{code}/fee-profileandPUT /customers/{code}/fee-profile/{symbol}— at least one ofmaker_fee/taker_feeis 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_priceis 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 return400 VALIDATION_ERRORbefore the order reaches the exchange.balance-changeschannel —pendingevents are no longer pushed. Each movement is now emitted exactly once, on its transition into a terminal status (completedorcancelled), so consumers that dedup bycodealways receive the terminal event. Thependingstate itself is unchanged and remains visible in the withdrawal create response and onGET /customers/transactions.
Fixed
leverageonPOST /customers/{customer_code}/orders— the documented pattern now matches the enforced range1–13(it previously allowed values up to999that were rejected at runtime).reference_idpath parameter onGET/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
symbolonGET /tradesandGET /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 WSauthenticatenonce) 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 returns401on every request. Sendtime.Now().UnixNano()(or your language's equivalent) and keep the host clock synced; theDateresponse 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 with400 VALIDATION_ERRORnaming the offending parameter and the supported set. In particular,start/endsenders get pointed atfrom/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 asGET /customers/{code}/trades, plus an optionalcustomer_codequery parameter to scope results to a single customer.customer_codeon trade records — returned byGET /trades,GET /customers/{code}/tradesandGET /customers/{code}/trades/{trade_code}.sortquery parameter onGET /customers/{code}/trades—asc(oldest-first) ordesc(newest-first, the default). Results were previously always newest-first regardless of any suppliedsortvalue; the default is unchanged.
Changed
- A withdrawal is now reported as
pendingbefore it transitions tocompleted— in the create response, onGET /customers/transactionsand on thebalance-changeschannel. This is the lifecycle deposits have always had. Itscodeis stable across the whole lifecycle, so abalance-changesevent still joins back to its ledger entry bycode.
Fixed
POST /customers/{code}/withdrawals— withdrawals were failing with400 VALIDATION_ERRORafter an upstream rule change; they succeed again.
2026-08-03 — v1.12.3
Fixed
GET /customers/transactionsandGET /customers/{code}/transactions—created_from/created_tonow 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 with400 VALIDATION_ERROR.GET /customers/{code}/trades—from/tolikewise now accept milliseconds since epoch in addition to an RFC 3339 date-time.
2026-07-23 — v1.12.2
Fixed
POST /customers/{code}/withdrawalsandPOST /customers/{code}/deposits— a transfer the exchange rejects due to a business rule violation (e.g. an amount above the available balance) now returns400withINSUFFICIENT_FUNDS(orVALIDATION_ERROR) instead of500 "unexpected exchange error".POST /customers/{code}/withdrawalsandPOST /customers/{code}/deposits— theamountfield 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 with400 VALIDATION_ERROR.
2026-07-16 — v1.12.1
Fixed
balance-changesWebSocket channel — mark-to-market movements now carrytransaction_type: "settlement", matching thetypethe same ledger row carries onGET /transactions. They were previously published under the internal nameremarking; clients matching onremarkingmust switch tosettlement.
2026-07-13 — v1.12.0
Added
GET /v1/customers/transactions— broker-scoped transaction ledger across all customers, with an optionalcustomer_codequery parameter to scope to one customer. Responses cover all transaction types (deposit,withdrawal,trade,settlement,funding) newest-first with cursor pagination (after/before, populatedresponse_metadata); liquidation/auto-deleveraging fills are reported as plaintraderows andstatusis limited topending/completed/cancelled.balance-changesWebSocket channel —transaction_typegainsliquidationandauto_deleveraging: forced fills (exchange liquidation engine / auto-deleveraging) are now labeled instead of arriving as plaintrademovements. Same message shape astrade(total_amount = realised_pnl - fee).balance-changesWebSocket channel —symbolis now also present on trade-family movements (trade,liquidation,auto_deleveraging), so a movement can be tied to its instrument/position without joining against thetradesfeed. Forced fills carry noreference_id(there is no client order), makingsymbolthe position reference.
Changed
GET /v1/customers/{code}/transactionsis served from the same internal ledger as the new broker-scoped endpoint: identical row semantics, and the previously ignoredafter/beforecursor parameters now paginate.
Deprecated
GET /v1/customers/{code}/transactions— superseded byGET /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: ontrade,liquidationandauto_deleveragingrows,total_amount(and its deprecated duplicateamount) is now the fill's net cash movement (realised_pnl−fee, absolute value, at full precision) — the same quantity thebalance-changesWebSocket publishes astotal_amount. It was previously the traded notional (price × quantity) rounded to 2 decimals.directionon those rows now carries the sign of the cash movement (previously it encoded the order side: buy =credit, sell =debit). GET /v1/customers/{code}/transactionsno longer returns a secondtraderow 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 itsbalance-changesWebSocket counterpart (signed there; absolute here, withdirectioncarrying the sign). It currently duplicatesamount, which is deprecated.GET /v1/customers/{code}/transactions— new fields mirroring thebalance-changesWebSocket:realised_pnlandreference_id(the client-supplied order id) on trade-type rows,mark_priceonsettlementrows, andfunding_rateonfundingrows.max_leverageonGET /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
amounton transaction rows — replaced bytotal_amount(currently an exact duplicate); it will be removed in an upcoming release. Migrate tototal_amount.max_leverageonGET /v1/instrumentsrows — never populated (always an empty string); it will be removed in an upcoming release. Usemax_leverageonGET /v1/customers/{code}/margin-profileinstead, 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 samemaker_feeandtaker_feebody as the per-instrumentPUT /v1/customers/{code}/fee-profile/{symbol}endpoint (both in basis points) and returns the customer's fullFeeProfileListwith the updated schedule for every instrument.
2026-06-17 — v1.9.0
Added
- WebSocket
balance-changes— real-time balance ledger movements, the push counterpart ofGET /v1/customers/{code}/transactions. Private, broker-scoped channel: an authenticated connection receives movements for every customer under its broker. Each message'scodeequals the matching/transactionsrow'scode(and the/trades/tradesWScodefor trade and trade-fee movements), so a balance change can be joined back to its ledger entry.balanceandbalance_as_ofcarry the resulting end balance and are present only oncompletedevents. Subscribe with{"op": "subscribe", "args": ["balance-changes"]}.
2026-06-15 — v1.8.0
Changed
- Breaking — the order
statusenum was reworked on both the REST order schema and the WebSocketorderschannel:- Added
partially_filled— an order that has been partially executed and is still resting on the book. Such orders previously reportedopen. - Removed
canceled_partially_filled— an order canceled after a partial execution now reportscanceled. Inspectfilled_amount(andfilled_quote) to tell whether any quantity filled before the cancellation.
- Added
2026-05-25 — v1.7.0
Added
cash_amounton 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 withamount(exactly one is required for market and limit orders), must be a positive number, and requirestime_in_force: IOC(the default when omitted;FOKandGTCare rejected). Not supported for stop/take-profit order types. The notional is resolved into a contract quantity and filled immediately.cash_amountandfilled_quoteon order responses — both the REST order schema and the WebSocketorderschannel 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. Acceptsmaker_feeandtaker_feein the request body and returns the updatedFeeProfileItemfor the symbol.
Changed
amountis no longer required on order entry — supplycash_amountinstead for market/limit orders. It remains required for all other order types.FeeProfileItem.maker_feeandtaker_fee(returned byGET /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 morechanges({action: new|change|delete, type: bid|ask, price, amount}) that should be applied together.amountis"0"ondelete. Public channel, no authentication required. - WebSocket
market-trades:{symbol}— real-time anonymous trade tape. One envelope per trade. Field set mirrors the RESTPublicTradeschema returned byGET /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-issuesubscribeto 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}. UseGET /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/tickersfilter semantics). - Removed
GET /v1/instruments/{symbol}/leverage_brackets. The single-tier brackets it returned were derivable from theinit_margin_req,maint_margin_req, andmax_leveragefields already present on each instrument record fromGET /v1/instruments. Instrument.statusenum changed: removedsettled; addedpost_only(limit orders only, matching disabled) andtrade_halt(new orders rejected). The same enum applies to thestatusquery filter on/v1/instruments.
Added
symbolquery 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
leveragefield onPOST /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_marginsarray on theMarginProfileresponse (GET /v1/customers/{code}/margin-profile) — one entry per instrument with a non-default initial margin, each carryingsymbol,leverage, andinitial_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, andquote_currencyfilters on/v1/tickersand/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_pricefield from all order schemas — replaced bytrigger_price,stop_loss_price,take_profit_price, andtrigger_price_type - Removed
stop_pricefrom amend order request stop_lossandtake_profitorder types now usetrigger_priceinstead of the removedstop_price
Added
- New order types:
stop_loss_limit,take_profit_limit,take_profit_stop_loss trigger_pricefield — trigger price for stop_loss, stop_loss_limit, take_profit, take_profit_limit ordersstop_loss_pricefield — stop-loss price for take_profit_stop_loss combo orderstake_profit_pricefield — take-profit price for take_profit_stop_loss combo orderstrigger_price_typefield —trade_price(for stop/TP types) ormark_price(for combo type)- Mechanical types
stop_marketandstop_limitin 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
ordersandtradestopics 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}/leverageto/customers/{code}/margin-profile - Removed
/customers/{code}/risk-limitsendpoint - Removed
realised_pnlfrom account summary response - Removed
expiryfield andFuture/Optionfrom instrument types - Removed
typequery parameter from/instruments - Transaction types changed: removed
funding_payment,realised_pnl,adjustment; addedsettlement,funding,auto_deleveraging
Added
typefield on customer trades (trade,liquidation,auto_deleveraging)initial_marginandmaintenance_marginfields on margin profilesettlementandfundingtransaction types from collateral adjustmentsbeforepagination parameter for historical trades
Fixed
- Historical trades now default to
sort=ascand validate requiredfrom/toparameters - Currency fields now correctly show
USDCinstead ofUSD - WebSocket
tradestopic is now per-account (no symbol suffix required)
2026-03-31 — v1.0.0
Added
- Initial public release