Skip to main content
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.
Submit orders, cancel orders, and manage trades over the order execution WebSocket. This is the recommended path for programmatic trading when you need real-time feedback: commands and the resulting fill/status events travel on one connection. A machine-readable AsyncAPI 3.1 contract for this surface — every command, every server event, the close codes and the limits — lives at services/order_execution/spec/execution-ws.asyncapi.yaml.

Connection

This uses the same WebSocket connection as Order Updates:
See Order Updates - Connecting for authentication details and connection setup. Once connected, you can send commands as JSON text frames.
For API-key connections, each provider command checks both its required scope and platform access. A disabled provider returns an 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 a type 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 as POST /orders but without CSRF/service token requirements (authentication is established at connection time).
Payload fields:
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.
Success response:
Error response:
After submission, the order flows through the execution pipeline. You will receive status_changed, filled, position_updated, and balance_updated events as the order progresses.
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.
Gotcha: warming is fail-soft. It never rejects your contract-open, so a successful order_response does not promise a live subscription — read the result fields. A rate-limited, streamer-less, or credential-less warm returns {"warmed": false, "reason": "rate_limited" \| "no_fill_streamer"} rather than an error. Fill detection falls back to the polling worker either way.

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 an order_error frame on the same connection, with request_id echoed so you can correlate it to your in-flight command:
The connection stays open after an 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

JSON type 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/unknown type, 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.
After reconnect:
  1. Reconcile open orders with GET /orders?status=live.
  2. 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.
  3. 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 an orders 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_id on all commands to correlate responses. Generate a unique UUID for each command.
  • Use client_order_id for idempotency. If you submit the same client_order_id twice, the second submission returns the existing order instead of creating a duplicate.
  • Check order_error responses for the error_details.code field to programmatically handle specific failure cases (e.g., VALIDATION_INVALID_ORDER, FUNDS_INSUFFICIENT_BALANCE).
  • Don’t assume order success from the order_response. Wait for status_changed events to confirm the order is live or filled.