Most rejections happen before the order reaches the book, so a
400 means nothing was placed.
markets-service verifies your signature at submission — see Authentication and
signing. An order whose signature does not authorize its
action is rejected here rather than failing later onchain.Signature
400
The signature did not recover to
signer_address, and the signer is not a contract whose
isValidSignature accepted it.The digest is over the Action struct exactly as described in
Authentication and signing — domain Matching version 1.0, bound to the chain and
the Matching contract. A mismatch in any signed field produces a different digest and so a
different recovered address. Check that action_json is byte-for-byte what you signed, and that
signer_address is the key that signed it rather than the account that owns it.A signature that cannot be checked — for example an RPC failure while resolving a contract
signer — is not a rejection. The order is accepted and the failure is logged, so a transient
outage never blocks a valid order.
Missing fields
Every one of these is required onPOST /v1/orders.
Action payload shape
These come from decodingaction_json.data, the ABI-encoded TradeData tuple.
400
data is not exactly seven 32-byte words. After the 0x prefix it must be 448 hex
characters. See Encoding data.400
data is the right length but not valid hex.400
A wrapper around the field-level failures below. The suffix names the specific problem.
Field consistency
The flat order fields and the signedaction_json describe the same order, so they must agree.
Rebuild both from one set of values rather than editing one of them.
Scaling
These are the most common rejections, and they almost always mean the body was sent in wei.400
data.limitPrice must be a positive whole multiple of the body’s limit_price after tick
normalization. The body takes human decimals; only data carries wei.400
Same rule for size, normalized against the instrument’s minimum size.
400
limitPrice is a signed int256 but zero and negative values are rejected.400
Same for
desiredAmount.400
desired_amount is smaller than the instrument’s minimum size, so it normalizes to nothing.400
desired_amount is not a whole multiple of the instrument’s minimum size.Amounts and expiry
Instrument
400
The
(asset_address, sub_id) pair is not an enabled market. Read GET /v1/markets — both
values must come from the same entry.400
The
symbol or asset_address + sub_id on a read endpoint did not resolve.400
These two fields apply to the spot market only. Omit them on futures orders.
Spot order translation
These apply toUSDCcNGN-SPOT only. Both fields are optional — omit them and your raw engine
values are used as sent. Send either one and the rules below apply. See the
Spot market guide for the units involved.
400
You sent
ui_intent, or an order_entry_spec with a different value. The spec string must be
exactly usdc_cngn_spot_v1.400
The two fields travel together. Send both, or neither.
400
You sent both
side and ui_intent, and they disagree. Engine side is the inverse of
ui_intent.side. Omit side to let the service derive it.400
You sent both
limit_price and ui_intent, and they disagree. The engine price is
1 / ui_intent.price. Omit limit_price to let the service derive it.400
You sent both
desired_amount and ui_intent, and they disagree. The engine amount is
ui_intent.size * ui_intent.price. Omit desired_amount to let the service derive it.Duplicate orders
409
order_id has already been submitted. Generate a fresh one; do not retry with the same id.order_id as an idempotency key you control. A 409 means the first submission was
accepted, so check its status with GET /v1/orders/{order_id} rather than resubmitting.
Cancelling
Read endpoints
unknown market applies to GET /v1/book, GET /v1/trades, and GET /v1/candles alike.
Authentication and signing
Most rejections trace back to how the action payload was built.

