> For the complete documentation index, see [llms.txt](https://titan-exchange.gitbook.io/titan/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://titan-exchange.gitbook.io/titan/developer-doc/special-order-types/order-types/otoco.md).

# Conditional Entry & Bracket

Buy on a condition, then protect the position automatically — one request, one signature, one deposit.

Add `onFillOco` to a `stop_loss`, `take_profit` or `slice` order — the **entry** — and when the entry fills, Titan creates a second order — the **bracket** — on the tokens the entry bought. One request, one signature, one deposit.

The bracket is created in the same database transaction that marks the entry `completed`, so the bought tokens are never withdrawable in between. From the moment the entry's swap is sent, they count as locked in [`GET /me/balance`](/titan/developer-doc/special-order-types/reference/endpoints.md#balances): first as the entry's pending output, then as the bracket's `amount`.

| Bracket field | Value                                                                                     | Settable |
| ------------- | ----------------------------------------------------------------------------------------- | -------- |
| `inputMint`   | The entry's `outputMint`                                                                  | No       |
| `outputMint`  | The entry's `inputMint`                                                                   | No       |
| `amount`      | The **actual on-chain output** of the entry's fill, with fees and slippage already netted | No       |

## Bracket shape

Supply `takeProfitPrice`, `stopLossPrice`, or both. At least one is required, and the bracket's `orderType` follows from which legs are present:

| Legs supplied          | Bracket `orderType` | Bracket trigger                                                                           |
| ---------------------- | ------------------- | ----------------------------------------------------------------------------------------- |
| Both                   | `oco`               | `takeProfitPrice` / `stopLossPrice`, plus a trailing stop leg if `trailingStopBps` is set |
| `takeProfitPrice` only | `take_profit`       | `triggerPrice = takeProfitPrice`                                                          |
| `stopLossPrice` only   | `stop_loss`         | `triggerPrice = stopLossPrice`, plus `trailingStopBps` if set                             |

The bracket is an ordinary order of that type. Read it at the matching detail endpoint — `GET /oco/{id}`, `GET /take-profit/{id}` or `GET /stop-loss/{id}` — or from the lists, and route by its `orderType`. It carries `parentOrderId` pointing back at the entry.

## `config.onFillOco`

Optional on `stop_loss`, `take_profit` and `slice`. On any other `orderType` it returns `400 VALIDATION_ERROR`.

| Field                    | Required       | Notes                                                                                                                                                                                                                                                                                                             |
| ------------------------ | -------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `takeProfitPrice`        | One of the two | String, `> 0`. On the **bracket's** `priceBasis`, scaled by the bracket's `10^priceDecimals`.                                                                                                                                                                                                                     |
| `stopLossPrice`          | One of the two | String, `> 0`. When both prices are present, **strictly less than** `takeProfitPrice`.                                                                                                                                                                                                                            |
| `priceDecimals`          | ✅              | Integer `0`–`18`. Independent of the entry's.                                                                                                                                                                                                                                                                     |
| `priceBasis`             | ❌              | `"pair"` (default) or `"usd"`. Prices the bracket's input token — the entry's `outputMint`. `"usd"` when that is a stablecoin returns `400 VALIDATION_ERROR` at path `config.onFillOco.priceBasis`. See [Price basis](/titan/developer-doc/special-order-types/order-types/stop-loss-take-profit.md#price-basis). |
| `trailingStopBps`        | ❌              | Integer `1`–`9999`. Makes the stop leg trailing. Requires `stopLossPrice`; otherwise `400 VALIDATION_ERROR` at path `config.onFillOco.trailingStopBps`.                                                                                                                                                           |
| `expiresAt`              | ❌              | Unix seconds, in the future. If the entry has `expiresAt`, must be **after** it.                                                                                                                                                                                                                                  |
| `outputRecipientAddress` | ❌              | Where the bracket's fill is delivered. Defaults to `userPubkey`; must be `userPubkey` or the user's manager. Echoed back on reads.                                                                                                                                                                                |

{% hint style="warning" %}
**The entry must deliver to the manager.** With `onFillOco` present, the entry's top-level `outputRecipientAddress` may be omitted — it resolves to the manager instead of the usual `userPubkey` default — or set to the manager. Any other value returns `400 VALIDATION_ERROR`. This is what lets the bracket trade the bought tokens without a second deposit. The **bracket's** `outputRecipientAddress` is where the round trip ends; leave it at the default to return the proceeds to the user's wallet.
{% endhint %}

`onFillOco` is immutable after creation.

## Example

Buy SOL with 1,000 USDC if SOL dips to $150, then bracket the position at $200 / $135 in USD:

```http
POST /orders/intent
X-Titan-Key: <partner-api-key>
X-Titan-User: <your stable user id>
Content-Type: application/json

{
  "orderType": "take_profit",
  "userPubkey": "<user's external Solana wallet>",
  "config": {
    "inputMint":  "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "outputMint": "So11111111111111111111111111111111111111112",
    "amount": "1000000000",
    "triggerPrice": "6666667",
    "priceDecimals": 9,
    "createdAs": "limit",
    "onFillOco": {
      "takeProfitPrice": "200000000",
      "stopLossPrice":   "135000000",
      "priceDecimals": 6,
      "priceBasis": "usd"
    }
  }
}
```

The entry is a buy-side order, so its `triggerPrice` is the pair ratio — SOL per USDC, `1/150` at 9 decimals — per the [price-basis rules](/titan/developer-doc/special-order-types/order-types/stop-loss-take-profit.md#price-basis). The bracket sells SOL, so it can use `"usd"`.

## Slice Order entries

On a [`slice`](/titan/developer-doc/special-order-types/order-types/slice.md) entry the slices execute immediately as usual, and the bracket is armed when the **whole Slice Order completes**, in the same transaction that marks the entry `completed`.

| Behavior         | Detail                                                                                                                                                                                                                                                                              |
| ---------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Bracket `amount` | The **sum of every slice's actual on-chain output**, read from each slice's own transaction — never a quote and never the wallet balance. Tokens already in the manager, or that arrive while the slices run from another source, are not included.                                 |
| While running    | Accumulated slice output counts as locked in `GET /me/balance`, as the entry's `amountReceived`. On completion, `amountReceived` is rewritten to the exact on-chain sum.                                                                                                            |
| After completion | The completed Slice Order owns nothing. `POST /orders/{entryId}/withdraw` returns `400 NOTHING_TO_WITHDRAW`.                                                                                                                                                                        |
| Partial fill     | A `failed` Slice Order — fewer slices landed than `executionsTotal` — creates **no** bracket. Slices that landed sit in the manager as available balance, reclaimable through the entry's order-level withdraw or a wallet-level withdraw. Unspent input is auto-returned as usual. |
| `expiresAt`      | Allowed in the spec, with no ordering rule, since a Slice Order has no expiry of its own.                                                                                                                                                                                           |

## Reads

| Order                                                                                                  | Adds                                                                                                                                                     |
| ------------------------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Entry — `GET /stop-loss/{id}` · `GET /take-profit/{id}` · `GET /slice/{id}` · lists                    | `onFillOco` (the stored spec, prices as strings, `outputRecipientAddress` always filled in), `childOrderId` (set once the bracket exists), `onFillError` |
| Bracket — `GET /oco/{id}`, or `GET /take-profit/{id}` / `GET /stop-loss/{id}` for a single-leg bracket | `parentOrderId`                                                                                                                                          |

Poll the entry. When `status` is `completed` and `childOrderId` is present, switch to polling the bracket — an ordinary order of its type, `active`, with `fundingSource: "balance"` and no deposit of its own. Do not assume the bracket is an `oco`; read its `orderType` from the list response or `GET /orders/{id}`.

## Lifecycle

| Phase                  | Behavior                                                                                                                                                                                                                                                                                                     |
| ---------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| Before the entry fills | Identical to any `stop_loss` or `take_profit`: pause, resume, cancel with withdrawal of the deposit, expiry, and failure with auto-return. In every non-fill terminal state the spec is discarded with the entry and no bracket is created.                                                                  |
| After the entry fills  | The entry is `completed` and owns nothing — `POST /orders/{entryId}/withdraw` returns `400 NOTHING_TO_WITHDRAW`. The bracket is a normal order of its type: pause, resume, [modify](/titan/developer-doc/special-order-types/guides/managing-orders.md#modify-a-trigger-order), and it fires like any other. |
| Exiting early          | Cancel the bracket: `POST /orders/{childOrderId}/cancel` with the default `withdraw: true` cancels it and builds the withdrawal of its full `amount` — the bought tokens — to `userPubkey`.                                                                                                                  |

### `onFillError`

If the entry's on-chain output cannot be verified from its transactions, the bracket is **not** created — a bracket sized from a quote could exceed what actually landed and fail when it fires. Titan retries verification for several minutes first, holding the entry in `executing` with the tokens locked. On a Slice Order entry, the worker retries for about 30 seconds and the recovery sweep makes the final attempt.

When verification fails, the entry completes with `onFillError: "amount_unverified"` and no `childOrderId`. The bought tokens remain in the manager as **available** balance, and the bracket can be placed manually from that balance.

## Related pages

* [Stop-Loss & Take-Profit](/titan/developer-doc/special-order-types/order-types/stop-loss-take-profit.md) — entry order types and price basis
* [Slice Orders](/titan/developer-doc/special-order-types/order-types/slice.md) — using a Slice Order as the entry
* [OCO Brackets](/titan/developer-doc/special-order-types/order-types/oco.md) — the two-leg bracket type
