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
429or a503. - Same key and same body: no second order. You get the existing order back, with status
200and the headerIdempotent-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_rejectedagain, with the headerIdempotent-Replayed: trueand the samedetail.order_idanddetail.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_priceorclient_order_id): refused with422 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. On429 rate_limited, wait the number of seconds in theRetry-Afterheader, then retry with the same key. Never create a new key to get around a429.
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
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 answer201 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 open
- 202 pending
- 409 order_rejected
- 503 booking_not_completed
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 apending order, or a POST that timed out or lost its connection:
- You have the order id (from a
202or an earlier answer): pollGET /orders/{order_id}. Reads do not use your order budget, and they keep working when production trading is switched off. - You have no id: list
GET /orders?since=<a moment before you sent it>and match yourclient_order_id. It is echoed on every order you placed through the API, which is why you should always send one. - Only if the order is still unknown, replay the
POSTwith 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 anyPOST. When production trading is switched off, the replay is refused with403 production_trading_disabledlike 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.