Skip to main content
A network timeout does not tell you whether your order was booked. The Idempotency-Key header lets you send the same order again without any risk of booking it twice.

The rules

POST /orders requires an Idempotency-Key header of 1 to 64 characters. Without it (or with an empty value) the order is refused with 400 idempotency_key_required; a value longer than 64 characters is refused with 422 invalid_request.
  • Use a new value per order. A UUID is a good choice. Save it with your own order record before you send the request.
  • Reuse the same value when you retry that order, after a timeout, a dropped connection, a 429 or a 503.
  • Same key and same body: no second order. You get the existing order back, with status 200 and the header Idempotent-Replayed: true. The body shows the order’s current state, which may have moved on since the first answer.
  • Same key, and the hedge refused the order (production): a replay answers 409 order_rejected again, with the header Idempotent-Replayed: true and the same detail.order_id and detail.order. Nothing is booked; place a new order with a new key if you still want it.
  • Same key and a different body (a different symbol_code, trade_type, quantity, expected_price or client_order_id): refused with 422 idempotency_key_reused. Nothing is booked.
  • Keys are remembered per account, not per API key. A retry sent with a new key after a rotation still finds the order the old key placed.
  • Two requests at the same moment with the same key book one order.
  • A retry counts against your order rate limit, like any POST /orders. On 429 rate_limited, wait the number of seconds in the Retry-After header, then retry with the same key. Never create a new key to get around a 429.

A retry loop

This sends one order and retries it safely. The key is created once, before the first attempt, and every attempt reuses it.
Python
201 Created
A 409 booking_conflict also asks for a retry with the same key. Any other 4xx is a definitive refusal: fix the request before sending a new order with a new key.

First answers to a production order

Test orders answer 201 the first time and 200 on a replay. Production orders can answer: When an order needs hedge confirmation, expect 202 first. A refusal by the hedge then shows up later, as status rejected in GET /orders/{order_id}, or as 409 order_rejected when you replay the same key. This is a production order and its first answer (the request is the same as in test, on the production path with a live key):
201 Created

Outcomes after a failure

If a production booking fails after you sent it, the answer reflects what was actually recorded, never a bare server error: Treat booking_not_completed as “not booked yet”, not as final. In a rare case (the connection dropped at the moment the order was being saved) the order can exist after all. The same-key retry settles it: it returns the order if it exists, and books it exactly once if it does not. After either 503, wait Retry-After seconds and retry with the same key and the same body. If the answer is 422 idempotency_key_reused, the key belongs to a different order body: check your records. If, before your retry, your key was revoked or rotated out, API access was turned off, or your account was made inactive, the retry is refused with 401 or 403 (for example credential_revoked, api_access_disabled or account_inactive) before the order is looked up. That refusal says nothing about the original order. Find it as described below, with a key that still works, or ask Bullionwale.

Recovering an order whose outcome you do not know

For a pending order, or a POST that timed out or lost its connection:
  1. You have the order id (from a 202 or an earlier answer): poll GET /orders/{order_id}. Reads do not use your order budget, and they keep working when production trading is switched off.
  2. You have no id: list GET /orders?since=<a moment before you sent it> and match your client_order_id. It is echoed on every order you placed through the API, which is why you should always send one.
  3. Only if the order is still unknown, replay the POST with the same key and body. It returns the order if it was booked and books it once if it was not. A replay uses order budget like any POST. When production trading is switched off, the replay is refused with 403 production_trading_disabled like any order; that check happens when your key is authenticated, before any order budget is used. That refusal says nothing about whether the original was booked: go back to step 2.