Skip to main content

Raw Signed Actions

This page documents the low-level API input envelopes accepted by POST /api/v1/trading/actions/hash and POST /api/v1/trading/actions.

API input is not canonical signing JSON

The examples on this page may use documented camelCase aliases such as marketId, timeInForce, and nonceReservationId. Do not hash these examples directly for local signing. Local signing must use the canonical snake_case, declaration-order encoding documented in Local Action Signing.

The compatibility/debug wallet flow is a two-step hash-and-submit round trip:

  1. POST /api/v1/trading/actions/hash with an unsigned payload.
  2. Sign the returned hash with the wallet.
  3. POST /api/v1/trading/actions with the returned payload and the wallet signature.

In this flow, sign the returned hash exactly. Do not reserialize the returned payload and hash it yourself. Response JSON exists for transport/debugging; it is not the canonical local-signing preimage.

Latency-sensitive integrators should skip the hash round trip and use the TypeScript SDK signAction() helper, or implement the canonical encoder from Local Action Signing.

Validation status

The following action variants are documented for raw HTTP use: Cancel, AmendOrder, SpotQuoteReplace, QuoteReplace, PlaceAlgoOrder, CancelAlgoOrder, PlaceConditionalOrder, and CancelConditionalOrder.

Use only the fields listed on this page. The current 0.1.3 parsers accept the documented camelCase aliases and canonicalize them back to snake_case for signing. Unknown fields are not a compatibility contract; clients must not rely on the server retaining or interpreting undeclared fields.

AmendOrder is quantity-only by design (it keeps time priority). Use SpotQuoteReplace / QuoteReplace to reprice; do not send new_price or newPrice.

Envelope

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"nonceReservationId": null,
"ts": 1781190000000,
"action": {
"SpotPlaceOrder": {
"marketId": 1,
"side": "Bid",
"price": 1000000,
"qty": 100000,
"timeInForce": "post_only",
"isMarket": false,
"reduceOnly": false
}
}
}

nonce: 0 is only a convenience for POST /actions/hash: when no nonceReservationId is present, the hash endpoint replaces it with the current account nonce in the returned payload. Do not submit locally signed actions with nonce: 0 unless 0 is actually the next usable nonce for the account. If you reserved a nonce range, send the explicit nonce and nonceReservationId before hashing/signing.

Submit the signed response payload:

{
"payload": {
"account": "0x1111111111111111111111111111111111111111",
"nonce": 42,
"nonceReservationId": null,
"ts": 1781190000000,
"action": {
"SpotPlaceOrder": {
"marketId": 1,
"side": "Bid",
"price": 1000000,
"qty": 100000,
"timeInForce": "post_only",
"isMarket": false,
"reduceOnly": false,
"expiresAt": null
}
}
},
"signature": {
"scheme": "EcdsaSecp256k1",
"bytes": [1, 2, 3]
}
}

Use Idempotency-Key on submit requests that may be retried.

Response contract

The order-entry batch response is governed by two orthogonal axes. Do not conflate them: verbosity controls how much of the response body you get back; the ack mode controls how far the request progressed before the server answered.

Axis 1 - BSL result mode

Selected per request via the preferred BSL HTTP header x-bsl-result-mode (alias x-bsl-response-mode; legacy aliases x-mm-response-mode and x-mm-response-detail):

ModeAliasesBehavior
ackSummary, compact, conflatedReturns the configured ingress acknowledgement with minimal body data. Reconcile terminal state from the stream.
durableDetailed, verbose where enabledWaits for the configured durable boundary and returns acknowledgement metadata.
fullFull, terminalWaits for engine apply and returns per-order actionResults (real fills). See below.

Legacy verbosity is gated by the mm_api.action_batch_response_conflation_enabled server flag. When that flag is off, the server may force Detailed regardless of the legacy header.

Unrecognized header values fall back to ack / legacy Summary.

Result modes are not ack classes

ack / durable / full are BSL result modes. The exact durability boundary is still reported by the separate ack axis below.

Axis 2 - Durability / ack mode

The IngressAckMode axis controls how far a batch must progress before the server responds. Each maps to an ack_class string (also exposed via x-bsl-ack-* headers, with x-mm-ack-* retained as legacy aliases):

