Skip to content

Risk channel

/ws/risk is the primary programmatic surface. It is bidirectional: the server pushes state, and you can act on the same socket instead of falling back to REST.

Nothing is pushed until you Subscribe to the topics you want.

Frame Carries
Account-State-Update The account’s aggregate state.
Balance-Update / Margin-Update / Funding-Update / Risk-Update Balance, margin, funding, and risk as they change.
Position-Update / Position-Removed / Position-Snapshot Live position state; the snapshot seeds your view.
Open-Orders-Update The working-order book for the account.
Order-Intent / Order-Intent-Update Order lifecycle, from composed to terminal.
Order-Update / Trade-Update Order state and closed round trips, in the external vocabulary.
Fill One frame per execution — never coalesced, so nothing is collapsed away.
Account-Event Lifecycle events on the account.
Prop-Account-Update Challenge progress and status on a prop account.
Blocking-Update, IP-Update, LoggedOff Session-level notices.
Resync-Required Drop local state and re-snapshot.

There is no server-pushed bracket frame. TP/SL leg state reaches you through Open-Orders-Update and Order-Intent-Update like any other working order.

Read requests — Get-Margin, Get-Balance, Get-Risk-Update, Get-Position-Update, Get-open-orders, Request-Trade-History — plus Subscribe / Unsubscribe to change topics and scope, and Resume to replay after a gap.

Write frames mirror the REST OMS: Order-Sign-Result, Cancel-Order-Intent, Replace-Order-Intent, Bracket-Insert, Bracket-Modify, Bracket-Cancel, Position-Close. Each is tier-gated.

There is no place-order frame. New orders start at POST /api/v1/oms/intents; the socket carries the signature back (Order-Sign-Result) and then everything that happens to the order afterwards. Acting on a live order over the socket saves a round trip on a hot path; REST is fine everywhere else and easier to retry safely.

Position-Close closes the whole position — passing qty is rejected with partial_close_unsupported — and it accepts only hyperliquid, aster and polymarket. Flatten anything else through POST /api/v1/oms/flatten.

Every pushed frame that leaves the per-socket outbox carries a monotonic integer seq, starting at 1 for the life of the connection.

if (frame.seq !== undefined) {
if (lastSeq !== null && frame.seq !== lastSeq + 1) {
await resnapshot() // a frame was dropped or coalesced away
}
lastSeq = frame.seq
}

A gap means one or more frames were dropped or coalesced — the server merges rapid updates for the same key under load. Re-request the relevant Get-* snapshot rather than trying to reconstruct the missing step.

Control frames sent outside the outbox — Pong and the Authenticated handshake — are deliberately unsequenced. Only assert contiguity over frames that carry seq. A new connection restarts the lane at 1.

  1. Subscribe { topics: ["Order-Intent-Changed"] } before you place, so no transition happens while you are unsubscribed.
  2. POST /api/v1/oms/intents returns an intent id.
  3. Order-Intent-Update carries the venue order id, then the fills, then the terminal state. The stream is account-scoped, not per-intent — there is no intent.<id> topic — so match each frame on the intent id you were handed.

This is the intended pattern — polling an order endpoint in a loop will always lag the socket and costs you rate limit.