Skip to main content
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.
Polymarket Perps is Polymarket’s perpetual futures venue. The Perps endpoints let you place and manage perpetual futures orders for your users through the Trade API you already integrate. Bravado routes each order to the venue in the user’s own Polymarket Perps account, where it is matched. Bravado is not the counterparty to any trade and does not hold the collateral: pUSD moves from the user’s deposit wallet to their venue account and back.
Enterprise plan only, switched on by Bravado. Perps is available on the Enterprise plan and on no self-serve plan. Being on Enterprise does not turn it on by itself: ask your Bravado integration manager to enable it for your organisation. Until it is enabled, every /v2/trade/perps/* path answers 403 INSUFFICIENT_SCOPE, naming the missing scope, even with a correctly signed request. Once enabled, your key needs perps.read for reads and trade.perps for everything that changes state.

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 every POST, honoured on PATCH and DELETE.
  • Same deposit wallet. Deposits are funded from the user’s deposit wallet, and withdrawals are always paid back to it.
What is different:
  • Deposit-wallet (V2) users only. A user still on a V1 Safe gets 409 USER_PROVISIONING_PENDING with reason: "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/withdraw until 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.
  • jurisdiction is ISO-3166-1 alpha-2; subdivision is ISO-3166-2 when you know it (for example UA-43).
  • method is kyc or ip_self_declaration.
  • A user in a blocked jurisdiction gets 403 GEO_BLOCKED and is never enrolled.
  • An attestation older than 180 days makes opens answer 409 USER_PROVISIONING_PENDING with reason: "attestation_stale". Call POST /v2/trade/perps/accounts again with a fresh attestation, under a new Idempotency-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.
Poll 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.
The answer is one of two:
  • 201: the venue answered. status is one of accepted, open, partially_filled, filled, cancelled, rejected or unknown (a venue status Bravado does not map), and venue_status is the venue’s raw string. For an IOC, FOK or post-only order, Bravado reads the order back before answering, so status and filled_size are the venue’s when the read-back resolves in time. If it does not, the 201 says accepted, with venue_status: null and no filled_size: read GET /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. Poll GET /v2/trade/perps/orders/{intent_id} after retry_after_ms until status is open or terminal. An order that never reached the venue ends as rejected with venue_status: "not_placed". Replaying the request with the same Idempotency-Key returns the same 202.
To find an order you placed, read 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 than min_notional. The venue has accepted such a close when what it leaves open is still worth at least min_notional; whether it accepts a whole-position close below min_notional is the venue’s call. If the venue does refuse a close, you get 400 INVALID_SIZE, or a 201 with status: "rejected" and venue_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_SIZE when what it leaves open, (position size − quantity) × mark price, would be worth less than min_notional. The refusal carries details.rule: "reduce_only_remainder_below_min_notional" and the numbers as decimal strings: details.position_size, details.remainder and details.min_notional. To close the whole position instead, send quantity: details.position_size.
  • On a 201, reduce_only_applied: true says 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.
The destination is always the user’s deposit wallet; there is no destination field. Poll 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 on GET /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 holds rule, a lower-case token, when Bravado names the rule it enforced (see Limits); this can be on any code. Only beside rule: "reduce_only_remainder_below_min_notional" does it also hold position_size, remainder and min_notional, as decimal strings. No other field appears in it.
Any refusal may come without 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 same Idempotency-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.
A 504 is an unknown outcome, never a failure. Bravado’s hosting edge turns every 502 and 503 into an HTML 504, so the JSON body never reaches you. The X-Bravado-Error-Code header usually does. Treat any 504 as “the request may have acted”:
  • Do not send it again under a new key. Retry with the same Idempotency-Key: Bravado either replays the stored answer or acts once.
  • For an order, look it up first at GET /v2/trade/perps/orders/ik:<your key>. While it answers pending_confirmation, keep polling; rejected with venue_status: "not_placed" means nothing was placed.
  • For a deposit or withdrawal, list GET /v2/trade/perps/deposits or /withdrawals before acting again.

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.
Quote request_id (or the X-Request-Id header on a 504) when you contact Bravado about a perps request.