IngressAckModeack_class
Acceptedaccepted
IngressWalingress_wal
Appliedexecution_ack
PrimaryDurableprimary_durable
Durablecluster_durable

BSL full mode semantics

Requesting BSL full result mode forces ack_mode = Applied and makes the request synchronously wait for the engine to apply every seq in the batch, bounded by durable_ack_timeout_ms:

  • Success → HTTP 200 with actionResults populated (an array of HotpathOrderResult, one per accepted action, in submission order).
  • Dropped (an action was rejected before/at apply) → HTTP 409.
  • Timeout (apply did not complete within durable_ack_timeout_ms) → HTTP 504, with actionResults null or partial.

actionResults entries are populated from the engine ActionApplyResult (trades plus execution events), so a place order can return its real fills, average price, fee, and resting leaves in the submit response.

Degraded fallback

If a terminal receipt is present but lacks an order result, the entry is filled in defensively from the receipt: filledQty is 0, feeMicro is 0, and fills is empty, with status/rejectCode/rejectReason derived from the receipt. This is a defensive fallback, not the common case — treat zeroed fill fields cautiously when reconciling.

HotpathOrderResult schema

All wire field names are camelCase. Optional fields are omitted when absent (for non-order actions, orderId / leavesQty / avgPx / market / shard are omitted).

FieldTypeNotes
sequ64Sequence assigned at ingress.
clientOrderIdstring?Echoes the signed clientOrderId (cloid) when present.
orderIdstring?Derived order id (omitted for non-order actions).
statusstringOne of filled, partially_filled, resting, canceled, applied, rejected.
rejectCodestring?Machine-readable reject code (rejections only).
rejectReasonstring?Human-readable reject detail (rejections only).
marketu64?Market id (omitted for non-order actions).
bookstring?Book / outcome where applicable.
sidestring?Bid / Ask.
priceu64?Limit price in micro-units.
shardusize?Routed shard index.
filledQtyu128Filled quantity (0 in the degraded fallback).
leavesQtyu128?Remaining resting quantity.
avgPxu64?Average fill price in micro-units.
feeMicrou128Fee in micro-units (0 in the degraded fallback).
fillsarrayHotpathFillResult[]; empty when no fills.
engineTsMsu64Engine apply timestamp (ms).
serverTsMsu64Server response timestamp (ms).

Each fills entry (HotpathFillResult) carries: fillId, market, orderId?, makerOrderId, takerOrderId, role, side?, price, qty, feeMicro, ts.

Example Full-mode success body:

{
"ok": true,
"responseMode": "full",
"ackClass": "execution_ack",
"seqs": [4810],
"derivedOrderIds": ["0xaaaa...aaaa"],
"actionResults": [
{
"seq": 4810,
"clientOrderId": "mm-quote-17",
"orderId": "0xaaaa...aaaa",
"status": "partially_filled",
"market": 1,
"side": "Bid",
"price": 1000000,
"shard": 0,
"filledQty": 40000,
"leavesQty": 60000,
"avgPx": 999950,
"feeMicro": 120,
"fills": [
{
"fillId": "...",
"market": 1,
"makerOrderId": "0xbbbb...bbbb",
"takerOrderId": "0xaaaa...aaaa",
"role": "taker",
"side": "Bid",
"price": 999950,
"qty": 40000,
"feeMicro": 120,
"ts": 1781190000123
}
],
"engineTsMs": 1781190000123,
"serverTsMs": 1781190000125
}
]
}

clientOrderId (cloid) propagation

clientOrderId is part of the signed ActionPayload when present: it is folded into both the signing hash and the derived orderId. It propagates into HotpathOrderResult and the drop-copy stream for direct-HTTP and FIX orders.

On the agent / delegated submission path the cloid is not carried into the engine ActionPayload (it is None there); for a single place-order (not agent batches) it survives only in an off-engine in-memory sidecar used to fill the drop-copy. Consequently dropCopy.clientOrderId is None for agent orders.

The cloid is not stored in order state or the state_root (the Order struct has no client_order_id field); it only influences the derived orderId. The separate Idempotency-Key header still exists for response dedup and is unrelated to the cloid.

API input casing vs canonical signing casing

0.1.3 accepts two API input families:

