Skip to main content
A limit order is a resting instruction: buy (or sell) a weight of one symbol when your price reaches your trigger_price. When it does, Bullionwale books it as a market order at that moment’s price, if your weight allocation still has room for it then. You place it with POST /limit-orders, follow it with GET /limit-orders/{limit_order_id}, list your limits with GET /limit-orders, change it with PATCH /limit-orders/{limit_order_id} and cancel it with DELETE /limit-orders/{limit_order_id}. For a walkthrough, see the Limit orders guide. Each path sits under the test base path /api/trading/test/v1 or the production base path /api/trading/v1: Test limit orders are simulated and never execute: they are checked exactly like real ones, then rest until you cancel them. Production limit orders are real, and production trading is switched off today: every production write answers 403 production_trading_disabled (see Production access requirements). Reads keep working in both environments. Limit orders are offered per platform. Where they are not offered, every call on this page answers 404.

The limit order object

Both environments return these fields: Production limit orders add:

Statuses

A production limit order moves pending → triggered or pending → cancelled. When Bullionwale hedges it, it is confirming first while it is being placed, and again while it is being cancelled. A test limit order is only ever pending or cancelled. Edit and cancel only a pending limit order. A confirming one answers 409 limit_order_confirming: read it again after a few seconds. A triggered or cancelled one answers 409 limit_order_not_pending.

How a limit order triggers

Production limit orders are checked against the live price every few seconds: “Your price” is the price you see in Market data, with your account’s pricing applied. When it triggers, the order is booked at that moment’s price, not at trigger_price: if the price moved past your trigger between two checks, a buy books at the lower price and a sell at the higher one. Your weight allocation is not reserved while a limit order rests. It is checked when the order triggers, together with the other conditions a market order must meet then: trading on for the symbol and for your account, the quantity rules, and stock. If any of them fails at that moment, the limit order is cancelled, not kept waiting, and cancel_reason says why, for example not enough free limit when the price triggered. Place it again once the reason is resolved. While it rests, blocked_reason warns you in advance when one of these would stop it. Some conditions only make it wait, without cancelling it: no live or confirmed price for the symbol, a frozen price, or a trading halt. It keeps resting and triggers once the price is live again.

Place a limit order

POST /limit-orders takes an Idempotency-Key header (required: 1 to 64 characters, a new UUID per limit order, reused unchanged on every retry; see Idempotency and safe retries). Keys for limit orders are separate from keys for market orders, and are remembered per account. Placement runs the same symbol, side, trading-halt, quantity and pricing checks as a market order, except your weight allocation and stock, which are checked when it triggers.
201 Created
A trigger the market has already reached is refused, and the message names your current price:
400 Bad Request
The production request is the same, on /api/trading/v1/limit-orders with a live key. What comes back differs:
202 Accepted
409 Conflict

Read one limit order

GET /limit-orders/{limit_order_id} returns one of your limit orders with its current status. One that is not on your account, or not in this environment, answers 404 limit_order_not_found. This example places a test limit order and reads it back:
Python
Once a production limit order is triggered, read the order it became with GET /orders/{order_id}, using its order_id.

List your limit orders

GET /limit-orders takes status (pending, triggered, cancelled or all, the default), limit (1 to 200, default 50), offset, symbol_code, trade_type and since (ISO 8601; a time without a zone is UTC), and returns items newest first plus total, the number of matching limit orders. status=pending is the resting queue, confirming limit orders included. In production it lists your account’s limit orders from every channel: the app, the web and the API. source says which were placed through the API, and only those carry your client_order_id. In test it lists your test limit orders.
200 OK

Edit a limit order

PATCH /limit-orders/{limit_order_id} takes a new trigger_price, a new quantity, or both. A body with neither is refused with 422 invalid_request. The new values face the placement’s rules: the price must still wait (400 limit_would_execute), the quantity must meet the quantity rules, and a larger quantity must fit your weight allocation now (400 limit_exceeded; in test, against your test book). It answers the limit order with its new values. PATCH sets absolute values, so sending the same edit twice leaves the same result. It takes no Idempotency-Key.
200 OK

Cancel a limit order

DELETE /limit-orders/{limit_order_id} cancels a pending limit order and answers it, cancelled. In production:
503 Service Unavailable
A cancel that arrives as the price triggers is settled one way or the other: either the cancel wins and the limit order is cancelled, or the order is booked first and the cancel answers 409 limit_order_not_pending. After a 409, read the limit order: if it is triggered, order_id names the order that was booked.
409 Conflict
This example places a test limit order, moves its price, then cancels it:
Python

Events

Production limit orders send the webhook events limit_order.placed (it is resting, after any confirmation), limit_order.triggered (it was booked; the event names the order) and limit_order.cancelled. Test limit orders send none. See the Event catalog.

Common refusals

The full list, with what to do for each, is in the Error reference.