OCO Brackets
One deposit, two triggers — a take-profit ceiling and a stop-loss floor where the first fill cancels the other.
An oco order brackets a position with one deposit: a take-profit above and a stop-loss below. Whichever leg fires first swaps the full amount and cancels the other. One-Cancels-Other — hence the name.
The alternative is two separate orders, which would lock the deposit twice. OCO reserves it once.
Take-profit
price >= takeProfitPrice
Stop-loss
price <= stopLossPrice
Prices use the same fixed-point encoding and price basis as stop-loss and take-profit — a pair ratio by default, or USD per input token with priceBasis: "usd". Both legs share one priceDecimals and one priceBasis.
Config
POST /orders/intent
X-Titan-Key: <partner-api-key>
X-Titan-User: <your stable user id>
Content-Type: application/json
{
"orderType": "oco",
"userPubkey": "<user's external Solana wallet>",
"config": {
"inputMint": "So11111111111111111111111111111111111111112",
"outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
"amount": "1000000000",
"takeProfitPrice": "200000000",
"stopLossPrice": "50000000",
"priceDecimals": 6,
"expiresAt": 1767225600
}
}inputMint
✅
Must differ from outputMint.
outputMint
✅
amount
✅
String, > 0. One deposit, shared by both legs.
takeProfitPrice
✅
String, > 0. On the order's priceBasis, scaled by 10^priceDecimals.
stopLossPrice
✅
String, > 0, and strictly less than takeProfitPrice.
priceDecimals
✅
Integer 0–18, shared by both legs.
expiresAt
❌
Unix seconds, must be in the future. At expiry the deposit is auto-returned.
stopLossPrice >= takeProfitPrice returns 400 VALIDATION_ERROR — the bracket has to have a gap. And minOutputAmount is not supported on OCO: it's silently ignored if you send it, so don't offer it in your OCO UI.
Top-level fields (userPubkey, outputRecipientAddress, platformFee, onboardIfNeeded) and the confirm step are identical to every other order type — see Creating Orders. The minimum-notional floor applies to the full amount, as with any trigger order.
Read
GET /oco/{orderId} returns the base Order shape plus amount, takeProfitPrice, stopLossPrice, priceDecimals, priceBasis, expiresAt?, executedPrice?, executedPriceDecimals?, amountReceived?, pendingLeg?, parentOrderId?, and the stop-loss leg's trailing state (trailingStopBps?, currentTriggerPrice, highestObservedPrice?).
parentOrderId is set when the OCO was created automatically as the bracket of a conditional entry. Scale executedPrice by executedPriceDecimals — see executedPrice scale.
pendingLeg — values stop_loss or take_profit — labels which leg is mid-execution while the order is executing, and is absent otherwise. Use it to show the user which side is filling.
Returns 404 NOT_FOUND if the order doesn't exist, isn't this user's, or isn't an oco order.
Executions
An OCO fill is recorded under its leg's type — stop_loss or take_profit_full. There is no oco execution type. Read executionType on the single execution row to show which side fired.
Lifecycle
Identical to stop-loss and take-profit: active immediately after confirm, no retry, and auto-return on both failed and expired. Both legs' prices, expiry and trail can be changed in place with PATCH /oco/{orderId} — see Modify a trigger order. Cancelling before either leg fires leaves the deposit in the manager, so a cancelled OCO is the one case where order-level withdrawal matters.
Pausing stops both legs from being watched at once; resuming re-arms both.
Related pages
Stop-Loss & Take-Profit — price basis and the single-leg types
Conditional Entry & Bracket — an OCO created automatically when an entry order fills
Trailing Stops — making the stop-loss leg trail while the ceiling stays put
Creating Orders — the shared two-step flow
Last updated