FamilyAPI input fields
Public place-order envelopes: SpotPlaceOrder, OutcomePlaceOrdermarketId, timeInForce, isMarket, reduceOnly, expiresAt
Raw advanced envelopes: Cancel, AmendOrder, QuoteReplace, SpotQuoteReplace, PlaceAlgoOrder, PlaceConditionalOrdermarket, order_id, new_qty, time_in_force, is_market, reduce_only, expires_at

The server accepts documented camelCase aliases for advanced fields in 0.1.3 and canonicalizes them internally. For deterministic client code, prefer the fields shown below for API input. For local signing, use the SDK LocalActionPayload shape or the canonical snake_case field order in Local Action Signing.

Spot Place Order

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"ts": 1781190000000,
"action": {
"SpotPlaceOrder": {
"marketId": 1,
"side": "Bid",
"price": 1000000,
"qty": 100000,
"stpMode": "cancel_maker",
"timeInForce": "post_only",
"isMarket": false,
"reduceOnly": false,
"expiresAt": null
}
}
}

Spot orders do not carry book or outcome.

Prediction Place Order

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"ts": 1781190000000,
"action": {
"OutcomePlaceOrder": {
"marketId": 10,
"outcome": "YES",
"side": "Bid",
"price": 520000,
"qty": 100000,
"timeInForce": "gtc"
}
}
}

book is still accepted as a deprecated alias for outcome on prediction place orders.

Cancel

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"ts": 1781190000000,
"action": {
"Cancel": {
"order_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
}
}
}

orderId is accepted as an alias.

Amend Quantity

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"ts": 1781190000000,
"action": {
"AmendOrder": {
"order_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"new_qty": 50000
}
}
}

Current amend semantics only allow quantity reduction while keeping priority. Do not send new_price; use quote replace for repricing.

Spot Quote Replace

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"ts": 1781190000000,
"action": {
"SpotQuoteReplace": {
"market": 1,
"legs": [
{
"cancel_order_id": "0xaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
"side": "Bid",
"price": 999900,
"qty": 100000,
"time_in_force": "post_only",
"is_market": false,
"reduce_only": false
},
{
"side": "Ask",
"price": 1000100,
"qty": 100000,
"time_in_force": "post_only"
}
]
}
}
}

Spot quote legs do not carry book. A leg with cancel_order_id and qty: 0 is cancel-only. A leg without cancel_order_id is place-only.

Prediction Quote Replace

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"ts": 1781190000000,
"action": {
"QuoteReplace": {
"market": 10,
"legs": [
{
"cancel_order_id": "0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb",
"book": "YES",
"side": "Bid",
"price": 510000,
"qty": 100000,
"time_in_force": "post_only"
},
{
"book": "NO",
"side": "Ask",
"price": 480000,
"qty": 100000,
"time_in_force": "post_only"
}
]
}
}
}

Quote replace is atomic at engine level. If any leg is rejected, the engine rolls back the full group. New child orders are derived from the parent action and leg index; replacing an order creates a new child order and does not preserve the canceled order's queue priority.

Algo Order

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"ts": 1781190000000,
"action": {
"PlaceAlgoOrder": {
"market": 10,
"book": "YES",
"side": "Bid",
"total_qty": 1000000,
"limit_price": 510000,
"time_in_force": "gtc",
"strategy": {
"kind": "twap",
"slice_qty": 100000,
"interval_ms": 5000
}
}
}
}

Supported strategies are iceberg, twap, and vwap.

Conditional Order

{
"account": "0x1111111111111111111111111111111111111111",
"nonce": 0,
"ts": 1781190000000,
"action": {
"PlaceConditionalOrder": {
"market": 10,
"book": "YES",
"side": "Ask",
"qty": 100000,
"limit_price": 700000,
"trigger_price": 690000,
"trigger_direction": "at_or_above",
"conditional_kind": "take_profit",
"reduce_only": true,
"signed_trigger_order": {
"payload": {
"account": "0x1111111111111111111111111111111111111111",
"nonce": 43,
"ts": 1781190000000,
"action": {
"OutcomePlaceOrder": {
"marketId": 10,
"outcome": "YES",
"side": "Ask",
"price": 700000,
"qty": 100000,
"reduceOnly": true
}
}
},
"signature": {
"scheme": "EcdsaSecp256k1",
"bytes": [1, 2, 3]
}
}
}
}
}

The nested signed_trigger_order must already be signed. The outer conditional action controls storage and trigger conditions.