Options: trading
Placing single and multi-leg options orders, cancels, positions, underlying-referenced exits, fee quotes and writing.
Signed endpoints — see Authentication. The key is the account binding; no request body carries a wallet or account field.
Options bodies are E6 integers, not decimal strings: priceE6, qtyE6, tpUnderlyingE6. See Notation.
Place an order
POST /api/v1/options/orders
{
"market": "BTC-20260925-81000-C",
"side": "buy",
"orderType": "limit",
"priceE6": 5242940000,
"qtyE6": 1000000,
"clientOrderId": "btc-81k-c-1",
"timeInForce": "gtc",
"maxSlippageBps": 150
}| Field | Required | Notes |
|---|---|---|
market | ✓ | contract symbol |
side | ✓ | buy / sell |
orderType | ✓ | limit / market |
qtyE6 | ✓ | E6 contracts, on the lotSizeE6 grid, ≥ minOrderSizeE6 |
clientOrderId | ✓ | idempotency key, ≤ 64 chars — required, unlike on perps |
priceE6 | for limit | multiple of tickSizeE6 |
timeInForce | gtc (default), ioc, fok, alo; market orders are ioc | |
postOnly | reject if it would take | |
reduceOnly | close only | |
maxSlippageBps | bound execution to this many bps from mark | |
tpUnderlyingE6 / slUnderlyingE6 | attach exits at placement — buy-to-open only |
Two things to internalize before your first order:
clientOrderIdis mandatory. The venue uses it as the idempotency key, which is what makes a retry after a timeout safe.maxSlippageBpsis the only real slippage guard. Without it, the only protection is the venue-wide price band of ±30% around mark — a fat-finger guard, not an execution guard. On a book that is often one-sided, a market order with no slippage bound can fill far from the displayed offer.
Exits are quoted on the underlying
tpUnderlyingE6 and slUnderlyingE6 trigger on the underlying's price, not on the option premium. For a BTC 81,000 call, tpUnderlyingE6: 90000000000 means "close this when BTC trades through 90,000". This is deliberate: premium is a function of spot, vol and time, so a premium-referenced stop fires on vol moves you did not intend to trade. Both levels are available only on long positions (buy-to-open).
Multi-leg baskets
POST /api/v1/options/orders/batch
{
"continueOnError": false,
"legs": [
{ "market": "BTC-20260925-81000-C", "side": "buy", "orderType": "limit", "priceE6": 5242940000, "qtyE6": 1000000, "clientOrderId": "spread-long" },
{ "market": "BTC-20260925-90000-C", "side": "sell", "orderType": "limit", "priceE6": 1120000000, "qtyE6": 1000000, "clientOrderId": "spread-short" }
]
}Rules: 1–6 legs, all on one underlying, no duplicate (market, side) pair and no buy+sell on the same contract. Each leg takes the same fields as a single order, including its own maxSlippageBps and exits.
The basket is sequential, not atomic. With the default continueOnError: false, the first failing leg stops the basket and the remainder come back skipped — legs of a spread are risk-coupled, and half a spread is not most of a spread. Walk the per-leg results and decide explicitly whether to unwind or complete the structure. Setting continueOnError: true means you accept partial structures.
Cancel
POST /api/v1/options/orders/cancel # {"orderId": "…"}
POST /api/v1/options/orders/cancel-all # {"market": "…"} — body optional; omit for all contracts
GET /api/v1/options/orders # open orders
GET /api/v1/options/orders/history # including terminalCancel is idempotent: an order that is already terminal returns cancelled: false rather than an error. Ownership is enforced in the domain layer — a cancel for someone else's order is a no-op, not an information leak. cancel-all accepts a missing body on purpose: an emergency stop must work from the simplest possible client.
Positions and exits
GET /api/v1/options/positions # ?includeClosed=true for history
POST /api/v1/options/positions/tpsl{ "positionId": "…", "tpUnderlyingE6": 90000000000, "slUnderlyingE6": 78000000000 }One endpoint sets, amends and clears. Send either level to set or amend it; send {"positionId": "…", "cancel": true} to clear both. cancel cannot be combined with a level — those are contradictory intents and the API refuses to guess. Long positions only; the direction of each level is validated against the contract (a call's take-profit must sit above the current underlying, and so on).
Fee quote
POST /api/v1/options/fee-quote
{ "symbol": "BTC-20260925-81000-C", "side": "buy", "priceE6": 5242947379, "qtyE6": 1000000, "reduceOnly": false }Returns the fee this account would actually pay, with VIP tier and any reduce-only waiver applied. Quoting a paused contract is harmless — the trading gate lives in order placement, not here.
Writing options
Selling to open locks maxPayoutPerUnitE6 × qty in USDC as collateral — full collateralization, no offset against other positions. It also requires the account to be on the writer allowlist; otherwise the order is rejected with WRITER_NOT_ALLOWED. Selling to close a long you hold is not writing and needs no allowlist.
Rejections
Options admission errors arrive as the message of the standard error envelope:
| Reason | When |
|---|---|
WRITER_NOT_ALLOWED | sell-to-open without writer permission |
PRICE_BAND | limit more than ±30% from mark |
MAX_SLIPPAGE | fill would exceed your maxSlippageBps |
NO_NEW_OPEN_WINDOW | opening trade inside the last noNewOpenWindowMs before expiry |
MARKET_SESSION_CLOSED | sessionBound contract outside its underlying's session |
ORACLE_OUTAGE / NO_MARK_PRICE | no fresh underlying price, so no admission decision can be made |
MARKET_NOT_ACTIVE / MARKET_EXPIRED / MARKET_SUSPENDED | contract state |
NO_POSITION | reduce-only or TP/SL against a position that is not there |
REQ_OUT_OF_RANGE | off-tick price, off-lot quantity, or below minNotionalE6 |
MARKET_SESSION_CLOSED on an equity contract during US market hours usually means the underlying's price feed is degraded rather than the market being shut — check sessionDegraded on the underlying's perp market before retrying.
Updated 9 days ago
