Authenticate with HMAC. Signature values in request examples are placeholders; compute a fresh signature over the exact method, path and body. See authentication and the runnable quickstart.
What stays the same
Perps is part of the Trade API, not a separate product:- Same base URL, keys and signing. Paths live under
/v2/trade/perps/, signed with the HMAC headers you already send. - Same users. Perps acts for one integration user at a time: send a user key, or your master key with
X-Partner-User-Id, exactly as on the other user routes. See sub-accounts. - Same
Idempotency-Key. Required on everyPOST, honoured onPATCHandDELETE. - Same deposit wallet. Deposits are funded from the user’s deposit wallet, and withdrawals are always paid back to it.
- Deposit-wallet (V2) users only. A user still on a V1 Safe gets
409 USER_PROVISIONING_PENDINGwithreason: "v2_wallet_required"on enrolment and on deposits. Migrate the user first. - The perps balance is separate. pUSD you deposit to Perps leaves the deposit wallet. It cannot back prediction-market orders or be sent out with
/v2/trade/withdrawuntil you withdraw it from Perps back to the deposit wallet. - Amounts, prices and quantities are decimal strings. Quantities are in the base asset (for example BTC), not in pUSD.
Endpoints
Instruments are named by the venue’s numeric
instrument_id. Order placement also accepts symbol (for example BTC-USD) as an alias.
Perps features roll out in stages. A route whose feature is not live yet answers
409 PERPS_CAPABILITY_UNAVAILABLE with Retry-After: 60 and a reason naming the feature (for example orders_modify or funding). Nothing reached the venue. Retry later with the same Idempotency-Key. GET /v2/trade/perps/instruments is always available.The flow
1
Check readiness
GET /v2/trade/perps/readiness tells you the next step for this user.Orders need
ready or credential_expiring. The response also carries rungs (credential, approval, funded, each ok, missing or unknown) and venue (cancel_only, in_liquidation).2
Enrol the user
POST /v2/trade/perps/accounts takes your attestation of where the end user is. Bravado supplies the user’s wallet; you send only the attestation.jurisdictionis ISO-3166-1 alpha-2;subdivisionis ISO-3166-2 when you know it (for exampleUA-43).methodiskycorip_self_declaration.- A user in a blocked jurisdiction gets
403 GEO_BLOCKEDand is never enrolled. - An attestation older than 180 days makes opens answer
409 USER_PROVISIONING_PENDINGwithreason: "attestation_stale". CallPOST /v2/trade/perps/accountsagain with a fresh attestation, under a newIdempotency-Key.
3
Deposit pUSD
POST /v2/trade/perps/deposits moves pUSD from the user’s deposit wallet to their venue account. There is a minimum deposit, and an amount below it is refused with 400 INVALID_BODY and a message naming the minimum. Amounts take at most 6 decimal places.GET /v2/trade/perps/deposits/{deposit_id} until state is credited. States are prepared, pending (sent, not yet confirmed and credited), confirmed (on chain, not yet credited by the venue), credited, failed, reverted and not_found. They update about once a minute, so polling faster returns the same object. Not enough pUSD on the deposit wallet answers 422 INSUFFICIENT_BALANCE.4
Set leverage
Leverage is set per instrument, for the whole account. It is not an order field.
leverage is an integer from 1 to 100, subject to your integration’s cap (409 LEVERAGE_CAP_EXCEEDED) and the venue’s risk tiers (422 INVALID_LEVERAGE). Leverage above the instrument’s maximum, or cross: true on an isolated-only instrument, is refused before the venue with 400 INVALID_BODY. cross: false selects isolated margin. The margin mode cannot change while a position or open orders exist on the instrument (409 POSITION_OR_ORDERS_EXIST). Use PATCH /v2/trade/perps/margin with a signed delta to add or remove isolated margin.5
Place orders
POST /v2/trade/perps/orders places an order. Idempotency-Key is required.201: the venue answered.statusis one ofaccepted,open,partially_filled,filled,cancelled,rejectedorunknown(a venue status Bravado does not map), andvenue_statusis the venue’s raw string. For an IOC, FOK or post-only order, Bravado reads the order back before answering, sostatusandfilled_sizeare the venue’s when the read-back resolves in time. If it does not, the201saysaccepted, withvenue_status: nulland nofilled_size: readGET /v2/trade/perps/orders/{order_id}for the outcome.202 pending_confirmation: the venue may or may not have the order. Do not send it again, and never under a new key. PollGET /v2/trade/perps/orders/{intent_id}afterretry_after_msuntilstatusisopenor terminal. An order that never reached the venue ends asrejectedwithvenue_status: "not_placed". Replaying the request with the sameIdempotency-Keyreturns the same202.
GET /v2/trade/perps/orders/ik:<your Idempotency-Key>. A read of a placed order returns the venue’s view, whose client_order_id is the order’s coid rather than your label, so keep your own map from label to order_id.DELETE /v2/trade/perps/orders cancels open orders only. A take-profit or stop-loss armed on a position stays armed until you cancel it by order_id or replace it with POST /v2/trade/perps/positions/{instrument_id}/tpsl.6
Close with reduce_only
To close, send the opposite side with
reduce_only: true. A reduce-only order can only shrink the position; one that would increase or flip it is refused with 422 REDUCE_ONLY_VIOLATION.- A close is exempt from
min_notional. A close is a reduce-only order against the position, no larger than it, and it may be worth less thanmin_notional. The venue has accepted such a close when what it leaves open is still worth at leastmin_notional; whether it accepts a whole-position close belowmin_notionalis the venue’s call. If the venue does refuse a close, you get400 INVALID_SIZE, or a201withstatus: "rejected"andvenue_status: "below_min_notional". Neither is ever reported as filled. - A partial close may not leave dust. A partial close is refused before the venue with
400 INVALID_SIZEwhen what it leaves open,(position size − quantity) × mark price, would be worth less thanmin_notional. The refusal carriesdetails.rule: "reduce_only_remainder_below_min_notional"and the numbers as decimal strings:details.position_size,details.remainderanddetails.min_notional. To close the whole position instead, sendquantity: details.position_size. - On a
201,reduce_only_applied: truesays the order went to the venue reduce-only.
7
Withdraw
POST /v2/trade/perps/withdrawals moves pUSD from the venue account back to the user’s deposit wallet. Pass an amount, or "max" for the venue’s current withdrawable.GET /v2/trade/perps/withdrawals/{withdrawal_id} until state is confirmed. When the venue pays out promptly, the withdrawal shows confirmed with its tx_hash within about 10 seconds of the request, so poll every few seconds at first. Bravado reports confirmed only once the pUSD transfer to the deposit wallet is verified on chain; tx_hash can appear while state is still pending. States are pending, confirmed, failed, rejected and unknown. An amount above withdrawable answers 422 INSUFFICIENT_BALANCE. To move the funds out of Bravado afterwards, use /v2/trade/withdraw.Limits
Every order rule is checked before the order reaches the venue. Each input is onGET /v2/trade/perps/instruments; read the values from there rather than hard-coding them, because they can change. A refusal frees your Idempotency-Key so you can fix the request and retry with the same key. When Bravado enforced the rule, the refusal names it in details.rule.
Your integration also has a policy that Bravado sets with you when Perps is enabled:
Errors
Errors use the Trade API envelope, plus two optional fields:reason, a lower-case token, when one code has several next steps.details, an object. It holdsrule, a lower-case token, when Bravado names the rule it enforced (see Limits); this can be on any code. Only besiderule: "reduce_only_remainder_below_min_notional"does it also holdposition_size,remainderandmin_notional, as decimal strings. No other field appears in it.
details: a refusal from request validation (a malformed field, a missing header) has none. Check that it is there before reading it, and read only the fields you know.
The reason values below are the ones you will meet most. The list is open: Bravado may send others, and a code may come with no reason at all. On a reason you do not know, read GET /v2/trade/perps/readiness before acting, rather than treating it as fatal.
When to retry, and with which key
Refused before the venue: retry with the same key. These codes mean nothing reached the venue. Fix the cause, or wait for it to clear, and send the request again with the sameIdempotency-Key:
GEO_BLOCKED, NOTIONAL_CAP_EXCEEDED, LEVERAGE_CAP_EXCEEDED, INSTRUMENT_NOT_ALLOWED, REDUCE_ONLY_MODE, PERPS_HALTED, VENUE_CANCEL_ONLY, ACCOUNT_IN_LIQUIDATION, PRICE_SOURCE_UNAVAILABLE, LOCAL_RATE_BUDGET_EXCEEDED, CLOCK_SKEW_REFUSED, OPERATION_DISABLED, PERPS_CAPABILITY_UNAVAILABLE, INVALID_PRICE, INVALID_SIZE, INVALID_BODY, and USER_PROVISIONING_PENDING from the readiness check.
Some of these codes can also come back after the venue was reached, and then the key is kept. VENUE_CANCEL_ONLY can be the venue’s own answer. INVALID_SIZE can be the venue’s refusal of a close. USER_PROVISIONING_PENDING with reason: "enrolment_in_progress" also keeps the key. In these cases a retry with the same key replays the stored answer (X-Idempotent-Replay: true).
Answered by the venue: the key is kept. Any other answer is stored and replayed for the same key (X-Idempotent-Replay: true). Read the order, position or transfer state first, and use a new key only once you know the first request did not act.
A 409 with PERPS_HALTED, VENUE_CANCEL_ONLY, CLOCK_SKEW_REFUSED or PERPS_CAPABILITY_UNAVAILABLE means “try later”, not a conflict to abandon. Wait for Retry-After when it is sent, then repeat with the same key. Generic HTTP clients rarely retry a 409 on their own, so handle these codes explicitly.
Perps error codes
When Bravado halts perps, opens, leverage increases and removing isolated margin (a negative
delta) answer 409 PERPS_HALTED. Cancels, reduce-only orders and closes, TP/SL cancels, leverage decreases, deposits, withdrawals and reads keep working, so your users can always exit.