Orders

Place, cancel, and query orders on the 4rho prediction market CLOB.

Place Order

POST /v1/orders

Scope: trade:orders

Placing orders requires both HMAC API key authentication (request headers) and an EIP-712 signature (request body). See the EIP-712 Signing guide for prerequisites and examples.

Sign the correct fee. fee_rate_bps is part of the signed order. It MUST equal the market's canonical effective fee or placement is rejected 422 (fee_rate_bps mismatch), and a resting order silently stops matching if the canonical fee later moves (#3672). Read GET /v1/market-data/:market_id/fee-rate and sign maker_fee_bps (currently 50 platform-wide). Details: Fees.

Request Body

{
  "market_id": "550e8400-e29b-41d4-a716-446655440000",
  "token_id": "12345678901234567890",
  "side": "BUY",
  "maker_amount": "1000000",
  "taker_amount": "2000000",
  "maker": "0xYourWalletAddress",
  "signer": "0xYourWalletAddress",
  "taker": "0x0000000000000000000000000000000000000000",
  "salt": "123456789",
  "nonce": 0,
  "expiration": 0,
  "fee_rate_bps": 50,
  "signature": "0x...",
  "outcome_index": 0,
  "time_in_force": "GTC",
  "post_only": false
}
FieldTypeRequiredDescription
market_idUUIDYesMarket to trade on
token_idstringYesERC-1155 token ID for the outcome
sidestringYesBUY or SELL
maker_amountstringYesAmount the maker provides (wei)
taker_amountstringYesAmount the maker receives (wei)
makerstringYesMaker wallet address
signerstringYesSigner wallet address
takerstringYesMust be the zero address - directed orders are not supported
saltstringYesRandom salt for order uniqueness
nonceintYesOrder nonce
expirationintNoUnix timestamp (0 = no expiry)
fee_rate_bpsintYesFee rate in basis points
signaturestringYesEIP-712 signature (signing guide)
outcome_indexintYesOutcome index (0 = Yes, 1 = No)
time_in_forcestringNoGTC, GTD, FOK, FAK (default: GTC)
post_onlyboolNoReject if would cross the book

Response

{
  "order_id": "550e8400-e29b-41d4-a716-446655440000",
  "order_hash": "0xabc123...",
  "status": "open",
  "matches": 0
}
StatusDescription
openResting on the book, no fills
partially_filledSome fills, remainder resting
filledFully filled

Cancel Order

DELETE /v1/orders/:hash

Scope: trade:orders

Cancels an open or partially filled order by its order hash.

Response

{ "message": "order cancelled" }

Get Order

GET /v1/orders/:hash

Scope: read:account

Returns the full order details including current status and fill amounts.

Response

{
  "id": "550e8400-...",
  "order_hash": "0xabc123...",
  "market_id": "...",
  "side": "BUY",
  "price": "0.50000000",
  "status": "open",
  "remaining": "1000000",
  "filled_amount": "0",
  "time_in_force": "GTC",
  "post_only": false,
  "created_at": "2024-03-01T00:00:00Z"
}

Get Open Orders

GET /v1/orders/open/:market_id

Scope: read:account

Returns all open and partially filled orders for the authenticated user in a specific market.

Get Account Orders

For account-wide filtered order history use GET /v1/user/orders/v2 (read:account). Its order DTO adds nullable integer-string USDC fields initial_limit_notional_usdc_micro, filled_limit_notional_usdc_micro, and remaining_limit_notional_usdc_micro. Divide by 1,000,000 for dollar display. These are signed-limit projections, not actual match proceeds: BUY uses maker-asset USDC; SELL uses the canonical cumulative integer-floor conversion. Missing or invalid lifecycle evidence is null, never a fabricated zero. An explicit "0" is known zero. Initial normally equals filled + remaining; cancelled partial FAK orders instead report zero remaining for the killed unfilled portion, so that terminal shape does not conserve the original total.

Get Order Book

GET /v1/orders/book/:market_id?outcome_index=0&levels=20

Public - No authentication required.

ParameterTypeDefaultDescription
outcome_indexint0Outcome index (0 or 1). Ignored when both is set.
levelsint20Max price levels per side (1-100).
bothbool-When true (or 1), return both outcome books in one response - see below.

Each level carries price (USDC per token), num_orders, and two explicit decimal-string integer amounts:

