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
}
FieldRequiredNotes
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
priceE6for limitmultiple of tickSizeE6
timeInForcegtc (default), ioc, fok, alo; market orders are ioc
postOnlyreject if it would take
reduceOnlyclose only
maxSlippageBpsbound execution to this many bps from mark
tpUnderlyingE6 / slUnderlyingE6attach exits at placement — buy-to-open only

Two things to internalize before your first order:

  • clientOrderId is mandatory. The venue uses it as the idempotency key, which is what makes a retry after a timeout safe.
  • maxSlippageBps is 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 terminal

Cancel 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:

ReasonWhen
WRITER_NOT_ALLOWEDsell-to-open without writer permission
PRICE_BANDlimit more than ±30% from mark
MAX_SLIPPAGEfill would exceed your maxSlippageBps
NO_NEW_OPEN_WINDOWopening trade inside the last noNewOpenWindowMs before expiry
MARKET_SESSION_CLOSEDsessionBound contract outside its underlying's session
ORACLE_OUTAGE / NO_MARK_PRICEno fresh underlying price, so no admission decision can be made
MARKET_NOT_ACTIVE / MARKET_EXPIRED / MARKET_SUSPENDEDcontract state
NO_POSITIONreduce-only or TP/SL against a position that is not there
REQ_OUT_OF_RANGEoff-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.


Did this page help you?