Testnet runs
testnet-v1.17.14. This page lists API spec changes as they reach testnet. Changes to existing endpoints can show up here before production; new endpoints are listed once they are live on mainnet. Building against production? See the mainnet changelog.testnet-v1.17.14
Stock dividends in corporate actions.GET /v1/corporateActions and GET /v1/corporateActions/{market_id} list stock dividends as STOCK_DIVIDEND, with a ratioNumerator / ratioDenominator once the ratio is published, and keep each action for 60 days after it takes effect.testnet-v1.17.6
API-key signing for transfers.POST /v1/transfer also accepts the X-API-Key / X-Timestamp / X-Signature headers in place of the body signature. X-Signature is an Ed25519 signature over compact, key-sorted JSON: {"ad":"<address>","af":<from>,"at":<to>,"ct":<X-Timestamp>,"n":"<nonce>","op":6,"q":<amount>,"v":1}, with the address in lowercase hex and the amount in quote quantums. A key scoped to one subaccount can only transfer out of that subaccount.Corporate actions. GET /v1/corporateActions lists each market’s splits, reverse splits, and dividends, keyed by marketId, from announcement until after they take effect, with exDate, effectiveTime, ratio or amount, status, and a version that increments on every change. GET /v1/corporateActions/{market_id} returns one market’s list, and GET /v1/markets adds upcomingCorporateActions per market.testnet-v1.17.0
TP/SL placement checks the mark price.POST /v1/placeOrder and POST /v1/batchPlaceOrders now check a TP/SL stopPrice against the mark price, the same price that triggers the order, and reject a trigger already crossed with InvalidRequest.testnet-v1.16.10
Entry stop orders. ASTOP_LOSS trigger on POST /v1/placeOrder can set reduceOnly: false to open a position in the trigger direction when it fires. TAKE_PROFIT orders and every TPSL leg in a batch grouping still require reduceOnly: true.testnet-v1.16.8
Withdrawal maximum.POST /v1/withdraw accepts up to $1,000,000 USDG (1000000000000000 quote quantums) per withdrawal and returns 400 above it.testnet-v1.15.10
Single TPSL orders onplaceOrder. POST /v1/placeOrder with tpslType set places one standalone trigger order instead of returning 501. It requires stopPrice and reduceOnly: true, and the response echoes stopPrice and tpslType. Use POST /v1/batchPlaceOrders groupings to bundle legs or bind them to an entry order.testnet-v1.15.8
Engine timestamp on order book deltas. Everyl2OrderbookUpdates delta carries timestamp, the Unix microseconds when the matching engine processed the event.testnet-v1.15.7
Throttledbbo and oraclePrices subscriptions. Add throttleMs (100, 250, 500, 1000 or 2000) to a bbo or oraclePrices subscribe to receive at most one frame per window. Other channels reject it.testnet-v1.15.0
Order and fill history defaults to the last 30 days.GET /v1/orders and GET /v1/fills without from return the 30 days before to. Pass from for older rows; an orders status filter of only OPEN / UNTRIGGERED still returns every open order.WebSocket close code 4401. A connection that repeatedly fails API-key authentication with no success in between is closed with code 4401.Share multiplier on spot assets. GET /v1/spotAssets returns multiplier, the underlying shares per token. It is informational and reads "1" until a value is published.testnet-v1.14.7
Recent closed orders in theorders snapshot cover 28 days. The orders channel snapshot lists every open order plus up to 100 orders closed in the last 28 days. Use GET /v1/orders for older history.Max tier in the commission schedule. GET /v1/commissionrates lists level 6 (Max).testnet-v1.14.5
Fee tier level and discount on account stats.tradingFeeTier on GET /v1/account/stats returns the account’s level and feeDiscountPpm, the referral fee discount in ppm of the fee rate, instead of maker and taker rates.testnet-v1.14.4
New invite-code conflict message.POST /v1/affiliate/redeemInvite by a caller who can already trade returns 409 with you already have access — invite code was not consumed.testnet-v1.13.7
Full-precisionavgFillPrice on the account channel. Order events on the account channel carry the full-precision average fill price, which can have more decimals than the market’s tick size.Deposit refund handle. Deposit rows on GET /v1/accountTransferUpdates and the accountTransferUpdates channel carry rootchainQueueIndex, the root-chain index used to claim a refund for a rejected deposit.testnet-v1.11.6
Rejected transfers on theaccountTransferUpdates channel. Every accountTransferUpdates event carries status (APPLIED or REJECTED), with the reason code in rejectReason on a rejection; REST GET /v1/accountTransferUpdates adds the generic rejection status REJECTED_UNKNOWN.testnet-v1.11.8
Liquidation fee rate.MarketInfo on GET /v1/markets and the markets channel add liquidationFeePpm, the market’s liquidation fee in parts per million of fill notional (10000 = 1%), null when the market has none.testnet-v1.13.6-4
Invite codes can’t become referral codes.POST /v1/affiliate/createCode and modifyCode return 409 for a string that is not a valid referral code, such as an existing invite code.testnet-v1.13.4
Global lending attributes. NewGET /v1/lendingAttributes returns the exchange-wide lending limits (per-account borrow cap, reserve ratio, deposit caps, loan-settlement bps) and the live borrow rate, borrow index and utilization. No authentication required.Invite codes only grant access. POST /v1/affiliate/redeemInvite by a caller who can already trade returns 409 (account is already whitelisted — invite code was not consumed) and the code is not spent. An invite code never moves an existing referral binding. A redemption that would create a cycle in the referral chain returns 409 (circular referral detected).Registering without trading access spends an invite code. On POST /v1/affiliate/registerAffiliate, a caller who cannot trade yet can only register by spending one of the code owner’s unused invite codes, which grants trading access and shows as used in the owner’s invite list. The request returns 403 when the owner does not allow it or invite codes are not available for the caller, 409 when the owner has none left, and 503 when invite codes are unavailable. A caller who can already trade spends no code. A registration cycle returns 409 (circular referral detected).testnet-v1.13.3
Referral acknowledgements clarified. A202 from POST /v1/affiliate/createCode or registerAffiliate means no confirmation arrived within the server-side timeout. It is not a rejection, but the referral service can still drop the request, so poll to confirm. A 200 from redeemInvite also covers a caller already bound to the code’s owner.testnet-v1.13.1
Invite-code earning rule.GET /v1/affiliate/inviteCodes documents that codes are earned on the caller’s own lifetime perps volume plus the all-time volume of their direct referees; deeper levels do not count.Redemption links by address. POST /v1/affiliate/redeemInvite links the caller to the code owner’s address in the referral tree, and the owner no longer needs a referral code.Registration grants access. A successful POST /v1/affiliate/registerAffiliate grants the caller trading access if they do not already have it. A new 502 means the access grant or the submit failed: nothing was registered, and the request is safe to retry.testnet-v1.13.0
Referral totals cover the whole downline.GET /v1/affiliate/info adds traderCount, the number of referees in the T1/T2/T3 downline. Its referred-volume and commission totals now cover that same downline, perps only, and equal the sums of the GET /v1/affiliate/referees rows.Referee sub-trees. GET /v1/affiliate/referees rows add volumeAllTime, each referee’s all-time perps volume, and a new parent query parameter pages the part of the downline under one referee.Self-redemption. Redeeming your own invite code on POST /v1/affiliate/redeemInvite grants trading access and spends the code, but creates no referral binding.testnet-v1.11.7
Order error coverage. The reference now documents errors these endpoints already returned:- A
502witherrorType: TransmissiononPOST /v1/placeOrder,batchPlaceOrders,cancelOrder,batchCancelOrders,cancelAllOrders,modifyOrderandbatchModifyOrders: the request was valid but never reached the matching engine, so nothing changed and the identical request is safe to retry. RejectionReasonaddsORDER_NOT_FOUND(a cancel matched no live order),OPEN_ORDER_CAP_EXCEEDED(the account is at its open-order maximum) andEXPIRED(a resting good-til-time order passed itsgoodTilTime).- The
ordersWebSocket channel’srejectionReasonis typed as the same enum, with a meaning for each value. DUPLICATE_CLIENT_IDnow points tomodifyOrderfor changing a live order in place.
testnet-v1.11.6
Open-interest cap documented under its wire name. The operator-set open-interest cap onGET /v1/markets and the markets WebSocket channel is openInterestCapNotional, which is the name the API has been serving; the reference previously called it openInterestCap. It is null when no cap is active, and the OPEN_INTEREST_CAP_EXCEEDED rejection description now references it.Spec catch-up with fields already served. The reference now documents lastTradePrice on MarketInfo (the most recently matched trade price, omitted until a trade is observed), address and accountIndex on order rows (accountIndex omitted when 0), oracleEpoch (Unix microseconds) and markEpochNanos on oraclePrices rows, and the oraclePrices subscribe reply, which carries the latest price for every market with epoch: 0. The account WebSocket snapshot is described as its own shape — lastSequenceId and accountEquity in place of REST’s sequenceNumber and equity, with no pending-transfer fields. The markets snapshot is a map keyed by marketId, where REST returns an array.Spot-asset transfers: deposit cap and loan-settlement proceeds. Account transfer updates add the status REJECTED_GLOBAL_DEPOSIT_CAP_EXCEEDED (a spot-asset deposit would breach the exchange-wide spot deposit cap) and the type LOAN_SETTLEMENT_PAYMENT, which records the sale proceeds of an executed POST /v1/settleLoan credited to the account (followed by an INTEREST_SETTLEMENT event when interest had accrued).testnet-v1.11.0
Item counts on list responses. REST list responses carry a new integertotal: the number of items in the returned page, not the size of the full result set matching the query. It appears on GET /v1/trades, /v1/candles, /v1/orders, /v1/openOrders, /v1/fills, /v1/positions, /v1/funding, /v1/fundingRates, /v1/accountTransferUpdates, /v1/spotFills, /v1/spotPositions, and /v1/interest. It is REST-only: the positions and spotPositions WebSocket snapshots don’t carry it.Spot asset contract addresses. GET /v1/spotAssets now populates contractId with each asset’s ERC20 contract address — the token the bridge accepts for deposits and pays out for withdrawals. It is omitted for an asset with no configured contract.settleLoanResults WebSocket channel. The new per-account settleLoanResults channel carries one APPLIED or REJECTED frame per POST /v1/settleLoan request, mirroring the REST response body and correlatable by requestId. It is the only WebSocket surface for a rejected settlement. Stream-only: no snapshot on subscribe.testnet-v1.10.13
Per-market dead man’s switch.POST /v1/scheduleCancel (and the WebSocket scheduleCancel action) accepts an optional marketId. Omit it for the existing account-wide switch, which cancels across every market; set it to arm a switch that cancels only that market’s orders. A per-market switch is independent of the account-wide switch and of other per-market switches on the same subaccount, and the response echoes marketId and marketDisplayName when the switch is per-market. A wallet may hold only a bounded number of armed switches across all its subaccounts and markets: arming one beyond that limit returns 429, while refreshing an already-armed switch is always allowed.API keys scoped to all subaccounts. POST /v1/createApiKey and POST /v1/revokeApiKey accept accountIndex: 255, which scopes one key to every subaccount of the address instead of a single index (0–9); values 10–254 are rejected. Like any non-zero index, 255 must be signed with the accountIndex-bearing EIP-712 type. Key rows on GET /v1/apiKeys and the create/revoke responses carry a new allSubaccounts boolean — use it as the scope discriminator; accountIndex is present only on single-subaccount keys.testnet-v1.10.12
Rename a referral code. NewPOST /v1/affiliate/modifyCode renames the authenticated affiliate’s code in place. Existing referees stay attached, since the referral link is by account rather than by code string. The affiliate must already hold a code (404 otherwise), and the new string must be free: 409 when it equals the current code, is owned by another affiliate, or was previously retired.testnet-v1.10.9
Spec correction: affiliatevolume30d. The spec for GET /v1/affiliate/info now names the affiliate’s own trailing-30-day notional fill volume field volume30d, matching what the API already returned; it was previously documented as rollingVolume. No client change is needed. The separate rollingVolume on the /v1/accountStats reads is a different field.testnet-v1.10.7
Look up a single trader on the leaderboard.GET /v1/leaderboard now accepts an address to return just that trader’s row — its volume, fees, and realized PnL over the window, plus its rank on the sortBy column wherever it sits on the board (not only within the top-N). Every leaderboard row also carries that rank field, computed on the active sortBy column in the same query so a row’s columns stay mutually consistent. When address is set, limit is ignored and an address that did not trade in the window returns an empty entries list.testnet-v1.10.6
Spot fills — REST and WebSocket.GET /v1/spotFills returns an account’s spot fill history, newest-first — the seizure and loan-settle executions of the lending system (spot has no order book, so there is no maker/taker role or fee). Optional from/to (epoch microseconds) bound the window and optional accountIndex selects a subaccount. The new spotFills WebSocket channel streams the same executions live, with a snapshot of recent spot fills on subscribe (skip it with snapshot: false). No authentication required for the REST read.testnet-v1.10.5
DedicatedspotPositions WebSocket channel. spotPositions streams a single account’s spot collateral holdings on its own channel, so clients can follow collateral without subscribing to the full perp positions stream. The snapshot carries the open holdings keyed by spot asset id; each update carries one changed holding with its post-event size, event valuation price, and a reason (DEPOSIT, WITHDRAWAL, SEIZURE, RELEASE, TRANSFER, UNKNOWN). With this move the positions channel is now perp-only — spot collateral no longer rides in a discriminated section of positions.Breaking: spot position qty renamed to size. On the REST spot-position surfaces — GET /v1/spotPositions rows and the spot positions on GET /v1/account — the holding-amount field qty is renamed size, matching the perp position surfaces. Update any client reading the old field name.testnet-v1.10.4
Affiliates: all-time referred volume.GET /v1/affiliate/info adds totalReferredVolumeAllTime — the combined lifetime notional fill volume of all of the affiliate’s current direct (T1) referees, in quote quantums (1e9 = $1). Same definition as totalReferredVolume30d but with no time window. Reads 0 if the underlying rollup is momentarily unavailable.testnet-v1.10.3
Borrow-interest settlement history.GET /v1/interest returns an account’s borrow-interest settlements, newest-first — the lending analog of GET /v1/funding: where funding charges an open perpetual, interest charges an open loan. Optional from/to (epoch microseconds) bound the window; when omitted, to defaults to now and from to 30 days before to. No authentication required.testnet-v1.10.2
Get a fill by trade id.GET /v1/fill/{tradeId} returns the single fill your account owns for a given tradeId (pass ?address=, optional accountIndex) — the point-lookup companion to GET /v1/fills. No authentication required.testnet-v1.10.1
closedPnl now nets the fill fee. Across GET /v1/fills and the fills/account WebSocket rows, closedPnl is redefined as the cost-basis release (old borrowedCapital − new borrowedCapital − Δsize × price) minus the fill’s fee. Opening and same-direction add legs now report −fee (previously "0").Settle a loan by selling spot collateral. New POST /v1/settleLoan sells up to maxQuantity of a spot collateral asset, never below minPrice, and applies the proceeds to the account’s outstanding loan and accrued interest — the way out of a loan left after closing perp positions at a loss, without depositing external funds. The engine never sells more than the debt needs at the execution price, and partial fills are possible.New features
Spot collateral positions.GET /v1/spotPositions returns your open spot collateral positions, keyed by spot asset id — each row carrying the asset’s balance and its live oraclePrice (the USD price of the position’s price-source market, "0" while a market is briefly unpriced). The same oraclePrice field is added to the spot position rows on GET /v1/account. Two new error reasons, GetSpotPositionsFailed and SpotPositionsHandlerUnavailable, cover a failed read.Breaking changes
Spot collateral fields renamed tospotAssetId / spotAssetDisplayName. The spot-asset identifier is unified across REST and WebSocket onto one name. Anywhere a spot-collateral row or transfer previously carried assetId / asset (and, on spot position rows, spotMarketId / spotMarketDisplayName; on the withdraw response, assetDisplayName), it now carries spotAssetId and spotAssetDisplayName. This touches POST /v1/withdraw (request, response, and the SpotAssetWithdraw EIP-712 payload — the signed field is now spotAssetId), GET /v1/account (netDepositsByAsset and spot position rows), and account transfer updates over both REST and the accounts / accountTransferUpdates WebSocket channels. Update any client reading or signing the old field names.New features
Multi-asset spot collateral.GET /v1/spotAssets returns the assets accepted as multi-asset collateral for lending — not tradable order books — for example SPY-USD-SPOT, each with its LTV, liquidation factor, price-source market, and supply caps. Spot-asset deposits and withdrawals flow through POST /v1/withdraw with a new spotAssetId selecting the spot asset and a dedicated SpotAssetWithdraw EIP-712 payload; they can be rejected with the new statuses REJECTED_INVALID_MARKET, REJECTED_INSUFFICIENT_SPOT_BALANCE, and REJECTED_SPOT_POSITION_CAPACITY. GET /v1/account gains netDepositsByAsset, and account transfers add the types LENDING_DRAW, INTEREST_SETTLEMENT, SEIZURE_PAYMENT, and CLAIM_REPAYMENT.Lending rates WebSocket channel. The global lendingRates channel streams the exchange-wide borrow rate and cumulative borrow index — the lending analog of oraclePrices — emitting one frame per SOFR-driven rate segment with the annual borrow rate the segment opens at (ratePpm) and the global borrowIndex after the close.New features
Invite codes are switched on (testnet only).GET /v1/affiliate/inviteCodes and POST /v1/affiliate/redeemInvite — long present in the spec but answering 503 — now answer on testnet; mainnet still returns 503 {"error":"Invite codes not available yet"} and the mainnet changelog will announce that rollout. One invite code is earned per $1M of lifetime perps volume, and codes materialize lazily — a batch of newly earned codes may take more than one read to appear (materializationPending). Codes are bearer secrets that grant perps trading access, so the list read is the one signed affiliate read: full header triple, message X-Timestamp + inviteCodes. Redemption is single-use and atomic — two callers racing on one code get exactly one win and one 409.The same release settles redemption semantics for callers who are already referred: redeeming moves the attribution to the invite code’s owner only when the code is what grants access — a caller who can already trade and has a referrer gets 409 and the code is not spent. A move is forward-only (the previous affiliate keeps commission already earned and stops accruing) and is confirmed asynchronously, so that path answers 202 rather than 200. The redemption response also no longer names the code’s owner, and GET /v1/affiliate/myReferrer redacts the same address — a referral binding is readable without disclosing the wallet behind it.New features
Subaccounts across the WebSocket surface. Subscriptions to the account-scoped channels —account, positions, orders, userFills, funding, accountTransferUpdates, and accountAttributeUpdates — take an optional accountIndex (0–9). Omit it for subaccount 0, which is what every client written before the field existed keeps getting. It is part of subscription identity: each subaccount is its own subscription with its own snapshot and stream, so one connection may hold several, and unsubscribe must repeat the same accountIndex — omitting it drops only subaccount 0. Every frame of an account-scoped channel now echoes accountIndex, including when it is 0, on subscribed, unsubscribed, degraded, and channel_data alike; that is what makes several subaccounts of one address demultiplexable on a single connection, since id alone is the master address and identical across all of them. Addresses in id come back lowercase whatever spelling was sent, so compare them case-insensitively, and an encoded core account id may be used instead — it already carries its own subaccount index, and supplying accountIndex alongside it is allowed only if the two agree. Supplying accountIndex on a channel that is not account-scoped is an error rather than an ignored field, as is a value outside 0–9: an out-of-range subaccount can never carry traffic, so accepting one would return a permanently silent subscription.Updates
Per-account sequence numbers on positions and orders. Position and order rows carrysequenceNumber — the per-account sequence of the engine event that last wrote that row, the same counter as AccountUpdate.sequenceNumber — so a row can be ordered against account updates without inferring it from arrival order. The orders snapshot now carries lastSequenceId next to openOrders and recentClosedOrders, matching the positions snapshot, and streaming PositionUpdate envelopes stamp it too, so a client can tell exactly where its snapshot sits in the account’s event sequence and discard the updates it already contains. Both are WebSocket-side; REST omits sequenceNumber when unset.marginUsed reads by margin mode. On position rows the field now depends on the row’s marginMode. CROSS: the initial margin required at the current mark — |size| × mark × initial-margin fraction — so it moves as the mark moves. ISOLATED: the walled-off isolated-leg pool, which does not track IMR: it moves only when collateral actually moves, growing one-for-one with POST /v1/adjustIsolatedMargin. The full isolated-leg cash balance is derivable as marginUsed + borrowedCapital.Updates
New modify rejection reasonMODIFY_SIZE_ALREADY_FILLED. Under total-size modify semantics — where the size on a POST /v1/modifyOrder is the order’s new total size, filled quantity included — a new total at or below the quantity already filled leaves nothing to rest. The original order is canceled (a CANCELED update precedes the rejection) and the modify is rejected with this reason. Use POST /v1/cancelOrder when the intent is to stop the unfilled remainder.Updates
Market filter on the remaining reads and account channels. An optionalmarket — display ticker (BTC-USD, case-insensitive) or numeric market id (1) — now restricts GET /v1/prices, GET /v1/mids, GET /v1/positions, and GET /v1/leverages to a single market; an unresolvable value returns HTTP 400 rather than an unfiltered response. On prices and mids a filter naming an offline market returns an empty object, matching how the unfiltered response omits offline markets; an account with no position in the requested market returns empty positions, not a 404. The same filter is accepted on the WebSocket positions, orders, and userFills subscriptions, where it applies to the snapshot, the live frames, and the post-snapshot catch-up alike, so the baseline and the diffs always describe the same markets. It is part of subscription identity — one connection may hold several market-scoped views of the same account, and unsubscribe must repeat the market it subscribed with. Supplying market on any other channel is an error, not an ignored field. Frames on those three channels now carry market (the resolved ticker) so a client holding several views can demultiplex, and the subscribed / unsubscribed acks echo the filter. The WebSocket get RPCs take it too, on fills, orders, positions, leverages, mids, and prices.adjustIsolatedMargin is confirmed on the positions channel. Adding or removing isolated margin through POST /v1/adjustIsolatedMargin now emits a PositionUpdate on the positions channel, carrying the originating requestId so it can be correlated with the 202 ACK, and marginMode: ISOLATED. Every position row — snapshot and streaming — now includes marginMode (CROSS or ISOLATED), so the collateral mode of a position is readable without a REST round-trip.isolated and marginMode are always present on leverage entries. On the accountAttributeUpdates channel, type: "leverage" entries previously carried the margin mode only when a mode change was part of the same setLeverage request. Both fields are now always populated: in snapshot frames they reflect the persisted state, and in streaming frames the account’s margin mode after the triggering event, defaulting to CROSS if the mode lookup fails.New features
Adjust margin on an isolated position.POST /v1/adjustIsolatedMargin moves collateral between the cross bucket and an isolated-mode position’s leg. amount is a dollar decimal string — positive adds margin to the isolated leg, negative removes it, and zero returns HTTP 400; send dollars, not quote quantums. The (account, market) must already be in isolated mode with an open position. Like the other engine-confirmed writes it carries a lifecycle status separate from the HTTP code: APPLIED (200), ACK (202, forwarded but unconfirmed — watch the positions channel), or REJECTED (422) with a rejectReason of UNKNOWN_MARKET, INVALID_AMOUNT, NOT_ISOLATED, NO_OPEN_POSITION, or UNDERCOLLATERALIZED. The response echoes newIsolatedMarginQuoteBalance, also in dollars.Dead man’s switch. POST /v1/scheduleCancel arms, refreshes, or disarms a per-subaccount deadline; if it passes without a refresh the gateway fires cancelAllOrders for that subaccount. Pass an absolute epoch-microsecond time to arm or refresh, and omit it (or send null) to disarm. Lead time must be between 5 seconds and 5 minutes, and successful auto-fires are capped at 10 per UTC day per subaccount — past which the endpoint returns HTTP 429. A 503 means the switch is not armed (cancel path disabled or store unavailable), so retry rather than assuming coverage.Updates
netDeposits semantics. The field on GET /v1/account and the accounts channel is documented as lifetime net deposits — cumulative inflows minus outflows across every applied account transfer, including deposits, withdrawals, and internal and self transfers between your own account indices, with rejected transfers excluded. Unlike equity and freeCollateral it never moves with oracle prices; only a transfer changes it. Subtract it from equity for all-time PnL, on the same basis GET /v1/portfolio uses.Updates
status and market filters on order history. GET /v1/orders and GET /v1/openOrders accept an optional status alongside market, which separates orders resting on the book (OPEN) from TPSLs parked on the untriggered book (UNTRIGGERED). Values are OPEN, UNTRIGGERED, FILLED, CANCELED, REJECTED, LIQUIDATED, and ADL, case-insensitive; status accepts either a JSON array (["OPEN","CANCELED"]) or a comma-separated string ("OPEN,CANCELED"), and an unrecognized value returns HTTP 400. It matches an order’s current status, not any status it held earlier. Both filters apply before the row cap, so a filtered page still carries up to limit orders. The same filters are available on the WebSocket orders RPC, where they apply to both the openOrders and recentClosedOrders halves of the result.from/to on funding are microseconds. The bounds on GET /v1/funding and GET /v1/fundingRates are epoch microseconds, matching the unit of the timestamp field each response reports — so a value read off one page is a valid bound for the next. The spec previously described them as epoch milliseconds; a millisecond value is now far below the accepted minimum.WebSocket error codes completed. Ten codes the gateway already returned were missing from the published enum: TimestampReused, ModifiesArrayEmpty, ModifiesArrayTooLarge, RateLimited, and AddressNotOnWhitelist on post, and MarketOffline, GetLeveragesFailed, GetRateLimitFailed, LeverageHandlerUnavailable, and RateLimitHandlerUnavailable on get. No behavior change — clients exhaustively matching on the enum can now recognize all of them.New features
Batch order modification.POST /v1/batchModifyOrders reprices or resizes up to 100 open orders in one request. Every row must share the same (address, accountIndex) — a batch is scoped to a single subaccount — and each row carries its own signature over the same typed canonical modify payload (op 3) a standalone POST /v1/modifyOrder signs, so the signed bytes are identical either way. Like the other batch routes it authenticates per element rather than with a verified envelope signature, and failures are per-row: a row rejected for validation or signature reasons comes back with status: ERROR and an error message while the remaining rows still reach the matching engine. The same operation is available over WebSocket.Isolated margin mode. POST /v1/setLeverage accepts an optional isolated flag that sets the margin mode for a (account, market) alongside the leverage — true for isolated, false for cross, omitted to leave the mode unchanged. Switching cross → isolated is rejected with HAS_OPEN_POSITION when a position is already open in that market; isolated → cross and no-op changes are always accepted. GET /v1/leverages now returns the margin mode (isolated / marginMode) next to the effective leverage for every market, and the accountAttributeUpdates channel folds the margin-mode outcome into the same entry via marginModeStatus and marginModeRejectReason, including on the leverageReject entry emitted when the engine refuses a request.Updates
Modify an order by client id.ModifyOrderRequest accepts clientId as an alternative to orderId, resolved engine-side against the account’s client-id index — so an order can be modified before the place response carrying its server-generated id has arrived. Supply exactly one: setting both is rejected, and so is setting neither.side filter on order and fill history. GET /v1/orders and GET /v1/fills accept an optional case-insensitive side (BUY / SELL) to restrict results to a single trade direction. Omit it to return both sides; an unknown value returns HTTP 400.Snapshot size control on userFills. Subscribing to the userFills channel accepts an optional nFills (1–500, default 500) that bounds the initial snapshot. It is ignored on other channels, and live channel_data frames are unaffected.Updates
Market filter on funding payments.GET /v1/funding accepts an optional market to restrict results to a single market, given as either the display name (e.g. BTC-USD, case-insensitive) or the numeric market id. Omit it to return every market; an unresolvable value returns HTTP 400.New features
Scoped API keys and API-key-signed withdrawals.GET /v1/apiKeys entries carry an optional permissions array describing scopes granted beyond the default. Every key can read and trade; the withdraw scope additionally authorizes POST /v1/withdraw using the X-API-Key / X-Timestamp / X-Signature header triple with an Ed25519 signature over the ordersign WithdrawV1 typed canonical payload (op 5), in which case the body signature field is ignored. The key must be bound to exactly the requested (ethereumAddress, accountIndex). Replay protection matches the wallet-signed mode: the X-Timestamp drift window plus a single-use nonce per (ethereumAddress, accountIndex), with HTTP 409 on reuse. The scope is grantable only through the operator’s provisioning flow — keys created via the public POST /v1/createApiKey are always trade-only and are rejected with HTTP 403. The original wallet-signed EIP-712 mode is unchanged, and withdraw-to-self remains the only supported destination on both modes.Updates
GET /v1/apiKeys spans every subaccount by default. GET /v1/apiKeys has no default accountIndex — omitting it lists the keys of every subaccount, each entry tagged with the accountIndex it belongs to, so a listing can be grouped client-side without one request per subaccount. Pass accountIndex to scope the listing to a single subaccount. Unlike other account-scoped reads, an omitted value is not the same as accountIndex=0, and a non-integer or out-of-range value returns HTTP 400 rather than silently falling back to 0. The response is all-or-nothing across subaccounts: an unreadable subaccount surfaces as HTTP 500 rather than a short list that would look like a revoked key.Updates
Market and role filters on order and fill history.GET /v1/orders and GET /v1/fills accept an optional market, and GET /v1/fills additionally accepts a case-insensitive role to restrict results to a single liquidity role. Market filters across the API — including GET /v1/markets — now accept either the display name (case-insensitive) or the numeric market id, and an unresolvable value returns HTTP 400.Multiple orderbook aggregations per market on one connection. A connection may subscribe to the l2Orderbook channel more than once for the same market as long as each subscribe uses a distinct (sigFigs, roundStep) pair. subscribed, channel_data, and unsubscribed frames echo those fields — omitted for full-precision views — so a client holding several views of one market can demux them. Unsubscribing must repeat the same pair to drop one view without affecting the others.New features
API-key replay protection & subaccount scoping.POST /v1/createApiKey and POST /v1/revokeApiKey accept an optional nonce (single-use per (address, accountIndex)) for replay protection and an accountIndex (0–9) that scopes the key to a subaccount. Both are bound into the EIP-712 signature: a non-zero accountIndex must use the new accountIndex-bearing CreateApiKey/RevokeApiKey type, and including a nonce requires the combined type that binds both. Send the nonce as nanoseconds since the Unix epoch (accepted within −48h/+24h; a legacy opaque UUID also works); a replayed nonce returns HTTP 409. Index 0 still accepts the original index-less type and the legacy EIP-191 fallback during the migration window.Updates
New order-rejection reasonPOSITION_SIZE_CAP_EXCEEDED. Orders, modifies, or fills that would exceed a market’s per-market position notional cap are rejected with POSITION_SIZE_CAP_EXCEEDED — distinct from UNDERCOLLATERALIZED, since a fully funded account can still hit it.Breaking changes
Time-window bounds are now microseconds. Thefrom/to query parameters on GET /v1/openOrders, GET /v1/orders, GET /v1/fills, GET /v1/trades, and GET /v1/accountTransferUpdates are now epoch microseconds, matching the createdAt / updatedAt / timestamp precision these endpoints already report — so a value read from one page is a valid bound for the next with no conversion. Millisecond- or second-scale values are rejected with HTTP 400 (the server requires at least 1e14); a millisecond bound read as microseconds lands in 1970 and silently returns nothing. Clients that previously sent milliseconds must update.Updates
GET /v1/openOrders is now bounded and paginated. GET /v1/openOrders accepts limit (1–1000; default and maximum both 1000, larger values silently clamped) plus from / to (epoch microseconds) to bound the window on createdAt. Responses stay newest-first; page backward by sending the oldest createdAt you received as the next request’s to, deduplicating by orderId since the inclusive bound overlaps page edges. An account can hold up to 10,000 live orders, so more than one page may be needed.Breaking changes
Deprecated*Bps fee-tier aliases removed. The makerFeeBps/takerFeeBps fields on GET /v1/feetiers, GET /v1/account/stats, and feeTierConfig entries on exchangeAttributeUpdates — along with the spot schedule’s maker_fee_bps/taker_fee_bps — are gone, completing the ppm rename announced in testnet-v1.2.31. Use makerFeePpm/takerFeePpm (and maker_fee_ppm/taker_fee_ppm); the values are unchanged.New features
Orderbook price aggregation.GET /v1/l2OrderBook/{market} and the l2Orderbook channel accept an optional sigFigs (2–5) to bucket price levels to that many significant figures, plus roundStep (1, 2, or 5) to refine the finest bucket when sigFigs is 5. Bids round down and asks round up so the displayed spread is never tighter than the true book, merged sizes are summed, and aggregation is applied before nLevels truncation. Omit sigFigs for full precision.Updates
Order-size bounds on markets.GET /v1/markets and the markets channel add minOrderSize and maxOrderSize (base-asset units). maxOrderSize is a per-order security gate rather than a dust filter — reduce-only orders are not exempt.Updates
Explicit RTH-transition events on marketAttributes. ThemarketAttributes channel’s boundEvent adds exitRth / enterRth for Regular Trading Hours boundary crossings, and is now present on every delta entry — only snapshots omit it. boundSide is likewise always present on deltas (null on RTH transitions and VWAP seals).Portfolio PnL rebased per timeframe. pnlHistory on GET /v1/portfolio now rebases each timeframe so its first point is 0 — every point is the trading + funding PnL earned since the start of that timeframe, and lifetime PnL is the last point of the all timeframe. The curve still does not jump on deposits or withdrawals.New features
Off-hours market attributes over WebSocket. NewmarketAttributes channel streams per-market Regular Trading Hours / off-hours trading-band state for every online market — isOutsideRth, the sealed currentSettlementPrice anchor, both current trading-band edges and their next-level edges, and per-side expansion-zone state with its clocks. It is a global channel (no id): a full per-market snapshot on subscribe (isSnapshot: true), then a single-market update on each RTH boundary crossing, VWAP seal, and off-hours bound event — each carrying that market’s complete post-event block plus a timestamp and marketSequenceNum. Fields that do not apply are null (24/7 crypto markets are included with isOutsideRth: false and all band fields null). Supersedes the marketAttributes entry variant on exchangeAttributeUpdates.Updates
Docs default to mainnet. The API playground and the curl / Python / JavaScript code samples throughout the API reference now target the production host —https://api.arcus.xyz (REST) and wss://api.arcus.xyz/v1/ws (WebSocket) — instead of testnet. Testnet (api.testnet.arcus.xyz) remains fully available and is still selectable from the server dropdown on every endpoint and channel page; the two environments share identical paths, payloads, and signing, so a sample runs unchanged against either by switching the host. This supersedes the earlier note (below) that samples and the playground targeted testnet.Updates
Forced closures are marked on fills. Fill rows add aliquidation object — method (LIQUIDATION or ADL) plus liquidatedUser — present on the liquidated account’s leg of a liquidation and the deleveraged counterparty’s leg of an auto-deleverage; absent on voluntary fills. The marker is persisted, so GET /v1/fills and userFills snapshots return it too, and it is the supported way to detect forced closures (the liq: order-ID prefix is an internal detail). On the liquidated leg, fee carries the liquidation penalty paid to the insurance fund and closedPnl reads "0" by design — the realized loss is already reflected in netQuoteBalance on the accompanying account update.New features
Account transfer events over WebSocket. NewaccountTransferUpdates channel streams deposits, withdrawals, internal transfers, and referral claims for an account: a newest-first snapshot on subscribe, then one event per transfer as the engine applies it. Amounts are decimal strings in quote currency, and globalSequenceId gives strict ordering. The REST equivalent is GET /v1/accountTransferUpdates.Predicted funding over WebSocket. New predictedFunding channel streams the predicted next-hour funding rate per market as a {market, rate1h} payload, refreshed each time the funder recomputes the prediction. Unlike most channels, payloads are not wrapped in an isSnapshot envelope.Updates
Subaccount selector on all account-scoped reads. The optionalaccountIndex query parameter (0–9, default 0) previously accepted only by GET /v1/fills now applies across account-scoped endpoints: GET /v1/account, /v1/positions, /v1/openOrders, /v1/orders, /v1/order/{orderId}, /v1/funding, /v1/accountTransferUpdates, /v1/portfolio, /v1/rateLimit, /v1/leverages, and /v1/apiKeys. Values above 9 return 400.Faster orderbook snapshots. The l2Orderbook full-snapshot cadence tightened from ~500 ms to ~200 ms.Updates
24-hour high/low on markets.GET /v1/markets and the markets channel add high24h and low24h — highest and lowest transaction price over the trailing 24-hour window, computed from closed 1-minute candles with at least one trade and omitted when no trades occurred in the window.Updates
Fee-tier fields renamed to ppm.makerFeeBps/takerFeeBps become makerFeePpm/takerFeePpm in the fee-tier shape returned by GET /v1/feetiers, GET /v1/account/stats, and feeTierConfig entries on exchangeAttributeUpdates; the spot fee schedule likewise renames maker_fee_bps/taker_fee_bps to maker_fee_ppm/taker_fee_ppm. The values were always parts-per-million despite the old names. The *Bps fields remain as deprecated aliases (identical values) for one release cycle — migrate to the *Ppm names now.Breaking changes
Testnet withdraw/transfer signing domain rotated. The July 22 testnet reset rotated theBridgeVault verifyingContract for POST /v1/withdraw and POST /v1/transfer to 0x9a6d3499149fea853efe775a3577701539054eaf; signatures against the previous address are rejected with 401. This address rotates on every testnet reset — read the current value from the withdraw endpoint’s per-environment table. The funding guide contract addresses rotated in the same reset.Updates
Perp candle timestamp bounds declared in the schema.GET /v1/candles from/to (Unix microseconds) now declare minimum: 1e14, matching what the handler always enforced.Updates
API key create/revoke signing migrates to EIP-712.POST /v1/createApiKey and POST /v1/revokeApiKey are now authenticated by an EIP-712 typed-data signature (eth_signTypedData_v4) instead of an EIP-191 personal_sign. Both schemes are accepted during the migration window; EIP-191 is deprecated and will be rejected once the window closes. Always send validUntil explicitly and sign that value — omitting it makes the server verify against its own default. See each endpoint description for the domain, types, and signing examples.New features
Internal transfers between subaccounts. NewPOST /v1/transfer moves collateral between two account indexes of the same wallet — in v0 there is no cross-wallet recipient. The request is signed with an EIP-712 typed-data signature over the Transfer payload (eth_signTypedData_v4) and carries a single-use nanosecond-timestamp nonce for replay protection (a repeated nonce returns 409). amount is a quote-quantum integer string (1e9 = $1); unlike withdrawals it never settles on-chain, so it is not constrained to collateral-base-unit multiples. Acceptance is asynchronous: the response carries status: PENDING and a transferId, with the terminal outcome delivered on the account transfer update stream — insufficient free collateral rejects there with REJECTED_INSUFFICIENT_COLLATERAL.Per-market open-interest caps. GET /v1/markets and the markets WebSocket channel add openInterestCap — the operator-set maximum open interest as a USD-notional decimal string (present only while a cap is active). While a market is at its cap, the engine rejects OI-increasing fills with the new rejection reason OPEN_INTEREST_CAP_EXCEEDED; OI-neutral and OI-reducing trades (closes, and opener-vs-closer pairings) still match. Fills executed before the halt stand, and the cap clears as open interest falls or the operator raises it.Updates
Signed withdrawals are single-use.POST /v1/withdraw now rejects a replayed (ethereumAddress, accountIndex, nonce) with 409 — resubmitting an identical signed body (or any body reusing the same nonce) no longer queues a second withdrawal. Use a fresh nonce for each new withdrawal; the recommended nonce is the current time in nanoseconds since the Unix epoch.Updates
API keys are scoped to(address, publicKey). An Ed25519 public key is globally unique across accounts: POST /v1/createApiKey returns 409 Conflict when the publicKey is already registered to a different account (including expired-but-unrevoked keys), and only the owning account may register or renew it. POST /v1/revokeApiKey returns 404 when the key is not registered to the caller’s account — including cross-account revoke attempts — with no side effects.New features
Sectioned account stats.GET /v1/account/stats is now sectioned so clients fetch only what they need via the include query parameter: feeTier (the current fee tier — cheap, right shape for frequent fee-rate polling) and volumes (notional fill volume and fees paid over the 14-day fee-tier rolling window and all-time — backed by analytics storage, fetch on demand). The volumes section also accepts opt-in fixed windows via windows=24h,7d,30d.Market filter on fills. GET /v1/fills adds an optional market query parameter to restrict fill and trade history to a single market, alongside the existing from/to time bounds and accountIndex subaccount selector.Breaking changes
Withdraw EIP-712 signing domain rotated; production domain published. TheBridgeVault verifyingContract for POST /v1/withdraw changed on testnet (0x317253c5c134c3ac60e84c4ca069c9f588030480) — withdrawal signatures produced against the previous addresses are rejected. The production domain is now published: chainId 4663, verifyingContract 0x14b107cf534239c59571b066cb6497a321da897c.Updates
Modify order is no longer marked “coming soon”. The stale caveat claimingPOST /v1/modifyOrder returns 501 Not Implemented is removed — the endpoint has been live for some time. The docs now also spell out the queue-priority contract: a modify is applied in place (keeping queue priority) only when it leaves the price unchanged and reduces size; any price move or size increase is an atomic cancel + replace that loses priority. On the orders WebSocket channel a non-crossing modify arrives as a single update; a crossing modify arrives as a fill followed by a PLACED (or REJECTED) — no separate cancel/place event pair is emitted.goodTilTime is required on every order — the schema now says so. The order schema previously required goodTilTime only for resting time-in-forces (GTT, ALO) and described it as “ignored” for IOC/FOK; the API has always rejected orders without it. It is now documented as unconditionally required on POST /v1/placeOrder, the batch variants, and the WebSocket order payloads: for IOC/FOK it acts as a mandatory replay-protection timestamp, and it must be at least one month in the future.Time-in-force: submit GTT, read GTC. Order submissions must use GTT (Good Till Time) for resting orders, but order reads — REST order queries and the orders WebSocket channel — currently report resting orders as GTC, the legacy name for the same bucket. A new TimeInForceResponse schema documents the read-side enum; treat GTC and GTT as equivalent when reading.modifyOrder signing payload documented in full. The typed canonical payload for modifyOrder now shows its complete field set — {"ad","ai","c","ct","g","id","m","op":3,"p","q","r","s","t","v"}. id is always required, and g/r/s/t echo the resting order’s goodTilTime (in nanoseconds), reduceOnly, side, and time-in-force. The modify request body now also requires reduceOnly and goodTilTime to be echoed explicitly (a differing goodTilTime performs a cancel-replace with the new expiry).Subaccount authorization on order writes. The 403 responses on POST /v1/placeOrder, POST /v1/cancelOrder, the batch variants, and POST /v1/modifyOrder now also cover the case where accountIndex does not match the API key’s authorized subaccount.Breaking changes
Off-hours trading-band fields reshaped. OnGET /v1/markets and the markets WebSocket channel, the off-hours band is now reported as actual USD price boundaries rather than multiplier strings. currentTradingBound / nextTradingBound are removed; use upperTradingBound / lowerTradingBound (plus nextUpperTradingBound / nextLowerTradingBound) and the new per-side expansion-zone state — isUpperInExpansionZone / isLowerInExpansionZone, upperZoneEnteredAt / lowerZoneEnteredAt, and upperExpectedExpansionAt / lowerExpectedExpansionAt. Each side’s band now expands independently on the 0.5× → 1× → 2× → 4× ladder.Rejection reason consolidated. ORDER_WILL_TAKE_LIQUIDITY_DURING_MARKET_HALT and PRICE_WILL_EXCEED_MAXIMUM_OUTSIDE_RTH_TRADING_BOUND are deprecated (no longer emitted) and replaced by a single FILL_WILL_EXCEED_TRADING_BOUND, covering crossing takers, crossing modifyOrder cancel-replaces, triggered TPSLs, and placements that would rest reachable beyond the band.New features
Maximum order size enforced.POST /v1/placeOrder, POST /v1/modifyOrder, and the batch variants now reject an order whose quantity exceeds the market’s maxOrderSize (see GET /v1/markets) with OrderSizeTooLarge. Reduce-only orders are not exempt.Market-attribute events over WebSocket. The exchangeAttributeUpdates channel now emits marketAttributes entries carrying per-market Regular Trading Hours and off-hours band events: RTH boundary crossings (isOutsideRth), VWAP settlement seals (sealedVwapSettlementPrice), and expansion-zone / band-expansion events (boundEvent: expansionZoneEntered, expansionZoneExited, tradingBoundExpanded).Updates
Candle prices are oracle-derived.GET /v1/candles open / high / low / close are derived from the market’s oracle price feed, not executed trades. volume, takerBuyVolume, the notional volumes, and tradeCount remain trade-derived statistics for the bucket.Breaking changes
reduceOnly no longer requires IOC or FOK. POST /v1/placeOrder and POST /v1/modifyOrder now accept reduceOnly: true with any timeInForce — the previous constraint forcing reduce-only orders to be IOC or FOK is removed. Reduce-only position semantics are enforced regardless of TIF, so a resting reduce-only order is now valid.New features
Rolling fees-paid on account stats.GET /v1/account/stats adds rollingFeesPaid — perps trading fees paid over the 14-day fee-tier rolling window (quote quantums, 1e9 = $1), the same window as rollingVolume. It’s computed from an hourly rollup, so it can trail live fills by up to the current partial hour, and reads 0 if that rollup is temporarily unavailable.New features
Lightweight referral-code lookup. NewGET /v1/affiliate/code returns just { address, code, isAffiliate } from a single point read — none of the volume, commission, or fee-tier enrichment that GET /v1/affiliate/info performs. It’s meant for UI surfaces that only need the code (an “Invite Friends” / copy-referral-link control) and may poll every session. Public — no X-API-Key, no signature. “Not an affiliate” is a valid empty state served as 200 with code: "" and isAffiliate: false, never a 404, so callers don’t have to overload the status code. Initially mainnet-only; available on both environments since mid-July 2026.New features
Per-subaccount rate-limit snapshot on write responses.POST /v1/placeOrder, POST /v1/batchPlaceOrders, POST /v1/cancelOrder, POST /v1/batchCancelOrders, POST /v1/cancelAllOrders, and POST /v1/modifyOrder now return a rateLimit object (pool — order or cancel — and remaining tokens after the request) on both REST and WebSocket, so clients can track their remaining budget without an extra call. remaining can be 0 or negative while the request still succeeds when the account is on the drip throttle; -1 is a sentinel meaning the per-subaccount layer was not enforced for this request.clientId echo on rate-limit rejections. The 429 body now echoes the request’s clientId (single-order endpoints) or clientIds (batch endpoints, positionally aligned with the submitted array), so a client firing concurrent requests can correlate the rejection to a specific order or batch.New modify rejection reason. MODIFY_WOULD_CROSS_OUTSIDE_RTH_TRADING_BOUNDARY — a non-crossing modifyOrder reprice rejected because the new price violates the active off-hours trading band. Unlike PRICE_WILL_EXCEED_MAXIMUM_OUTSIDE_RTH_TRADING_BOUND (the crossing cancel-replace path, which removes the original), the original order stays resting on the book unchanged.Rejected setLeverage is now observable on WebSocket. The accountAttributes channel adds a streaming-only leverageReject entry — the REJECTED half of the setLeverage lifecycle. It carries the rejectReason (UNDERCOLLATERALIZED, INVALID_LEVERAGE, or UNKNOWN_MARKET) and the originating requestId for correlation; an applied change still arrives as a leverage entry.Updates
Deterministic market ordering documented.GET /v1/markets now documents that markets are returned sorted by ascending marketId, and the mids map on GET /v1/mids is ordered by ascending marketId and includes only ONLINE markets (OFFLINE markets are omitted).Breaking changes
Position-tiered margin fields removed. Themarkets WebSocket channel no longer returns transferMarginFraction, incrementalInitialMarginFraction, incrementalPositionSize, baselinePositionSize, or maxPositionSize. Initial margin is no longer scaled by position-size brackets in the published market schema; initialMarginFraction, maintenanceMarginFraction, and the off-hours variants remain.minOrderSize and maxOrderSize removed. The same market reads drop minOrderSize and maxOrderSize, and minOrderSize is no longer a required field. Per-market order sizing is now expressed through minOrderNotional (the $5 minimum-notional floor introduced in testnet-v1.1.93) and stepSize.Breaking changes
Minimum order notional enforced.POST /v1/placeOrder and POST /v1/batchPlaceOrders now reject position-opening orders whose notional (quantity × price) is below $5 with InvalidRequest. Reduce-only orders (including TPSLs) are exempt. The per-market floor is published as minOrderNotional on GET /v1/markets.All-traders leaderboard replaces the affiliate volume leaderboard. GET /v1/affiliate/volumeleaderboard (getVolumeLeaderboard) is gone, replaced by GET /v1/leaderboard (getTraderLeaderboard). A new sortBy query param (volume, pnl, or fees; default volume) chooses the ranking column, and every row now carries volume, feesPaid, and pnl (realized only) together, computed in one query so the three columns are always mutually consistent. The window (all/30d/24h, default 30d) and limit (max 100, no cursor) semantics are unchanged.API key keyName removed. POST /v1/createApiKey no longer accepts or returns the optional keyName label.New features
Off-hours trading-band fields. Themarkets WebSocket channel now exposes currentSettlementPrice, currentTradingBound, and nextTradingBound for equity-class markets while outside regular trading hours (omitted in-RTH and for 24/7 crypto markets).Lifetime volume & fees on the fee-tier read. GET /v1/feetiers now returns lifetimeVolume and lifetimeFeesPaid alongside the existing 14-day rollingVolume.Updates
Open interest is now populated.openInterest on the markets WebSocket channel now reports the total size of open long positions (equal to total short, in base-asset units), refreshed from a periodic snapshot, instead of the previous placeholder "0".New features
Mainnet is live. The production API is now serving athttps://api.arcus.xyz (REST) and wss://api.arcus.xyz/v1/ws (WebSocket), selectable from the server dropdown on every endpoint page. Testnet (api.testnet.arcus.xyz) remains available, and the two environments share identical paths, payloads, and signing — only the host differs. The code samples and API playground throughout the docs continue to target testnet. See the API introduction.Updates
Minimum withdrawal size enforced.POST /v1/withdraw now rejects amounts below the collateral currency’s minimum with 400 — e.g. validation error on field 'amount': must be at least 1000000000 quote quantums (1e9 quote quantums = $1). Amounts must also be exactly representable in collateral base units.Breaking changes
Order time-in-force rules tightened. New per-order-typetimeInForce constraints, enforced at request validation:- Every order must include
goodTilTime(epoch microseconds) set at least one month in the future — now required even forIOCorders, which previously ignored it. A missing value is rejected withvalidation error on field 'goodTilTime': is required. MARKETorders must useIOC.GTT,FOK, andALOare rejected withvalidation error on field 'timeInForce': must be IOC for MARKET orders.- TPSL orders:
LIMITTPSL legs must useGTT;MARKETTPSL legs must useIOC. (TPSL legs must also bereduceOnly, and are placed viabatchPlaceOrdersgroupings.)
New features
Server time.GET /v1/time returns the current server time — useful for aligning the nanosecond signing timestamp with the gateway’s ±30s drift window.Breaking changes
Order signing moved to the ordersign typed canonical payload.placeOrder, cancelOrder, and modifyOrder — and each element of batchPlaceOrders / batchCancelOrders — now sign a compact, key-sorted JSON payload built from the parsed request: the payload object is the Ed25519 message, with no timestamp + action prefix. The timestamp is carried as the ct field (it must equal X-Timestamp), and price/size are signed as integer ticks/quantums derived from the market’s tickSize / stepSize. The previous X-Timestamp + action + canonicalJSON(body) scheme is now rejected with 401 for these endpoints. cancelAllOrders, setLeverage, and WebSocket authentication keep the legacy message. See Authentication.GTC replaced by GTT. The timeInForce enum is now GTT, IOC, FOK, ALO. Resting orders (GTT, ALO) require goodTilTime (epoch microseconds) set at least one month in the future at the time the order is processed; nearer or missing values are rejected.Withdrawals now use EIP-712 typed data. POST /v1/withdraw authenticates with an eth_signTypedData_v4 signature over a Withdraw typed payload (domain and types in the endpoint reference) instead of the previous SHA-256 ECDSA canonical message.Market orders require a protective price bound. A MARKET order’s price must be within 10% of the current mark price (TPSL market orders: within 10% of the trigger price), or the order is rejected. With this bound, market orders behave like marketable limit orders.Updates
New order rejection reason.PRICE_WILL_EXCEED_MAXIMUM_OUTSIDE_RTH_TRADING_BOUND is added to the RejectionReason set delivered on the orders WebSocket channel — emitted when a resting order’s price would breach the maximum off-hours trading bound once the current regular-trading-hours session ends.Breaking changes
API base URLs moved toapi.testnet.arcus.xyz. REST is now https://api.testnet.arcus.xyz and WebSocket wss://api.testnet.arcus.xyz/v1/ws/. The previous hostnames are being retired — update any saved base URLs, SDK configs, and clients at your earliest convenience to avoid disruption. Paths and payloads are unchanged. See the API introduction.Breaking changes
Candles pagination replaced.GET /v1/candles no longer accepts limit. A request must now pass to — the upper bound of the window as a Unix microsecond timestamp (seconds- or milliseconds-scale values are rejected) — and may scope the lower bound with either countback (number of closed bars counting back from to) or from (a microsecond lower bound); from and countback are mutually exclusive. A request without to now returns 400.volume30d renamed to rollingVolume. GET /v1/account/stats and the affiliate commission and claim schemas now report notional fill volume under rollingVolume, measured over the fee-tier rolling window rather than a fixed trailing 30 days. Update any client that reads volume30d.Updates
New order rejection reasons. TheRejectionReason set delivered on the orders WebSocket channel adds OPEN_ORDER_CAP_EXCEEDED, ORDER_NOT_FOUND, ORDER_NOT_FOUND_FOR_MODIFY, MODIFY_CHANGED_IMMUTABLE_FIELD, MODIFY_ZERO_SIZE, and ORDER_WILL_TAKE_LIQUIDITY_DURING_MARKET_HALT.Market asset name in metadata. Market metadata responses now include fullAssetName (e.g. Bitcoin) when a canonical name is configured for the market.Breaking changes
Unified request signing. REST and WebSocket now share one signing message:X-Timestamp + action + canonicalJSON(body), where action is the canonical operation name — the REST path’s final segment or the WebSocket request type (e.g. placeOrder, cancelOrder). The HTTP method and the /ws/v1/ path prefix are no longer part of the signed message; re-sign with the new message before sending or the request is rejected with 401 invalid signature. Batch methods sign each element over its singular action (e.g. placeOrder for each order in batchPlaceOrders) with one shared timestamp. The cutover also invalidates API keys registered before it — re-register via POST /v1/createApiKey (EIP-191 registration is unchanged). See Authentication.Breaking changes
Protected requests now require nanosecond timestamps. The signing timestamp —X-Timestamp for REST, timestamp for WebSocket order methods — must now be Unix time in nanoseconds as a decimal string (e.g. 1713825891591000000), not milliseconds. Millisecond or second epochs are rejected with 401 Unauthorized. Update any client that builds the signing message before re-signing. See Authentication.Updates
User-facing timestamps are now microseconds. Response time fields — candleopenTime, funding payment times, and order placement/update times — are emitted in Unix microseconds for consistent precision across the API.New features
Volume leaderboard REST endpoint.GET /v1/affiliate/volumeleaderboard returns the top addresses ranked by signed-notional fill volume over a rolling window — all, 30d, or 24h (default 30d).Referral commission rate schedule. GET /v1/commissionrates returns the referral commission rate schedule used to compute affiliate payouts on each fill.New features
Rate-limit usage REST endpoint.GET /v1/rateLimit returns the state of both per-subaccount throttle pools (order and cancel), so clients can self-pace and see which limit is currently constraining them.Withdrawals REST endpoint. POST /v1/withdraw submits a withdrawal of collateral to the chain.Referral code availability. GET /v1/affiliate/codeAvailable returns { code, available } for a single referral code string.New features
Fee tier table REST endpoint.GET /v1/feetiers returns the full fee tier table, sorted ascending by level (0=Base through 4=Platinum) — the maker/taker fee BPS for each tier and the 30-day notional volume threshold required to reach it.Exchange attribute updates WebSocket channel. Subscribe to exchangeAttributeUpdates for exchange-wide state, discriminated by entry type. The current type, feeTierConfig, sends the full fee tier table on subscribe and a fresh replacement whenever an operator updates it — the streaming counterpart to GET /v1/feetiers.New features
Account attribute updates WebSocket channel. Subscribe toaccountAttributeUpdates for live per-account state — effective leverage per market and the account’s current trading fee tier — discriminated by entry type ("leverage" or "feeTier"). On subscribe the server sends one leverage entry per market plus, when available, one fee tier entry; updates stream a single-entry delta for whichever attribute changed.Leverages REST endpoint. GET /v1/leverages returns the effective leverage for (address, accountIndex) across every market, sorted by ascending marketId. Each entry reflects the user’s override (set via POST /v1/setLeverage) when one exists, otherwise the market default.Account stats REST endpoint. GET /v1/account/stats returns 30-day rolling notional fill volume and the current trading fee tier (level + BPS) for any Ethereum address.Updates
Affiliate endpoints moved. All/v1/api/affiliate/* paths are now under /v1/affiliate/* — the legacy /api/ segment has been dropped. Update any clients that hard-coded the old prefix. Operation IDs and request/response schemas are unchanged.New features
Trades REST endpoints. Query recent public trades for a market withGET /v1/trades, or fetch a single trade by ID with GET /v1/trade/{tradeId}.Funding payments — REST and WebSocket. Track per-account funding history with GET /v1/funding and per-market rates with GET /v1/fundingRates. Stream live funding payments over the new funding WebSocket channel — snapshot of the 100 most recent payments on subscribe, then one event per (market, account) at each funding time. Positive payment = received, negative = paid.Portfolio history. GET /v1/portfolio returns account equity, PnL, and value-over-time history for charting.Positions REST endpoint. GET /v1/positions returns the current open positions for an account — the REST counterpart to the existing positions WebSocket channel.Cancel-all and set-leverage. POST /v1/cancelAllOrders cancels every open order for an account in one call; POST /v1/setLeverage updates per-market leverage.Account transfer updates. GET /v1/accountTransferUpdates returns deposit/withdrawal history with cursor pagination.Referral program. Twelve new endpoints under /v1/api/affiliate/* cover the full affiliate lifecycle — create and revoke referral codes, register affiliates, view referees and leaderboard, track commissions and kickbacks, and claim commission payouts.New features
REST and WebSocket API now live on testnet. Trade, stream market data, and manage accounts againsthttps://api.testnet.arcus.xyz (REST) and wss://api.testnet.arcus.xyz/v1/ws/ (WebSocket). Both APIs are non-custodial — register an Ed25519 API key against your Ethereum address and the server never sees a private key. See the API introduction to get started.Real-time market data channels. Subscribe to live updates over WebSocket:l2Orderbookandl2OrderbookUpdates— full snapshots every ~500 ms, or initial snapshot plus incremental updates (1–100 levels).bbo— best bid and offer at the top of book.trades— live public trade stream.markets— global market metadata with funding rate, oracle and index prices, and 24h volume.oraclePrices— per-market oracle prices from the on-chain Slinky aggregator.candles— OHLCV across 13 timeframes from1mto1w, with up to 200-candle snapshots.
account, positions, orders, and userFills.Order routing over WebSocket. Place, cancel, modify, and batch orders directly over WebSocket. All order methods are asynchronous and return a 202 ACK — observe lifecycle through the orders and userFills channels. See Placing orders.Ed25519 API keys with Ethereum-signed registration. Generate an Ed25519 key pair, register it with an ECDSA signature from your master Ethereum address, and sign protected requests with a Unix-millisecond timestamp. Full instructions in Authentication.Updates
Rebranded to Arcus. The product is now Arcus across all user-facing surfaces, including the API spec and documentation.Known limitations
batchModifyOrdersis not yet implemented and returns501.createApiKeyis REST-only; the WebSocket equivalent returns501 NotImplemented.