FieldMeaning
token_quantity_atomicOutcome-token quantity in 6-decimal atomic units.
notional_usdc_microCollateral notional in micro-USDC; use for dollar depth.
sizeDeprecated maker-denominated remainder, preserved without redefining it. Synthetic ask levels can mix BUY collateral and SELL tokens; it is not a canonical token or dollar amount.

Canonical amounts come from the signed order ratios and cumulative fills, not rounded display prices. If any contributor lacks exact evidence, both canonical fields are omitted: unknown is not zero. A known zero is "0". Do not substitute size for a missing canonical field.

Response

{
  "market_id": "...",
  "outcome_index": 0,
  "bids": [
    { "price": "0.50", "size": "5000000", "token_quantity_atomic": "10000000", "notional_usdc_micro": "5000000", "num_orders": 1 }
  ],
  "asks": [
    { "price": "0.60", "size": "14000000", "token_quantity_atomic": "20000000", "notional_usdc_micro": "12000000", "num_orders": 2 }
  ]
}

Both outcomes in one request

GET /v1/orders/book/:market_id?both=true&levels=20

Pass both=true (or both=1) to get outcome 0 and outcome 1 in a single response. outcome_index is ignored. For a market maker quoting two-sided this is one rate-limited request instead of two. The keys are outcome_0 and outcome_1:

{
  "market_id": "...",
  "outcome_0": {
    "bids": [ { "price": "0.50", "size": "5000000", "token_quantity_atomic": "10000000", "notional_usdc_micro": "5000000", "num_orders": 1 } ],
    "asks": [ { "price": "0.60", "size": "14000000", "token_quantity_atomic": "20000000", "notional_usdc_micro": "12000000", "num_orders": 2 } ]
  },
  "outcome_1": {
    "bids": [ { "price": "0.40", "size": "4000000", "token_quantity_atomic": "10000000", "notional_usdc_micro": "4000000", "num_orders": 1 } ],
    "asks": [ { "price": "0.50", "size": "5000000", "token_quantity_atomic": "10000000", "notional_usdc_micro": "5000000", "num_orders": 1 } ]
  }
}

The YES ask above combines a direct SELL of 10 tokens at 0.60 and an opposite BUY of 4 USDC at 0.40. Together they represent 20 YES tokens / 12 USDC, not the 14 mixed units in legacy size.

Current WebSocket book_delta frames describe individual order events; they cannot replace or remove a whole price level. Coalesce them into bounded REST refreshes of both outcomes, and retain periodic/reconnect recovery for dropped frames. Depth aggregates do not prove your order's FIFO or direct/complement queue position.

Cancel-Replace

POST /v1/orders/cancel-replace

Scope: trade:orders

Atomically cancels an existing order and places a new one. If the cancellation fails, the new order is not placed.

Request Body

{
  "cancel_hash": "0xoriginal_order_hash...",
  "new_order": {
    "market_id": "...",
    "side": "BUY",
    "maker_amount": "1500000",
    "taker_amount": "3000000",
    ...
  }
}

Amend Order

PUT /v1/orders/:hash/amend

Scope: trade:orders

Re-price or re-size a resting order. Because maker_amount and taker_amount are part of the signed EIP-712 order, any change to the amounts requires a freshly signed replacement - supply it as new_order (a full Place Order body). The server cancels the old order and places the re-signed one atomically (a cancel-replace). A price change or quantity increase loses time priority.

The bare maker_amount / taker_amount fields are retained for backwards compatibility but now reject any actual amount change with 422 AMEND_RESIGN_REQUIRED - they can no longer silently invalidate a signature. Always use new_order to re-price or re-size.

Request Body

{
  "new_order": {
    "market_id": "market-id-1",
    "outcome_index": 0,
    "maker": "0xYourWallet",
    "maker_amount": "2000000",
    "taker_amount": "4000000",
    "expiration": 0,
    "nonce": "1718900000000",
    "signature": "0x... (EIP-712 signature over the NEW amounts)"
  }
}

Response

{
  "order_hash": "0xnewhash...",
  "status": "open",
  "price": "0.50000000",
  "maker_amount": "2000000",
  "taker_amount": "4000000",
  "remaining": "2000000",
  "time_priority": false,
  "price_changed": true
}