Skip to main content
Connect to wss://api.arcus.xyz/v1/ws on mainnet or wss://api.testnet.arcus.xyz/v1/ws on testnet. The connection multiplexes channel subscriptions and request/response (RPC) calls.
A client that cannot drain its socket promptly falls behind and can be disconnected. See Latency → Receiving market data for recommended client resources.

Message envelopes

Subscribe / unsubscribe:
The server replies with { "type": "subscribed", ... } whose contents is the initial snapshot for channels that deliver one — there is no separate bare acknowledgement frame. (A connection-health check should therefore count channels that delivered contents, not empty acks.) It then streams { "type": "channel_data", ... } updates. Each update carries the channel, the subscription id (the market, or the lowercase master address on account-scoped channels; absent on global channels), and a contents payload. Timestamps are inside contents (e.g. timestamp, epoch, markEpochNanos); there is no top-level publish timestamp. Some frames carry extra envelope fields so a connection holding several views can tell them apart:
  • accountIndex — on every frame of an account-scoped channel (subscribed, unsubscribed, degraded, and channel_data), including when it is 0. id is the master address and is identical across subaccounts, so demultiplex on id + accountIndex.
  • market — on positions, orders, and userFills. On channel_data it is the ticker of the market the frame is about (present even on unfiltered subscriptions); on subscribed / unsubscribed it echoes the market filter, if any.
  • sigFigs / roundStep — on l2Orderbook frames of an aggregated view (see the subscribe fields below).
Degraded subscribe. If the initial snapshot cannot be served (too stale to join the live stream, or the read failed), the server sends { "type": "degraded", "channel": …, "id": …, "reason": "snapshot_stale" | "snapshot_unavailable", "retryAfterMs": … } instead of subscribed (plus accountIndex on account-scoped channels). The subscription stays pending and the server keeps retrying; it completes with a normal subscribed frame once a snapshot is available. No client action is needed — show the stream as stale until subscribed arrives. Optional subscribe fields: accountIndex, market, and sigFigs / roundStep are part of the subscription’s identity: an unsubscribe must repeat the same values to remove that view. An unsubscribe without accountIndex removes only subaccount 0; one without market removes only the unfiltered view. One connection may hold several l2Orderbook views of the same market as long as each uses a different sigFigs / roundStep pair. Request / response: every post and get message must include a numeric id chosen by the client. The server echoes the same id in its response. IDs must be strictly increasing per connection: a get or post whose id is not greater than the previous one on that socket is rejected with an error frame. That error frame does not echo the id, so a client that matches replies by id will see the request as unanswered rather than failed. A counter that starts at 1 for each new connection satisfies this.
See Authentication for the signing rules that apply to order methods.

What authorizes a subscription

Nothing. Subscribing is never authenticated — not even on the account-scoped channels (account, positions, orders, userFills). A subscribe frame carries no signature, the socket is not bound to an API key, and there is no authenticate step to miss: any connection may subscribe to any address and will receive that address’s balances, positions, open orders, and fills. This is deliberate, not an oversight. These channels are the streaming form of the account-scoped REST reads (GET /v1/fills, /v1/orders, /v1/positions, /v1/account), which are themselves public and take ?address= without an API key. Account state on Arcus is readable by anyone who knows the address; the API key authorizes writes only.
Your positions and fills are observable in real time by anyone who knows your address. If a strategy depends on that activity not being attributable, separate it by address — the API offers no per-address read restriction.
The WebSocket authenticate method exists, but it is not a prerequisite for subscribing or for signed order methods (each order carries its own signature). See Authentication.

Placing orders

Order methods are asynchronous. The server returns 202 with status: "ACK" (or CANCEL_ACKNOWLEDGED); subscribe to the orders or userFills channels to observe the lifecycle.
Server response:
No cancel-on-disconnect. Dropping a WebSocket connection tears down your subscriptions but does not cancel your resting orders. An order rests until it fills, you explicitly cancel it, or its goodTilTime expires (an expired resting order is removed when matching reaches it and reported on the orders channel as REJECTED / EXPIRED); see Order types for the goodTilTime rules. If you need orders pulled when your process dies, arm the dead man’s switch or run your own external kill-switch that cancels on disconnect.

Dead man’s switch (scheduleCancel)

