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.
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:
POST /api/v1/trading/actions/hashwith an unsigned payload.- Sign the returned
hashwith the wallet. POST /api/v1/trading/actionswith the returnedpayloadand the walletsignature.
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):
| Mode | Aliases | Behavior |
|---|---|---|
ack | Summary, compact, conflated | Returns the configured ingress acknowledgement with minimal body data. Reconcile terminal state from the stream. |
durable | Detailed, verbose where enabled | Waits for the configured durable boundary and returns acknowledgement metadata. |
full | Full, terminal | Waits 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.
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):
IngressAckMode | ack_class |
|---|---|
Accepted | accepted |
IngressWal | ingress_wal |
Applied | execution_ack |
PrimaryDurable | primary_durable |
Durable | cluster_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
actionResultspopulated (an array ofHotpathOrderResult, 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, withactionResultsnull 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.
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).
| Field | Type | Notes |
|---|---|---|
seq | u64 | Sequence assigned at ingress. |
clientOrderId | string? | Echoes the signed clientOrderId (cloid) when present. |
orderId | string? | Derived order id (omitted for non-order actions). |
status | string | One of filled, partially_filled, resting, canceled, applied, rejected. |
rejectCode | string? | Machine-readable reject code (rejections only). |
rejectReason | string? | Human-readable reject detail (rejections only). |
market | u64? | Market id (omitted for non-order actions). |
book | string? | Book / outcome where applicable. |
side | string? | Bid / Ask. |
price | u64? | Limit price in micro-units. |
shard | usize? | Routed shard index. |
filledQty | u128 | Filled quantity (0 in the degraded fallback). |
leavesQty | u128? | Remaining resting quantity. |
avgPx | u64? | Average fill price in micro-units. |
feeMicro | u128 | Fee in micro-units (0 in the degraded fallback). |
fills | array | HotpathFillResult[]; empty when no fills. |
engineTsMs | u64 | Engine apply timestamp (ms). |
serverTsMs | u64 | Server 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:
| Family | API input fields |
|---|---|
Public place-order envelopes: SpotPlaceOrder, OutcomePlaceOrder | marketId, timeInForce, isMarket, reduceOnly, expiresAt |
Raw advanced envelopes: Cancel, AmendOrder, QuoteReplace, SpotQuoteReplace, PlaceAlgoOrder, PlaceConditionalOrder | market, 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.