Prefer WebSocket for order submission. Placing and cancelling orders over the persistent
/ws socket is lower-latency (one round trip instead of the REST submit/poll pair) and simpler to build: one connection, one auth handshake, and pushed order_update/fill events instead of polling. See Order Execution over WebSocket.services/order_execution/spec/execution-ws.asyncapi.yaml.
Connection
This uses the same WebSocket connection as Order Updates:order_error frame with status 403; if platform access cannot be verified, the status is 503. Commands that preserve the structured error include error_details.code: "AUTH_INSUFFICIENT_SCOPE" or "INTERNAL_ERROR" respectively. Status-only command handlers, including cancel_batch_orders, may omit error_details. The connection remains open in every case.
Fee-quote subscriptions use the same platform gate but return a message-only fee_quote_error frame instead of order_error.
Commands
Each financial command includes atype field and a required string
request_id for correlating responses. Control messages such as ping have
their own smaller envelope.
submit_order
Submit a new order via WebSocket. Uses the same payload asPOST /orders but without CSRF/service token requirements (authentication is established at connection time).
Hyperliquid needs three things at once:
outcome, whole-share
quantities, and the side-specific token_id — without the latter it falls
back to the bare-market_id side-0 behaviour. See
Hyperliquid orders for a complete
example and time-in-force mappings.Gotcha:
order_response is not success. The "queued" status means the
order was accepted into the pipeline. Wait for status_changed events to
confirm the order is live or filled.cancel_order
Cancel a single open order.
Requires
trade:execute scope for API key users. Only orders you own can be cancelled. Orders in terminal states (filled, cancelled, expired, failed) cannot be cancelled.
If the order hasn’t reached the exchange yet (no exchange_order_id), it is cancelled locally. Otherwise, a cancel request is sent to the exchange.
A successful cancel triggers a status_changed event with the new status.
cancel_all_orders
Cancel all open orders on an exchange, optionally restricted to a specific market.
Response:
Gotcha: Predict.fun cancel-all is synthetic and non-atomic. It is a list
plus batched per-order cancels (≤100 ids per venue request), so partial
success is possible and
cancelled_count counts venue-confirmed cancels
only — see per-venue semantics.cancel_batch_orders
Cancel a selected set of open limit orders atomically before any venue request is sent.
Requires
trade:execute scope for API key users. The server validates ownership, non-terminal status, exchange homogeneity, and exchange order IDs for the full selection before sending any venue cancel request. Supported venues are Polymarket, Kalshi, Predict.fun, and Hyperliquid.
Response:
Gotcha:
noop_order_ids need reconciliation. They are orders the venue
already considered terminal or absent. The service does not overwrite
ambiguous local fill state for noops; use subsequent order updates or REST
reads for final reconciliation.redeem
Redeem a resolved position on-chain.
Requires
trade:execute plus platform access to exchange_id. The redeem
signs an on-chain transaction with your delegated key, so the connection’s
signing-policy version is also checked; an out-of-date policy returns an
order_error rather than signing. Legacy identity fields (user_id,
turnkey_org_id, wallet_address) are still accepted but are resolved from
the authenticated session and can be omitted.
Completion arrives asynchronously as a
position_redeemed event.
ctf_split / ctf_merge
Split collateral into a full set of conditional tokens, or merge a full set back into collateral.
Same gating as
redeem: trade:execute, platform access, and the
signing-policy version check. Completion arrives as a
ctf_action_completed event.
submit_signed_order
One-round-trip external-signing submit: you send an order you built and signed yourself and the server verifies it (signature recovery plus owner-belongs-to- user) and forwards it to the venue without signing anything.builder_code is required, must match the current public Kairos builder code,
and must be the exact bytes32 value included in the signed EIP-712
Order.builder field. Read polymarket_builder_code from the socket’s initial
connected frame and refresh it after reconnect.
Gotcha: this path is allowlist-gated. A caller that is not enabled for
external signing receives an
order_error with the same status the REST
external-signing endpoint would return. error_details is not populated on
this command.warm_market
Tell the cell which market you just opened so it scopes your live fill subscription to it before you place an order. Purely a latency optimization.
Requires
trade:execute plus platform access to exchange_id, and is rate
limited to 240 warms per minute per user.
Response Types
Errors & Disconnection
Command errors, connection-level failures, and disconnects are documented in detail on the Order Updates → Errors & Disconnection page, since both surfaces share the same WebSocket — including the server-initiated close codes (1000, 1001, 1006) and the reconnect/backoff flow. Quick reference for command-specific behavior:
Command error shape
Every command that fails returns anorder_error frame on the same connection, with request_id echoed so you can correlate it to your in-flight command:
order_error — only the offending command is rejected. Branch on error_details.code (e.g. VALIDATION_INVALID_ORDER, FUNDS_INSUFFICIENT_BALANCE) rather than on the human-readable error string.
Common per-command errors
Unknown command type
JSONtype values outside the server’s accepted command and control-message
sets are silently ignored for forward compatibility. The shared connection
also accepts the RFQ messages subscribe_fee_quote /
unsubscribe_fee_quote (see Fee Quote (RFQ)) and the
order-updates surface’s ping.
Malformed frames
Invalid JSON, a missing/unknowntype, and unknown message types are silently
ignored. If a recognized command has an invalid envelope — most commonly a
missing or non-string request_id — the server returns:
Disconnect handling
Gotcha: on disconnect, in-flight commands have indeterminate state. The
server may have already submitted the order before the connection dropped.
- Reconcile open orders with
GET /orders?status=live. - If you used
client_order_id, re-submitting the same command is safe (idempotent) — the server returns the existing order rather than creating a duplicate. - The server does not replay missed events; pull anything authoritative from REST.
Limits
Order submissions are limited to 5 per second per user, shared between this WebSocket and REST; an API key carrying anorders override gets its own
budget on top, over that same one-second window — not a per-minute one. Connection-level limits (max connections per user,
max frame size) are on
Order Updates → Limits.
WebSocket vs REST
Both the WebSocket and REST API (POST /orders, POST /orders/{id}/cancel) support the same order operations. Key differences:
Recommendation: Use WebSocket for programmatic trading bots and real-time UIs. Use REST for simple one-off operations or integrations that don’t need streaming updates.
Best Practices
- Use
request_idon all commands to correlate responses. Generate a unique UUID for each command. - Use
client_order_idfor idempotency. If you submit the sameclient_order_idtwice, the second submission returns the existing order instead of creating a duplicate. - Check
order_errorresponses for theerror_details.codefield to programmatically handle specific failure cases (e.g.,VALIDATION_INVALID_ORDER,FUNDS_INSUFFICIENT_BALANCE). - Don’t assume order success from the
order_response. Wait forstatus_changedevents to confirm the order isliveorfilled.