scheduleCancel is a server-side dead man’s switch: you arm a deadline, and if you don’t refresh it before it elapses, the gateway automatically cancels your resting orders. Use it so a crashed or disconnected trading process doesn’t leave stale quotes on the book. It’s a signed post method using the same envelope as other order methods, but it signs with Scheme 2 (see Authentication → Sign protected requests), not the typed order payload. It is also available over REST as POST /v1/scheduleCancel.
  • Arm or refresh — send time, an absolute epoch-microsecond deadline between 5 seconds and 5 minutes in the future. Keep the switch alive by sending a new, later time before the current one elapses (typically now + 60s on a short refresh interval); reusing a past time does not extend it.
  • Disarm — omit time (or send null).
Scope. Omit marketId for an account-wide switch (cancels every open order across all markets when it fires), or set it to scope the switch to a single market. Per-market switches are independent of the account-wide switch and of each other on the same subaccount — each keeps its own deadline and cancels only its own market — so you can run one process per market, each arming its own switch. marketId selects which switch a given arm/refresh/disarm targets.
Add "marketId": 1 to the payload to scope the switch to a single market. Limits. Every arm/refresh draws from the per-subaccount cancel pool, and the switch has its own quotas on auto-fires and on switches armed at once — see Rate limits → Dead man’s switch quotas. When a switch fires, the resulting cancellations stream over the orders channel like any other cancel.

Channels

See the Channels reference for the full list and per-channel payload schemas.

Sequence numbers

Streamed messages carry sequence numbers so you can order events, detect dropped messages, and resync after a reconnect. Three scopes exist:
  • Global — one monotonic counter across the whole exchange, incremented for every event the matching engine processes. Use it to order events across markets and to tell whether you’ve missed anything.
  • Per-market — a counter local to a single market.
  • Per-account — a counter local to a single account.
Which field carries the sequence depends on the message: The lastSequenceId on account and positions snapshots is the per-account sequence at snapshot time — the same counter as AccountUpdate.sequenceNumber and Order.sequenceNumber. Streaming positions deltas carry this same per-account counter: each PositionUpdate envelope stamps lastSequenceId and each position row carries sequenceNumber, so a delta can be ordered directly against account updates and used to discard updates already contained in a snapshot. For account state, treat each snapshot as the reconciliation point: the account channel re-snapshots every 5 seconds, and a re-subscribe fetches a fresh snapshot on any other account-scoped channel.

Resyncing the order book

lastSequenceId on l2OrderbookUpdates increases by exactly 1 per delta. Any gap, including between the subscribe snapshot and the first delta, is a missed update and does not repair itself: deltas carry absolute level sizes, and the snapshot can lag the live stream. To (re)build the book, keep buffering deltas, take a fresh snapshot with the same nLevels (an l2Orderbook frame or GET /v1/l2OrderBook/{market}), replace your book with it, and apply only buffered deltas with a higher lastSequenceId. If the snapshot’s lastSequenceId is lower than your oldest buffered delta minus 1, take another snapshot. After that, also drop any later delta at or below the snapshot’s lastSequenceId: the snapshot can arrive ahead of your buffer. Size "0" removes a level. Don’t need deltas? Subscribe to l2Orderbook instead: every frame is the full book (to nLevels), so there is nothing to reconcile. Use globalSequenceId to order order-book events against other markets.

Order-book sequence contiguity

The order-book channels share the same per-market lastSequenceId, but only l2OrderbookUpdates (and the l2Orderbook snapshot it baselines against) is contiguous — the only stream where a gap is meaningful. Treat each field by its guarantee: bbo is a snapshot channel: every frame carries the full current top of book and self-replaces the previous one, so a dropped frame self-heals on the next — there is no in-band gap detection and none is needed. If you need a contiguous, gap-detectable, replayable order-book stream, use l2Orderbook / l2OrderbookUpdates. On every reconnect, rebuild the book the same way; don’t assume lastSequenceId continuity across the gap.

Errors

Responses include an HTTP-like status and either a result or an error object with type, message, and optional field, errorType / errorSource (machine-readable code and the layer that raised it; errorType: Transmission means the order never reached the matching engine and is safe to resend unchanged) and retryAfterMs (on 429). Close codes. After 100 consecutive failed API-key authentications on a connection, the server closes it with code 4401 and reason too many consecutive authentication failures; a successful authentication resets the count. Treat 4401 as a credentials problem: fix the API key or signature before connecting again rather than reconnecting in a loop.