WebSocket Order Entry API Reference
Overview
The Reya DEX WebSocket Order Entry API v2 is a request/response surface for placing, modifying, and cancelling orders — and managing an account-scoped cancel-on-disconnect switch — over a persistent WebSocket connection. It carries the same operations and payload bodies as the REST /v2 endpoints (POST /v2/createOrder, POST /v2/modifyOrder, POST /v2/cancelOrder, POST /v2/cancelAll, POST /v2/cancelAllAfter), with lower per-operation overhead and id-correlated responses on the same channel.
Payload bodies are reused verbatim from REST. Request and response envelopes are id-correlated; the server replies on the same connection with a frame carrying the same id the client sent.
This surface is order-entry only. For real-time market data, position updates, and fill streaming, see the WebSocket Info API Reference. The recommended integration runs both connections in parallel: this surface for order entry, the streaming surface for market and account updates.
Server Endpoints
Production Environment
URL:
wss://ws-exec.reya.xyzProtocol: WSS
Description: Production WebSocket order-entry server for live trading
For integration testing against the same contract, use devnet1. See Environments for each environment's endpoints and signing values.
Connection & Auth Model
The connection itself is anonymous — no handshake, no login, no API key. There is no concept of a session-bound wallet identity.
Authentication is per-frame: every order-bearing request carries an EIP-712 signature in its payload (signature, nonce, and deadline — the required signature-validity timestamp; GTT orders additionally carry an expiresAfter lifetime). createOrder, modifyOrder, and cancelAllAfter also carry signerWallet; cancelOrder recovers the signer from the signature. The server validates the signature against the order contents on every request — identical to the REST /v2/createOrder etc. body shape. See Signatures and Nonces for the signing model; both transports use the same scheme and the same Python SDK helpers.
A consequence of per-frame authentication is that a single WebSocket connection can carry orders signed by multiple different signerWallet values — useful for integrators operating multiple subaccounts on one connection.
Choosing WebSocket instead of REST does not change order semantics. Account-operation allowances are shared across both transports, with a separate allowance for cancelAllAfter. See Rate Limits for account, message and IP limits. Production Order Entry access is restricted to approved market-maker source IPs; provide your egress IPs to the Reya team during onboarding.
Message Structure
All WebSocket messages follow a standardized envelope structure with a type discriminator and a client-chosen id for correlation.
Request Envelope (Client → Server)
Components
type (string, required): One of
createOrder,modifyOrder,cancelOrder,cancelAll,cancelAllAfter,ping. (pongis server-only — see Heartbeats.)id (string, required): Client-chosen correlation identifier. Must be unique across in-flight requests on the connection — see In-Flight
idUniqueness below.payload (object, required for
createOrder/modifyOrder/cancelOrder/cancelAll/cancelAllAfter): Operation-specific request body, byte-identical to the corresponding REST endpoint's request body.
Response Envelope (Server → Client)
or, on failure:
Components
type (string, required): Echoes the request
type.id (string, required): Echoes the request
id.ok (boolean, required):
truefor success,falsefor failure.payload (object, required when
ok = true, forbidden whenok = false): Operation-specific success body, byte-identical to the REST200response body.error (object, required when
ok = false, forbidden whenok = true): See Error Catalog for the shape and possible codes.
Top-Level Error Envelope (Server → Client)
The server emits a top-level error envelope when it cannot parse a request at all (malformed JSON, unknown type, in-flight id collision, internal failure). Connection stays open. Operation-specific errors instead come back as { ok: false, error } on the corresponding response envelope correlated by id.
id is present when the offending request carried one (e.g. for
DUPLICATE_REQUEST_ID); absent for frame-level errors (e.g. unparseable JSON) where the server has no id to echo.
Heartbeats
The heartbeat / connection-liveness mechanism is documented in detail on its own page — see Heartbeats. Short version: protocol-level pings handle liveness automatically, no application-level code is required on the client.
Operations Reference
createOrder
Purpose: Place spot or perp LIMIT orders (IOC, GTC, or GTT), or arm perp STOP_LOSS / TAKE_PROFIT trigger orders. Identical body and semantics to REST POST /v2/createOrder.
Request Envelope (spot LIMIT GTC):
Request Envelope (perp IOC):
Request Envelope (perp GTT — good-til-time, post-only):
Success Response (rested without an immediate fill):
Success Response (IOC filled on entry):
Error Response:
modifyOrder
Purpose: Amend one live order in place: a resting LIMIT (GTC or GTT; spot or perp) or an armed perp STOP_LOSS / TAKE_PROFIT. The order keeps its orderId and clientOrderId. Identical body and semantics to REST POST /v2/modifyOrder.
Full restate. The payload carries the complete post-modify OrderDetails state plus a target id — restate every value, even unchanged ones. It reuses the same signed EIP-712 field universe as createOrder, but not always the same JSON field set: trigger create and modify both require timeInForce and must omit reduceOnly and postOnly, including false; both Boolean fields are reconstructed as fixed false for signing. The modifiable set is class-split: limitPx, qty, postOnly, and expiresAfter for a LIMIT order; only limitPx and triggerPx for a STOP_LOSS / TAKE_PROFIT trigger. All other fields are immutable and must be restated at the live order's values. Signing reuses the OrderDetails envelope with a fresh nonce, exactly as createOrder. For the complete modify lifecycle — modifiable-vs-immutable fields, targeting, crossing behavior, queue-priority effects, and the full error set — see Modifying a resting order. The field-level rules for this WebSocket payload are in the schema block below.
Request Envelope (re-price + resize, targeting by orderId):
Success Response (rested after modify, preserving earlier fills of 0.6 at 40000):
Success Response (alternatively, the modify crossed and filled the remaining 0.4 at 41000):
Error Response:
cancelOrder
Purpose: Cancel a previously placed order by orderId, or by non-zero clientOrderId when orderId is absent. Identical body and semantics to REST POST /v2/cancelOrder.
Request Envelope:
Success Response:
cancelAll
Purpose: Mass-cancel all open orders for an account on a given market (spot or perp), or across all markets if symbol is omitted. Identical body and semantics to REST POST /v2/cancelAll. Per-market mass-cancel is supported for both spot and perp markets.
Request Envelope:
Success Response:
cancelAllAfter
Purpose: Arm, refresh, or disarm an account-scoped dead-man's switch (cancel-on-disconnect). Identical body and semantics to REST POST /v2/cancelAllAfter. Send a signed cancelAllAfter with a timeoutMs in [5000, 60000] to arm/refresh a countdown, or 0 to disarm; if the countdown elapses without a refresh, the matching engine cancels every open order on the account except protective stops. The switch is transport-agnostic (REST and ws-exec share one account-level timer) and is not tied to the connection — closing the WebSocket neither fires nor disarms it, and only another cancelAllAfter refreshes it. For the full behavior — bounds, protective-stops exemption, trigger semantics, and recommended heartbeat use — see Cancel-on-Disconnect. The field-level rules are in the schema block below.
The EIP-712 envelope signs accountId, timeoutMs, nonce, and deadline. Note that deadline is the signature-validity window (unix seconds), not the trigger time — the trigger time is timeoutMs relative to server receipt.
Request Envelope (arm a 30-second countdown):
Success Response (armed — echoes timeoutMs and the triggerAt fire time):
Success Response (disarm — timeoutMs: 0, no triggerAt):
ping / pong
Purpose: Optional client-initiated application-level liveness/RTT probe. The client sends {type:"ping", id?}; the server replies with {type:"pong", id?} echoing the optional id. See Heartbeats for the full description — when to use it, when not to, and how it relates to the protocol-level liveness mechanism that keeps the connection alive automatically.
Error Catalog
Account-operation and resting-order rejections use the codes below. An optional positive retryAfterMs gives the minimum wait after an account-operation rate rejection. Message-rate limits instead close the connection with 4029; they do not produce a per-operation error response. See Rate-limit errors and recovery.
Every error envelope (both per-operation {ok: false, error} and top-level error) carries a RequestError-shaped object:
The error field is one of the codes below. Per-operation responses ({ok: false, error} on createOrder / modifyOrder / cancelOrder / cancelAll / cancelAllAfter) use codes from the Order Management group, shared 1:1 with REST. Top-level error envelopes use codes from the Framing Layer group exclusively; these only make sense for a streamed envelope protocol and never appear in REST responses.
Order Management Codes (shared with REST)
SYMBOL_NOT_FOUND_ERROR
The symbol in the payload doesn't resolve to a known market.
Refresh market definitions via REST GET /v2/perpMarketDefinitions.
NO_ACCOUNTS_FOUND_ERROR
The accountId doesn't exist or isn't owned by the signer.
Verify account configuration.
INPUT_VALIDATION_ERROR
Generic body-validation failure (missing field, wrong type, illegal value). Also a modifyOrder that restated an immutable field with a value that doesn't match the resting order, or whose expiresAfter is inconsistent with the order's timeInForce.
Fix the request body; consult the human-readable message.
RATE_LIMITED_ERROR
The account exhausted the allowance for this operation group.
Respect retryAfterMs when present and back off; see Rate Limits.
INSUFFICIENT_BALANCE_ERROR
Available collateral is insufficient for the order.
Reduce size or add collateral.
OPEN_ORDER_COUNT_EXCEEDED_ERROR
The account-wide or per-market resting-order count cap is reached.
Cancel resting orders or wait for fills before placing another GTC/GTT order.
OPEN_ORDER_NOTIONAL_EXCEEDED_ERROR
A create or modify would exceed the account's gross resting-order notional cap.
Reduce the proposed notional or release capacity through cancellations or fills.
CAPACITY_LIMITED_ERROR
Venue load temporarily restricts creates and modifies.
Back off with jitter; cancellations and countdown requests retain their own allowances.
PRICE_QTY_BOUNDS_ERROR
Price or quantity is outside the market's accepted bounds.
Refresh market definitions and adjust price/quantity.
SERVICE_DISABLED_ERROR
Order entry is disabled for this market or instrument.
Stop submitting until the market is re-enabled.
UNAUTHORIZED_ACCOUNT_ERROR
The signer is valid but is not authorized for the target account/order.
Check account ownership and trading-key permissions.
TRADING_HALTED_ERROR
A createOrder reached the matching engine while it was shutting down for a restart. Not returned when settlement falls behind: that pauses matching and returns CROSSING_ORDERS_TEMPORARILY_UNAVAILABLE_ERROR instead.
The order was not accepted. Back off, then re-sign with a fresh nonce and resubmit if it is still wanted.
DUPLICATE_CLIENT_ORDER_ID_ERROR
A live order already uses the supplied clientOrderId.
Use a fresh client order id or reconcile the existing order.
ACCOUNT_BELOW_LIQUIDATION_MARGIN_ERROR
The account is already below liquidation margin, or its realised balance is below the chain floor.
Restore the margin / real-balance floor; ordinary reducing orders do not bypass it. Read message for the binding condition.
ACCOUNT_BELOW_INITIAL_MARGIN_ERROR
The proposed trade would leave the account below initial margin, including the settlement buffer.
Reduce size, add collateral, or submit a genuinely risk-improving order. Do not retry unchanged.
OPEN_INTEREST_CAP_ERROR
The market's open-interest cap is reached.
Wait for market open interest to fall or reduce exposure; do not loop retries.
OPEN_INTEREST_BUDGET_ERROR
The tighter budget covering matched-but-unsettled fills is exhausted.
Let pending settlement clear, then retry deliberately; do not blindly loop.
REDUCE_ONLY_CONDITION_NOT_MET_ERROR
A reduce-only order would not strictly reduce the current position.
Reconcile the position, side, and size, then re-sign a correcting order.
CROSSING_ORDERS_TEMPORARILY_UNAVAILABLE_ERROR
A createOrder or modifyOrder would cross immediately while crossing orders are temporarily unavailable: the exchange cannot run pre-trade risk checks for it, or matching is paused. Nothing is placed; a rejected modify leaves the order unchanged.
Back off, then retry the crossing request. Cancels remain available. During a matching pause, non-crossing creates and modifies and trigger placement keep working; while risk checks are unavailable, other admission still requires its own risk data to be ready.
TRIGGER_IOC_MUST_NOT_EXPIRE_ERROR
An IOC trigger carries an expiry. This invalid combination can also return INPUT_VALIDATION_ERROR.
Omit expiresAfter for IOC and re-sign.
TRIGGER_LIMIT_OUTSIDE_BAND_ERROR
limitPx is outside the permitted trigger-price band, or stops are unavailable on the market.
Read message: move the limit closer and re-sign for a price-band rejection; contact Reya if stops are unavailable.
TRIGGER_ALREADY_EXISTS_ERROR
An armed trigger of the same type already exists for this account and market.
Modify or cancel the existing trigger before creating another of that type.
ORDER_EXPIRES_TOO_SOON_ERROR
A GTT expiresAfter is still in the future but leaves too little time for settlement.
Re-sign with a later expiresAfter; do not retry the same payload.
CREATE_ORDER_OTHER_ERROR
Generic matching-engine create failure not covered by a more specific code.
Read message for the underlying reason; do not infer a settlement result from this catch-all.
CANCEL_ORDER_OTHER_ERROR
Generic cancelOrder failure not covered by a more specific code.
Read message.
ORDER_DEADLINE_PASSED_ERROR
deadline (the signature-validity timestamp) is in the past.
Re-sign with a fresh deadline.
ORDER_DEADLINE_TOO_HIGH_ERROR
deadline is more than 600 seconds in the future. Enforced on IOC creates and on all cancels (cancelOrder / cancelAll); GTC / GTT creates are not capped.
Use a deadline within 600 seconds of now.
INVALID_NONCE_ERROR
Nonce is not strictly monotonic for this signer, or was already used.
Re-sign with a fresh monotonic nonce.
CANCEL_ALL_AFTER_OTHER_ERROR
A matching-engine-side cancelAllAfter failure with no more specific code. (A non-zero timeoutMs outside [5000, 60000] is rejected with INPUT_VALIDATION_ERROR, not this code.)
Read message; for an out-of-band timeoutMs, send 0 or a value in range.
SERVICE_UNAVAILABLE_ERROR
The service was temporarily unavailable. The request was not accepted.
Back off and re-sign with a fresh nonce, retaining the same clientOrderId when supplied.
ORDER_OUTCOME_UNKNOWN_ERROR
API 3.6.2: the operation may have been applied.
Reconcile state and executions before resubmitting; never replace an unresolved attempt with a fresh nonce. See Handling ambiguous outcomes.
UNAUTHORIZED_SIGNATURE_ERROR
The recovered signer is not authorized to act on the accountId.
Verify the signer wallet is in the account's permissioned-addresses list on-chain.
NUMERIC_OVERFLOW_ERROR
A numeric field exceeds the allowed uint64 / int256 range.
Fix the request body.
POST_ONLY_WOULD_CROSS_ERROR
A post-only order (on createOrder) or post-only modify (on modifyOrder) would cross or touch the best opposite price at entry. Nothing is placed; on a modify the resting order is left untouched.
Re-price the order so it rests as a maker, or drop postOnly to allow it to take.
ORDER_NOT_FOUND_ERROR
The orderId / clientOrderId targeted by a modifyOrder or cancelOrder does not resolve to a live resting order.
Verify the order is still resting (check GET /v2/wallet/{address}/openOrders).
EMPTY_MODIFY_ERROR
A modifyOrder whose post-modify state is identical to the order's current state — no field actually changed.
Send a modify that changes at least one of limitPx / qty / postOnly / expiresAfter / triggerPx.
MODIFY_QTY_BELOW_FILLED_ERROR
A modifyOrder qty (the total post-modify quantity) is not strictly greater than the order's already-filled cumQty.
Set qty above the filled amount, or cancel instead.
MODIFY_ORDER_OTHER_ERROR
A modifyOrder the matching engine can't apply — e.g. the signer isn't authorized for the account, or (for a trigger modify) a matching-engine-level trigger rule is violated.
Read message — the engine's reason is passed through. A fired protective child must be cancelled, not modified.
Framing Layer Codes (top-level error envelope only)
These codes appear only in the top-level error envelope, never inside a per-operation {ok: false} response.
MALFORMED_JSON
The frame body could not be parsed as JSON, or required envelope fields (type, id) are missing. Connection stays open.
Fix the client serializer.
UNKNOWN_TYPE
The frame's type field is not one of the accepted values. Connection stays open.
Verify the request type.
DUPLICATE_REQUEST_ID
The frame's id is already in-flight on this connection — i.e., the client sent a new request with an id whose response hasn't yet been emitted. Connection stays open; the new request is rejected, the original is unaffected.
Use a fresh id for each request (UUIDs work).
INTERNAL
The server hit an internal failure handling the frame. Connection stays open.
Retry; if the problem persists, contact support with the id.
SERVER_SHUTTING_DOWN
The service is restarting and is not accepting new requests.
Reconnect and reconcile unanswered requests before resubmitting; see Heartbeats.
TOO_MANY_INFLIGHT
The connection has too many un-acknowledged in-flight requests.
Throttle — wait for outstanding responses before sending more.
Data Types & Schemas
Enumeration Types
For the complete enumeration of RequestErrorCode (per-operation errors) and WsExecErrorCode (top-level errors), see the Error Catalog above.
Connection Management
Reconnection Pattern
Reconnect with the usual exponential-backoff-with-jitter pattern any robust WebSocket client should use. The Reya-specific bits are:
No subscription state to replay. There is no session-bound identity to restore — the next request authenticates itself via its EIP-712 signature just like the first one did.
Verify in-flight requests via REST before resubmitting. Closing the WebSocket has zero persistent side effects on the order book itself, but an unanswered request may already have reached the matching engine and created, matched, modified, or cancelled an order. Check
GET /v2/wallet/{address}/openOrders,orderHistory, and the relevant executions endpoint before retrying — otherwise you risk a duplicate action. See Idempotency (clientOrderId) for safe correlation; do not infer settlement merely from request acceptance.
For the meaning of WS close codes you'll see on onclose (1000, 1001, 1006, etc.), see What Happens When the Server Closes the Connection.
Service Restarts
Order Entry service restarts close connections with code 1001; reconcile any unanswered request. The Info WebSocket uses a 1012 resync close. Both are documented in Service restarts.
In-Flight id Uniqueness
Each id must be unique across in-flight requests on the connection. Once the server has emitted the corresponding response envelope, the id is free to reuse. Sending a fresh request with the same id as an in-flight one triggers a top-level DUPLICATE_REQUEST_ID error envelope; the original in-flight request is unaffected.
In practice, clients should generate a fresh id for every request (e.g. UUIDv4 or a monotonic counter prefixed with a session token).
Idempotency (clientOrderId)
For createOrder, the optional clientOrderId field is echoed back unchanged in the successful response and in any subsequent order-update events on the Info WebSocket. Use it for client-side correlation independent of the server-issued orderId — particularly useful when the response envelope is lost mid-flight and the client must reconcile state from Info-WebSocket updates after reconnect.
When supplied with a non-zero value, clientOrderId must be unique among live orders for the same signer wallet on the same market. A duplicate live clientOrderId is rejected with DUPLICATE_CLIENT_ORDER_ID_ERROR. Omit the field when you do not want a client tag; the signer encodes the omitted value as 0. This is live-order deduplication for correlation and safe retries, not exactly-once idempotency across all historical terminal orders.
Signatures and Nonces
All createOrder, modifyOrder, cancelOrder, cancelAll, and cancelAllAfter payloads carry an EIP-712 signature over the order contents. The shape of the signed message is identical to the REST /v2 endpoints — see Signatures and Nonces for the canonical reference. The Python SDK provides the helpers sign_order, sign_cancel_order, sign_mass_cancel, and sign_cancel_all_after to produce these signatures correctly. A modifyOrder reuses the same OrderDetails envelope as createOrder (sign_order), signed over the full post-modify order state with a fresh nonce.
The WebSocket transport does not add or change any signing requirement. Frames are signed by your wallet (or a permissioned trading key) at the payload level; the WebSocket connection itself is unauthenticated.
Differences vs REST
The WebSocket Order Entry surface is functionally equivalent to the corresponding REST endpoints, with transport-level differences:
Request body
CreateOrderRequest / ModifyOrderRequest / CancelOrderRequest / MassCancelRequest / CancelAllAfterRequest
Same — embedded as payload
Response body
CreateOrderResponse / ModifyOrderResponse / CancelOrderResponse / MassCancelResponse / CancelAllAfterResponse
Same — embedded as payload on ok: true
Success / failure
HTTP 200 vs 400 / 500
ok: true vs ok: false inside the frame
Error body
RequestError
Same — embedded as error on ok: false
Correlation
HTTP request/response pairing
Client-supplied id field
Auth
EIP-712 signature in body
Same EIP-712 signature in same body
Idempotency
clientOrderId echoed
Same
Connection
Per-request
Persistent, multiplexed
When to use which:
REST — One-off requests, low frequency, simpler client integration, no need to maintain a long-lived connection.
WebSocket Order Entry — High-frequency order submission, lower per-request overhead (no TLS handshake per request), latency-sensitive integrations. Recommended for latency-sensitive integrations running together with the WebSocket Info API.
Python SDK Example
A worked example is included in the Python SDK 3.6.1.0 at examples/websocket/exec/ws_exec.py — a minimal quickstart that places a resting spot LIMIT GTC order and cancels it through the high-level ReyaWsExecClient (which handles the wire envelope, EIP-712 signing, and in-flight request dispatch for you).
After installing the pinned SDK, run from its checkout:
See the example's README for prerequisites (.env setup, funded test accounts).
Transport failures and retrying
See Handling ambiguous outcomes for the API 3.6.2 error codes and when to retry or reconcile. An unanswered request may have been applied.
Last updated