Release 20260223
Changes relevant to 3rd-party integrators between releases
release-all-20260212 (12 Feb 2026) and release-all-20260223 (23 Feb 2026).
Summary
| Action | Change Type | Breaking? | Action Required |
|---|---|---|---|
| All order-returning actions | Response change | Possibly | Migrate from flat orderId/id to composite order object |
getBalanceUpdates | Response change | Yes | Update id parsing from number to string; handle new TRANSFER filter |
createSubaccount | Behavior change | Yes | Delegated signers can no longer call this action |
transferCollateral | Behavior change | Possibly | Failed transfers now return HTTP 400/500 instead of 200 |
getTrades | Response change | Possibly | clientOrderId field removed (was always empty) |
| All actions | Behavior change | Possibly | 500 error responses no longer include internal error details |
getTransfers | New endpoint | No | None |
cancelAllOrders (REST) | New endpoint | No | None |
cancelAllOrders | Behavior change | No | None |
| Trade subscription (WS) | Response change | No | None |
| Internal refactoring | Internal only | No | None |
All changes are available on both REST (POST /v1/trade) and WebSocket (/v1/ws/trade) unless otherwise noted.
getBalanceUpdates — response change
The id field type has changed and the TRANSFER action filter has been added.
What you need to do
- Update your
idfield parsing: it is now a JSON string (e.g.,"12345") instead of a JSON number (e.g.,12345). - If you filter by
actionFilter, you may now also use"TRANSFER"to retrieve inter-subaccount transfer records. - If you validate error messages for invalid
actionFiltervalues, update your matching.
Response field changes
| Field | Before | After | Notes |
|---|---|---|---|
id | int64 (JSON number) | string (JSON string) | Breaking: 12345 → "12345" |
action | "DEPOSIT" or "WITHDRAWAL" | "DEPOSIT", "WITHDRAWAL", or "TRANSFER" | New value |
fromSubAccountId | (not present) | string (optional) | New field; present for TRANSFER records |
Error message change
- Before:
"actionFilter must be 'DEPOSIT', 'WITHDRAWAL', or comma-separated combination" - After:
"invalid action filter, available filters: DEPOSIT, WITHDRAWAL, TRANSFER"
Response example
{
"status": "ok",
"response": {
"balanceUpdates": [
{
"id": "42",
"subAccountId": "1867542890123456789",
"action": "TRANSFER",
"status": "success",
"amount": "100.00",
"collateral": "USDT",
"timestamp": 1740300000000,
"fromSubAccountId": "1867542890123456788",
"toSubAccountId": "1867542890123456789"
}
]
}
}Errors
| HTTP Status | Condition | Error Code |
|---|---|---|
400 | Invalid actionFilter value | VALIDATION_ERROR |
400 | Invalid limit or offset | VALIDATION_ERROR |
500 | Internal service failure | INTERNAL_ERROR |
createSubaccount — behavior change
The createSubaccount action now requires the wallet owner.
What you need to do
If you use delegated signers to create subaccounts, move this call to the wallet owner. Delegated signers will now receive an authorization error.
What changed
createSubaccount was added to the RequiresOwner() list alongside transferCollateral, withdrawCollateral, removeAllDelegatedSigners, and updateSubAccountName. Requests signed by a delegated signer are now rejected.
Composite OrderId — response change across all order-returning actions
Every API response that previously returned order identifiers as flat strings now returns a composite object alongside the deprecated flat field. This affects: placeOrders, cancelOrders, cancelAllOrders, modifyOrder, getOpenOrders, getOrderHistory, getTrades, getPositions, and all WebSocket order/trade event notifications.
What you need to do
Migrate from the deprecated flat fields to the new composite order object. The deprecated fields remain present during the transition period but will be removed in a future release.
| Endpoint | Old field (deprecated) | New field |
|---|---|---|
placeOrders | statuses[].resting.id / filled.id / canceled.id | .resting.order / .filled.order / .canceled.order |
cancelOrders | statuses[].canceled.id | statuses[].canceled.order |
cancelAllOrders | [].orderId | [].order |
modifyOrder | orderId | order |
getOpenOrders | orderId, takeProfitOrderId, stopLossOrderId | order, takeProfitOrder, stopLossOrder |
getOrderHistory | orderId | order |
getTrades | orderId | order |
getPositions | takeProfitOrderIds, stopLossOrderIds | takeProfitOrders, stopLossOrders |
| WS order/trade events | orderId | order |
New OrderId object shape
{ "venueId": "1948058938469519360", "clientId": "my-order-1" }| Field | Type | Description |
|---|---|---|
venueId | string | System-generated order identifier (definitive) |
clientId | string | Optional client-provided identifier; empty string if not set |
Notes
getOpenOrders: TP/SL fields are now nullable. When no TP/SL is attached,takeProfitOrderandtakeProfitOrderIdare omitted entirely (previouslytakeProfitOrderIdcould be"0").getPositions: TP/SL are now arrays of composite objects alongside the deprecated arrays of flat strings.
transferCollateral — behavior change
Failed transfers now return proper HTTP error responses instead of HTTP 200 with failure status in the body.
What you need to do
Handle HTTP 400/500 for transfer failures. Successful transfers still return HTTP 200.
Error code mapping:
| Error Code | HTTP Status |
|---|---|
ASSET_NOT_FOUND, INSUFFICIENT_MARGIN, INVALID_VALUE, VALIDATION_ERROR | 400 |
| All others | 500 |
getTrades — response change
The clientOrderId field has been removed. It was always an empty string. The client order ID is now available via the composite order.clientId field.
500 error response sanitization — behavior change across all actions
HTTP 500 error responses no longer include raw internal error messages or the details map. Messages are now generic (e.g., "Failed to cancel all orders"). If you parse message text or details.error, update your matching.
Non-breaking changes
-
getTransfers— New endpoint. Query inter-subaccount transfer history with optional filtering bysymbol,startTime,endTime, and pagination vialimit/offset. Returns{ transfers: [...], total: N }. Available on both REST and WebSocket. -
cancelAllOrders(REST) — Now registered on the RESTPOST /v1/tradehandler in addition to WebSocket. -
cancelAllOrders— Wildcard support. Passsymbols: ["*"]to cancel orders across all markets. The"*"must be the sole element. -
Trade subscription (WebSocket) — Notifications now include
"channel": "trade"and"timestamp"(milliseconds) at the top level. -
Internal refactoring — Error codes and categories moved to
lib/core/status_codes. No wire-format impact.
Release 20260206
Changes relevant to 3rd party integrators across releases release-all-20260205 (Feb 5) and release-all-20260206 (Feb 6).
Summary
| Action | Change Type | Breaking? | Action Required |
|---|---|---|---|
getSubAccounts | New endpoint | No | Implement if needed |
removeAllDelegatedSigners | New endpoint | No | Implement if needed; requires new EIP-712 type |
withdrawCollateral | Behavior change | Possibly | Update error message parsing if you match on fee errors |
getTrades, placeOrders | Internal only | No | None. Wire format unchanged. |
All changes are available on both REST (POST /v1/trade) and WebSocket (/v1/ws/trade).
getSubAccounts — new endpoint
Returns all subaccounts under the authenticated user's master account. Works for both owners and delegates. Each subaccount includes its delegated signers and fee rate tier.
What you need to do
Sign using the existing SubAccountAction EIP-712 type (same as getSubAccount) with action set to "getSubAccounts". No nonce required (read-only action).
Request
Identical to getSubAccount. The only difference is params.action:
{ "params": { "action": "getSubAccounts" } }No additional parameters.
Response
Returns { subAccounts: [...] } where each element has the same shape as an existing getSubAccount response, plus two additional fields: feeRates and delegatedSigners.
getSubAccount):
| Field | Type | Description |
|---|---|---|
feeRates | FeeRateInfo | Fee tier for this subaccount |
delegatedSigners | DelegatedSigner[] | All delegates authorized on this subaccount. Empty [] if none exist. |
FeeRateInfo fields:
| Field | Type | Example | Description |
|---|---|---|---|
makerFeeRate | string | "0.0002" | Maker fee rate (decimal) |
takerFeeRate | string | "0.0005" | Taker fee rate (decimal) |
tierName | string | "Regular User" | Fee tier name |
DelegatedSigner fields:
| Field | Type | Example | Description |
|---|---|---|---|
subAccountId | string | "2011391943438766080" | Subaccount this delegation applies to |
walletAddress | string | "0xDelegateWalletAddress" | Delegate's Ethereum address |
permissions | string[] | ["trading"] | Granted permissions |
expiresAt | number | null | 1738800000000 | Expiry as unix ms, or null for no expiration |
Errors
| HTTP Status | Condition | Error Code |
|---|---|---|
400 | Missing subaccountId | VALIDATION_ERROR |
500 | Internal service failure | INTERNAL_ERROR |
removeAllDelegatedSigners — new endpoint
Atomically removes all delegated signers from a subaccount. Owner-only: delegates receive a 403 error.
What you need to do
Add the new RemoveAllDelegatedSigners EIP-712 primary type to your signing code. This is a write action, so nonce is required.
RemoveAllDelegatedSigners: [
{ name: "subAccountId", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "expiresAfter", type: "uint256" }
]Use the standard EIP-712 domain (same as all other actions). Sign with primaryType: "RemoveAllDelegatedSigners".
Request
params contains only action — no additional action-specific fields:
{
"params": { "action": "removeAllDelegatedSigners" },
"subaccountId": "123",
"nonce": 1738700000000,
"signature": { "v": 27, "r": "0x...", "s": "0x..." },
"expiresAfter": 1738700060000
}| Field | Type | Required | Description |
|---|---|---|---|
params.action | string | Yes | Must be "removeAllDelegatedSigners" |
subaccountId | string | Yes | Subaccount to remove all delegates from |
nonce | number | Yes | Timestamp in ms (replay protection) |
signature | object | Yes | EIP-712 signature { v, r, s } |
expiresAfter | number | No | Request expiry as unix ms. 0 = no expiry |
Response
{
"status": "ok",
"response": {
"subAccountId": "123",
"removedSigners": ["0xDelegateAddr1", "0xDelegateAddr2"]
}
}| Field | Type | Description |
|---|---|---|
subAccountId | string | The subaccount from which signers were removed |
removedSigners | string[] | Wallet addresses of removed delegates. Empty [] if none |
Errors
| HTTP Status | Condition | Error Code |
|---|---|---|
400 | Invalid format or validation failure | VALIDATION_ERROR |
403 | Caller is a delegate, not the owner | VALIDATION_ERROR |
404 | Subaccount not found | NOT_FOUND |
500 | Internal service failure | INTERNAL_ERROR |
withdrawCollateral — behavior change
What you need to do
If you parse or pattern-match on withdrawal fee error messages, update your matching. Request and response schemas are unchanged.
What changed
Withdrawal fees are now per-asset in native units instead of a flat $5 USD. The minimum withdrawal amount must exceed the asset-specific fee.
| Asset | Fee | Approximate USD equivalent |
|---|---|---|
USDT | 5 | $5 |
WETH | 0.0017 | ~$5 at ~$3,000/ETH |
BTC | 0.00005 | ~$5 at ~$100,000/BTC |
Error message format change
- Before:
"withdrawal amount must be greater than $5 fee" - After:
"withdrawal amount must be greater than {fee} {asset} fee"
Examples of the new format:
"withdrawal amount must be greater than 5 USDT fee""withdrawal amount must be greater than 0.0017 WETH fee""withdrawal amount must be greater than 0.00005 BTC fee"
Net amount
The on-chain withdrawal amount remains amount - fee. If you display estimated net amounts to users, note that the fee now varies by asset. Withdrawal fees are not yet exposed via a public API endpoint.